# 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.json` rows 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_injections` YAML still loads only as a compatibility path when no applicable class feat `global.json` exists. - 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.json` using `FeatIndex.filter = "classskills"`. - Inject `feat:literate` only when the class table does not already include `feat: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 ```json { "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. ```json { "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_present` means every listed reference must exist. - `any_present` means at least one listed reference must exist, and the array must not be empty. - `unless_present` means 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.json` does not also receive YAML or hardcoded default class feat injections.