FACTSET Token
Purpose
This proposal covers a means for the data to declare _sets_ of named constants for rules objects (e.g deity) that have values provided by the data and that are fixed at data load time. The primary distinction between this and "FACT" is that this deals with groups of items where as a FACT is a single value. The overall proposal covers a number of tokens (many related to FACT as well) and this is the second in a set of proposals to disconnect the code from the specific underlying game mode.
The core of the proposal is a "FACTSET" token that allows the data team to specify specific groups of items (with normal LST file support like .CLEARALL making it usable in .COPY and .MOD lines)
Status
This is a draft proposal for architecture/code review. Syntax is not final and is subject to revision and limitations due to existing syntax which may not be apparent in this proposal.
Scope
This proposal is intended to cover factual information about sets (a list where repeated items are ignored) of objects applied to a PC.
"About the _object_" is key.
This means it is not intended to cover the SUBRACEs a PC has (that is a set of facts ABOUT the _PC_ that the objects can alter, we are ONLY covering sets of facts about the _object itself_). It is intended to cover things like the PANTHEONs of a Deity (a "set of facts" about that Deity that doesn't alter the PC)
In order to maintain the semantic definition of "fact", items here will be unmodifiable at runtime. If the intent is to modify an object, that is semantically a variable, and is outside the scope of this proposal.
Note for clarity: This modification statement is "unmodifiable at runtime", which means the lock occurs when LST load completes. They CAN be added to or cleared in a .MOD.
The distinction between "fact" and "variable" is significant. This is *intended* to cover a *limited* set of information, and support features *based upon* those limitations. Data is exchanging limitations for power, as seen below.
There will be future capabilities that are more flexible (and can be changed at runtime) but which will support a different (more limited) subset of features. That is out of scope of this proposal, however.
Background
Currently when new items are added to objects, it ends up requiring code intervention.
This has a number of unfortunate side effects:
- This gets stuck in a priority queue along with a lot of other work for the code team
- It binds the code tightly to the game mode (who says a PC has legs?)
- It often creates yet another PRExxx token to handle that item (PREDEITYALIGN, etc)
- It sometimes comes back as a request for a Primitive for use in CHOOSE
In order to get away from this tight binding, we need a more generic way of dealing with information in the data.
In addition to the work already done in the token/loader system and in the core, the final puzzle piece that enables this capability was the insertion of Freemarker as the output/template system.
Specifically, as we get to 6.5, we will be enabling a different system for output based on Freemarker rather than on the existing output tokens. (see https://groups.yahoo.com/neo/groups/pcgen_developers/conversations/messages/4165 )
This will enable more data control over what is output without relying on core knowledge.
Proposal
Base Token (FACTSET)
FACTSET:x|y|y x is an identifier y is a factual value
An identifier (x above) must have been defined in a FACTSETDEF line in a DATACONTROL LST file (*_datacontrols.lst).
FACTSET appends. .CLEARALL is available to reset the list.
It will be slowly, but consciously applied until it is eventually a global capability.
This does NOT support PRExxx (if the item is changing, then semantically it is NOT a fact - see "Background" above)
e.g. a Deity might have:
FACTSET:PANTHEONS|Greek|Roman
.CLEARALL will be supported as a "y" value, and it will clear just the set of facts for the single identifier. If .CLEARALL appears as a y value, it MUST be alone or the first y value on the FACTSET token.
It is important to note that FACTSET cannot be used in a Campaign (PCC) file. This is because the Campaign files must be loaded to know what the data control files are, and then the data control files define what FACTSETs are legal. This is a race condition we do not intend to work around. FACTSET is simply not legal in Campaign files.
PCC (Campaign) LST token (DATACONTROL)
In order to make useful work of a FACTSET, we require a few things. One of these is a set of controls for how the FACTSET is used. This requires a Campaign LST file token in order to store those controls:
DATACONTROL:x
x is a file (as per other files in the Campaign LST file)
Data Control LST Contents
First Token (FACTSETDEF)
The Data Control LST file controls how facts (and in the future other things) are handled by the code. Specifically, we can have a line define legal facts:
FACTSETDEF:x|y <> ...tokens... x is a file type (e.g. DEITY for Deity LST files) y is the identifier (then usable as x in the FACTSET LST token)
Note: The "x" Items will be hardcoded. See FACT Token legal values
This token MUST appear as the first token on the line in the file.
A Second FACTSETDEF with matching values here but different DATAFORMAT: will produce an error, since you have two conflicting items with the same identifier.
Duplicate FACTDEF with matching x, y and DATAFORMAT: are legal (thus allowing it to be present in multiple sources), but other tokens on the line are CUMULATIVE (dupe entries even in different files ACT LIKE A MOD, see DATAFORMAT for the exception)
DATAFORMAT
DATAFORMAT:x
This token is REQUIRED. A FACTSETDEF with no DATAFORMAT will produce an LST load error.
Note: The "x" Items will be hardcoded. In addition to "STRING", it is expected IN THE FUTURE that the same items that are valid as the "x" value of QUALIFY (the existing Global token) would also be legal here. For currently legal items, see the FACT Token page
Note: This is an exception to a duplicate FACTDEF: acting as a MOD. Specifically, A DATAFORMAT: with a type that does not match a previous value will produce an error, since you have two conflicting FACTs with the same identifier.
VISIBLE
For any given FACTSETDEF, we can control how that FACTSET is used. This allows us to make it visible to the end user, or display in output.
VISIBLE:x
x is:
YES NO (default is NO) DISPLAY EXPORT
For example:
FACTSETDEF:DEITY|Pantheons <> DATAFORMAT:STRING <> VISIBLE:EXPORT
EXPORT or YES would enable the output in Freemarker:
${deity.pantheons}
(which would produce an error if VISIBLE:NO or VISIBLE:DISPLAY)
EXPORT or YES or will trigger display in the UI, specifically in the items about the object, in Gui2InfoDisplay (code will know the meaning of this)
In case of .MOD (see FACTSETDEF for multiple definitions acting as a .MOD) this overwrites
Note: If a FACTSET def with a given identifier (z in the FACTSETDEF token) is VISIBLE then a FACT of the same identifier must not be visible. Otherwise, the implied output token would be ambiguous.
Note: Because this becomes an output token and is a "list" of items, conscious consideration should be made by the data team whether to keep these singular or plural. This will not be enforced by the code, but I would recommend selecting a data standard.
SELECTABLE
For any given FACTSETDEF, we can control how that FACT is used. This allows us to make it usable in CHOOSE or other locations where a Primitive is legal:
SELECTABLE:
x is:
YES NO (default is NO)
For example:
FACTSETDEF:DEITY|Pantheons <> DATAFORMAT:STRING <> VISIBLE:YES <> SELECTABLE:YES
SELECTABLE:YES enables use in a CHOOSE:
CHOOSE:DEITY|PANTHEONS=Greek
Given the above FACTSETDEF, this would allow a choice of any Deity with the 'PANTHEONS' FACTSET including 'Greek'
(It would produce an error if the FACTSETDEF for Pantheons was SELECTABLE:NO)
In case of .MOD (see FACTSETDEF for multiple definitions acting as a .MOD) this overwrites
REQUIRED
For any given FACTSETDEF, we can make that FACTSET required for a certain type of object.
REQUIRED:x
x is:
YES NO (default is NO)
For example:
FACTSETDEF:DEITY|Pantheons <> DATAFORMAT:STRING <> VISIBLE:YES <> REQUIRED:YES
This would then trigger an LST load error if a Deity did not have the "Pantheons" Fact assigned.
In case of .MOD (see FACTSETDEF for multiple definitions acting as a .MOD) this overwrites
As caution to the data team: Use of this forces it across ALL loaded data, NOT JUST DATA FROM THE LOCAL PCC FILE. That likely means this is used sparingly, and only in base sets, otherwise the additional sets would have to .MOD a lot of stuff... An item would "pass" the required test if and only if there is content at the end of load. Data followed by .CLEARALL without additional data would trigger an error.
WARNING: A FACTSETDEF where the "x" value of the FACTSETDEF token is "GLOBAL" MUST NOT be Required. Using REQUIRED:YES on a global FACTSETDEF will cause a load error.
DISPLAYNAME
For any given FACTSETDEF, we can control the name displayed for that type of item in the UI
DISPLAYNAME:x
x is a String (used to describe this fact in the UI)
Intent is that this is used to describe the object and is the string that will be displayed in the UI
In case of .MOD (see FACTSETDEF for multiple definitions acting as a .MOD) this overwrites
EXPLANATION
For any given FACTSETDEF, we can explain the purpose it serves in the data
EXPLANATION:x
x is a String
Intent is this is used to describe what the object is for purposes of the LST editor or readers of the data
In case of .MOD (see FACTSETDEF for multiple definitions acting as a .MOD) this overwrites
Global PREFACTSET
Of course, being able to define a FACTSET also demands something like:
PREFACTSET:w,x,y=z w is a number x is the type (e.g. DEITY) y is the identifier (e.g. PANTHEONS) z is the required value
For example:
PREFACTSET:1,DEITY,PANTHEONS=Greek
"x" matches the same strings used in the output file, which are shown on the FreeMarker Facet Output page. In some cases, an additional piece of information is required, as with equipment. See the FACT Token page for a specific example
Understanding Processing Order
It is possible, albeit a somewhat rare case, for an output name (interpolation in FreeMarker terms) to appear in multiple places in the hierarchy. Consider the following example:
- Create a GLOBAL FACTSET called "Hands"
- Support the output of "Hands" on Race (HANDS is currently an integer value)
In this case, the exact same priority rules that occur for loading occur for output. The "more local" output value will "win" (So Race [using ${race.hands}] would output an integer and every other object type [such as ${deity.hands}] would output the FACTSET.
Code Team Awareness
Generic behavior
This is designed to provide generic behavior to get us "out of the business" of dealing with minor token requests. There are actually dozens of "facts" about Equipment that have been requested for MSRD and this proposal would replace MANY of the existing NEWTAG proposals, and do it with less code (and without risk of copy/paste error)
Caching
One of the weaknesses we have in our existing Primitive system is multiple construction and caching. Let's take something like this in the data:
CHOOSE:DEITY|ALIGN=LG
If this appears twice, then the unfortunate part is that we create two Deity ALIGN Primitives. This is wasteful, albeit minor.
The challenge is that we also want to ensure that we don't have to redo work. So if we had that CHOOSE twice, we would want to cache that information. With multiple primitives that is not possible. With a centralized PrimitiveFactory (or whatever) it becomes practical to build the Primitive once, and then that Primitive can itself cache results.
However, a cache is only practical if the underlying information cannot change. (If it can change, it's both not a fact, and not cacheable)
This provides a generic method of dealing with facts that ensures those facts *do not change*, thus any infrastructure we build can be cacheable (and we can automatically build that infrastructure)
Challenges
Capitalization
Frankly this is a bear. Currently we import LST files in case-insensitive form (technically all caps), but Freemarker output is case sensitive. So the conversion of Symbol (or however it appears in the FACTSETDEF) to .symbol in the output is a convention we have to agree upon:
- Freemarker is always in lower case for a FACTSET:
- We enforce an identifier to appear in all caps (not strictly required, so perhaps this is a data standard)
Localization
Currently Gui2InfoFactory uses internationalized strings to identify each item (e.g. Symbol). In order to "unhardcode" Gui2InfoFactory, we need to put that into the FACTSETDEF line as well, but that raises the "localization in data" debate.