Documenting Querying in BattleScribe
We need to document queries: what are legal combinations, how do they work, what are the results.
BS Nodes
The elements in BattleScribe that use queries are: condition, constraint, repeat.
These elements appear as children in other BattleScribe elements:
-
conditioncan be child of:modifier,modifierGroup,conditionGroup. -
repeatcan be child of:modifier,modifierGroup. -
constraintcan be child of:selectionEntry,selectionEntryGroup,entryLink,forceEntry,categoryLinkinforceEntry,categoryEntry.
It's important to list all elements that can contain modifiers, since then they can also contain conditions and repeats within those modifiers. modifier can be child of:
- all those that can have
constraint(see list above) -
profile,rule,infoGroup,infoLink.
Query components
The elements of a query are:
-
field- what is the counted value. -
scope- where (in what subtree) are the values counted. -
childIdaka Filter By - which of the candidate entries/values do we actually process. -
value- reference value that the query result is compared with. -
comparison-conditionandconstraintuses it to compare the result with a referencevalue. - additional options:
shared,value-is-percentage,include-child-selections,include-child-forces,round-up.
Known options
Field
Allowed values for a field in a Query:
-
selections- count the selections. -
forces- count the forces. - specific Cost - sum cost values of the given Cost Type, looking at elements returned by the query.
- specific Cost Limit - use roster limit of the given Cost Type (
scopehas to be Roster,filteris unused).
Special cases:
-
forcesfield allows only the followingscopevalues:force,roster, specific force entry. - Cost Limit field allows only
scope=roster. Thefieldis then formatted likelimit::{type-id}where{type-id}is the ID of the Cost Type.
Scope
Scope can be one of:
-
self- restricted -
parent -
ancestor- restricted -
primary-category- restricted -
force -
roster -
primary-catalogue - specific selection, category, or force entry
Special cases:
-
selfis not allowed inconstraints. -
ancestorandprimary-categoryare only allowed withcomparisonof(not)instance-of. - specific entries allowed:
- any Category Entry
- any Force Entry (including nested ones)
- any Selection Entry that is ancestor of the querying constraint/modifier
Filter
Filter can be one of:
-
any(Anything) - noop. -
unit/model/upgrade(Unit/Model/Upgrade) - all elements with specified EntryKind (type) are returned. - specific selection, category, or force entry
When filter has specified value:
-
any(anything) - all elements in thescopeare returned. - EntryType (unit/model/upgrade) - all elements in the
scopewith specified EntryKind (type) are returned. - a Category Entry - all elements in the
scopewith specified category linked are returned. - a Force Entry - all elements in the
scopethat are instance of that entry. - any Selection Entry - all elements in the
scopethat are instance of that entry.
Special cases:
-
childId/filterinconstraintis disabled. It is implicitly the constraint's parent entry. - complete reference of what is allowed when: https://github.com/BSData/phalanx/issues/45#issuecomment-1197306993
Value
- in
repeatused to divide query result into "buckets", and for each such bucket there are N repetitions of the modifier applied (N isrepeatsvalue in BS XML). -
repeatrequires a positivevalue. - in
constraintused to compare usingcomparisonoperator. - in
conditionused to compare usingcomparisonoperator. If it's ainstance-ofkind of operator, thevalueis unused.
Generally, negative values work as expected. The exception here is a -1 value for a type=max constraint - in that case, it is interpreted as "infinity" or "no limit". Further modifications of the value (e.g. by Modifiers) are calculated as normal, e.g. when a constraint of -1 is modifed to increase value by 1, now the maximum is 0 so the selections are not allowed. Any other negative value (e.g. -2) is treated normally (no special behavior).
Comparison
-
repeathas none. -
conditionhas all numeric comparison operators and two special ones (instance-of):-
(not-)equal to -
greater/less than (or equal) -
(not-)instance of.
-
-
constrainthasmin(greater than or equal) andmax(less than or equal).
Special cases:
- when
scopeisancestororprimary-category,comparisonmust be(not)instance-of, thus those scopes are not available forconstraintandrepeat. -
(not-)instance-ofvalue ofcomparisonis a special case inconditionqueries: there's nofielddefined,scopedefines the checked entry, andchildId/filterdefines the type/entry thescopeshould be "instance of". Whenfilteris:-
any(anything) - it's a noop, always satisfied. - EntryType (unit/model/upgrade) - the
scopeshould have that type. - a Category Entry - the
scopeshould have that category linked. - a Force Entry - the
scopeshould be instance of that entry. - any Selection Entry - the
scopeshould be instance of that entry.
-
Options
shared - the query should sum up all instances of childId/filter in scope, disregarding the selection path. BS author explainer. ❓ When is this option allowed in Data Editor UI?
value-is-percentage - the value should be interpreted as percentage, and is limited to the range [0; 100].
include-child-selections - the scope includes selection subtrees (recursive/all descendants).
include-child-forces - the scope includes force subtrees (recursive/all descendants).
round-up - used in repeat calculations, rounds the division of result by value up, instead of down as is by default.
Loose notes
-
constrainthascomparisondefined differently: it hastypeofminormax- those are equivalent togreater than or equalandless than or equalvalues oftypeincondition. -
valuecan be negative forconstraintandcondition. Details: https://github.com/BSData/phalanx/issues/45#issuecomment-1197259096 -
repeathas nocomparison(type), but instead declares a number of repetitions per multiplications of referencevaluein query result ((result / value) * repeats). -
constraintofmaxvalue has an interesting quirk. When an entry has a constraint of type=min scope=parent value=0, the entry is hidden in Roster Editor, as it's not allowed to be selected. As expected, using a Modifier to Set that constraint's value to -1 makes this entry selectable (unlimited) and it's correctly shown in Roster Editor. However, when using a Modifier to Decrease the value by 1 (making it0 - 1 = -1- unlimited), the Roster Editor does not show the entry (incorrectly?), as that entry is unlimited due to the new value of max constraint.
❓ What are other unobvious interactions?
Negative value
I've investigated negative values in detail.
Generally, negative values work "as normal", except "-1" for a type=max constraint, which is equivalent to no limit. The logic might not be tied to max, but for min a non-positive value is the same as no limit anyway. Do note, that any other negative value for a max is processed as that value (and not as "no limit").
BattleScribe Data Editor does some additional validations on value:
- for Repeats, it must be positive
- when "value is percentage", it must be between 0 and 100
As an interesting quirk, when an entry has a constraint of type=min scope=parent value=0, the entry is hidden in Roster Editor, as it's not allowed to be selected. As expected, using a Modifier to Set that constraint's value to -1 makes this entry selectable (unlimited) and it's correctly shown in Roster Editor. However, when using a Modifier to Decrease the value by 1 (making it 0 - 1 = -1 - unlimited), the Roster Editor does not show the entry (incorrectly?), as that entry is unlimited due to the new value of max constraint.
Filter entries
In different comparison/field/scope combinations, different entries are allowed as filters. We'll use numeric comparison to mean all comparison operators that operate on numbers - that excludes the (not)-instance-of comparisons. Those will be called instance-of comparison.
Field selections or Costs
When field is selections or a specific Cost or Cost Limit, the following rules apply.
The comparison must be numeric (for instance-of the field is disabled, and so cannot have value).
scope=self and scope=parent allows filter to be:
-
any,unit,model,upgrade; - any Category Entry;
- any Shared Selection Entry or Shared Selection Entry Group, or any of their descendant selection entries and groups;
- any descendant selection entry or group of the resolved
scopeentry.
scope=force and scope=roster allows filter to be:
-
any,unit,model,upgrade; - any Category Entry;
- any Shared Selection Entry or Shared Selection Entry Group, or any of their descendant selection entries and groups;
- any Force Entry;
- any Root Selection Entry or any of their descendant selection entries and groups.
scope=specific entry allows filter to be:
-
any,unit,model,upgrade; - any Category Entry;
- any Shared Selection Entry or Shared Selection Entry Group, or any of their descendant selection entries and groups;
- any descendant selection entry or group of the resolved
scopeentry.
Field disabled
When comparison is instance-of, the field is disabled and the following rules apply.
scope=self, scope=parent and scope=ancestor allows filter to be:
-
any,unit,model,upgrade; - any Category Entry;
- any Shared Selection Entry or Shared Selection Entry Group, or any of their descendant selection entries and groups;
- any Force Entry;
- any Root Selection Entry or any of their descendant selection entries and groups.
scope=primary-category allows filter to be:
-
any,unit,model,upgrade; - any Category Entry.
scope=force allows filter to be:
-
any,unit,model,upgrade; - any Category Entry;
- any Force Entry.
scope=primary-catalogue allows filter to be:
- any Catalogue.
scope=specific entry allows filter to be:
-
any,unit,model,upgrade - any Category Entry.
Field forces
When field is forces, the following rules apply.
The comparison must be numeric (for instance-of the field is disabled, and so cannot have value).
scope=force and scope=roster allows filter to be:
-
any; - any Force Entry.
scope=specific Force Entry allows filter to be:
-
any; - any Force Entry that is a descendant of the resolved
scopeforce entry.