3.3 KiB
3.3 KiB
Class Feat Global Injection Contract
Status Snapshot
Current state as of 2026-06-05:
- Preferred class feat injection policy is authored in
topdata/data/classes/feats/global.json. global.jsonrows flow through the ordinary class feat expansion pipeline, including successor expansion, masterfeat expansion, class-skill filtering, reference resolution, deduplication, and final 2DA emission.- Legacy
topdata.class_feat_injectionsYAML still loads only as a compatibility path when no applicable class featglobal.jsonexists. - Toolkit hardcoded defaults are compatibility-only and must not be the normal source of project policy.
Objective
Native class feat generation must produce deterministic, legacy-equivalent effective output while keeping project policy in topdata authoring files.
Requirements
- For each class skill, inject the configured skill-focus masterfeat rows from
classes/feats/global.jsonusingFeatIndex.filter = "classskills". - Inject
feat:literateonly when the class table does not already includefeat:illiterate. - Inject the shared combat/menu feat rows declared in the manifest unless a
class already authors the equivalent
FeatIndex. - Explicit class feat rows take precedence over injected rows.
- Injections must be deterministic for identical inputs.
Manifest Shape
{
"position": "prepend",
"injections": [
{
"row": {
"FeatIndex": { "id": "feat:literate" },
"GrantedOnLevel": 1,
"List": 3,
"OnMenu": 0
},
"unless_present": [
{ "field": "FeatIndex", "id": "feat:illiterate" }
]
},
{
"row": {
"FeatIndex": {
"id": "masterfeats:skill_focus",
"filter": "classskills"
},
"GrantedOnLevel": -1,
"List": 1,
"OnMenu": 0
}
}
]
}
position defaults to append. The module uses prepend for class feat
injections to keep generated policy rows before per-class authored rows.
Global Injection Grammar
Each injection entry may use only row, require_present, any_present, and
unless_present. when_present is invalid and rejected; it is not an alias.
{
"row": {
"FeatIndex": { "id": "feat:example" }
},
"require_present": [{ "field": "FeatIndex", "id": "feat:required" }],
"any_present": [
{ "field": "FeatIndex", "id": "masterfeats:metamagic" },
{ "field": "FeatIndex", "id": "masterfeats:combat" }
],
"unless_present": [{ "field": "FeatIndex", "id": "feat:example" }]
}
require_presentmeans every listed reference must exist.any_presentmeans at least one listed reference must exist, and the array must not be empty.unless_presentmeans none of the listed references may exist.- Every present group on an entry must pass.
- Conditions observe the current rows in injection order, so earlier injections can affect later conditions.
Acceptance Criteria
- Class feat output contains the same effective shared feats as the prior YAML policy.
- Literacy injection is skipped for illiterate classes.
- Skill-focus masterfeat rows expand only for class skills.
- YAML compatibility continues to work for consumers that have not migrated.
- A project with
classes/feats/global.jsondoes not also receive YAML or hardcoded default class feat injections.