Support for global.json files
This commit is contained in:
@@ -1,207 +1,76 @@
|
||||
# Global Feat Injection — Specification
|
||||
# Class Feat Global Injection Contract
|
||||
|
||||
## Status Snapshot
|
||||
|
||||
Current state as of 2026-05-24:
|
||||
Current state as of 2026-06-05:
|
||||
|
||||
- Current code reads class feat injection policy from
|
||||
`topdata.class_feat_injections` in `nwn-tool.yaml`.
|
||||
- `global_feats` injects concrete `feat:*` references into each
|
||||
`classes/feats/*.json` table, unless the class already authors the same feat.
|
||||
- `global_feats` supports `require_present` and `unless_present` conditions
|
||||
against other feat references already present in the class feat table.
|
||||
- `class_skill_masterfeats` expands configured `masterfeats:*` rows once for
|
||||
each class skill.
|
||||
- If a project omits `topdata.class_feat_injections`, the toolkit keeps the
|
||||
former hardcoded default injections for compatibility.
|
||||
- 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
|
||||
|
||||
Ensure the native class feat generation pipeline produces **functionally identical output** to the legacy system by applying deterministic global feat injections.
|
||||
|
||||
---
|
||||
Native class feat generation must produce deterministic, legacy-equivalent
|
||||
effective output while keeping project policy in topdata authoring files.
|
||||
|
||||
## Requirements
|
||||
|
||||
### R1 — Skill-based master feats
|
||||
- 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.
|
||||
|
||||
For every **class skill**, inject:
|
||||
|
||||
- `masterfeats:skillfocus`
|
||||
- `masterfeats:greaterskillfocus`
|
||||
|
||||
With properties:
|
||||
|
||||
````json
|
||||
{
|
||||
"GrantedOnLevel": "-1",
|
||||
"List": "1",
|
||||
"OnMenu": "0"
|
||||
}
|
||||
|
||||
For the current implementation, `masterfeats:greaterskillfocus` is injected with
|
||||
`GrantedOnLevel = 12`, not `-1`.
|
||||
|
||||
---
|
||||
|
||||
### R2 — Spellcasting master feats
|
||||
|
||||
If class core defines:
|
||||
|
||||
- `"SpellCaster" = 1`
|
||||
|
||||
Then inject:
|
||||
|
||||
- `masterfeats:spellfocus`
|
||||
- `masterfeats:greaterspellfocus`
|
||||
|
||||
Historical note:
|
||||
|
||||
- This rule is not currently implemented as an automatic global injection in
|
||||
`native.go`.
|
||||
- Spell-focus expansion is supported when the class feat data explicitly
|
||||
references `masterfeats:spellfocus`.
|
||||
|
||||
If automatic spellcaster-driven injection is still required, it remains
|
||||
outstanding work.
|
||||
|
||||
With properties:
|
||||
## Manifest Shape
|
||||
|
||||
```json
|
||||
{
|
||||
"GrantedOnLevel": "-1",
|
||||
"List": "1",
|
||||
"OnMenu": "0"
|
||||
}
|
||||
````
|
||||
|
||||
---
|
||||
|
||||
### R3 — Literacy rule
|
||||
|
||||
Inject:
|
||||
|
||||
- `feat:literate`
|
||||
|
||||
**Only if**:
|
||||
|
||||
- `feat:illiterate` is NOT present
|
||||
|
||||
With properties:
|
||||
|
||||
```json
|
||||
{
|
||||
"GrantedOnLevel": "1",
|
||||
"List": "3",
|
||||
"OnMenu": "0"
|
||||
"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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### R4 — Default combat/menu feats
|
||||
|
||||
Always inject:
|
||||
|
||||
- `feat:special_attacks`
|
||||
- `feat:throw`
|
||||
- `feat:grapple`
|
||||
- `feat:offensive_fighting`
|
||||
- `feat:defensive_fighting`
|
||||
- `feat:horse_menu`
|
||||
|
||||
With properties:
|
||||
|
||||
```json
|
||||
{
|
||||
"GrantedOnLevel": "1",
|
||||
"List": "3",
|
||||
"OnMenu": "1"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### R5 — Conditional combat feats
|
||||
|
||||
Inject:
|
||||
|
||||
- `feat:power_attack`
|
||||
- `feat:combat_expertise`
|
||||
|
||||
With properties:
|
||||
|
||||
```json
|
||||
{
|
||||
"GrantedOnLevel": "-1",
|
||||
"List": "0",
|
||||
"OnMenu": "1"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### R6 — Override precedence
|
||||
|
||||
- Explicitly authored class feat rows take precedence over injected rows.
|
||||
- Injection MUST NOT replace explicit class feat data.
|
||||
- Dataset overrides are applied before class feat injection in the native
|
||||
dataset merge pipeline; author a class feat row directly to override or
|
||||
suppress the injected equivalent for a class.
|
||||
|
||||
---
|
||||
|
||||
## Behavioral Rules
|
||||
|
||||
### B1 — Output equivalence
|
||||
|
||||
- The resulting feat set MUST match legacy output **in substance**.
|
||||
- Exact row ordering and numeric IDs are NOT significant.
|
||||
|
||||
### B2 — Deterministic inclusion
|
||||
|
||||
- All injections MUST occur consistently for identical inputs.
|
||||
- No randomness or order-dependent behavior is allowed.
|
||||
|
||||
### B3 — Conditional correctness
|
||||
|
||||
- `feat:literate` MUST NOT be injected if `feat:illiterate` exists.
|
||||
- Spell focus feats MUST ONLY be injected when `"SpellCaster" = 1`.
|
||||
|
||||
### B4 — Integration with pipeline
|
||||
|
||||
- Injected feats MUST flow through the same processing stages as authored feats.
|
||||
- They MUST be subject to:
|
||||
- overrides
|
||||
- deduplication
|
||||
- final emission rules
|
||||
|
||||
---
|
||||
`position` defaults to `append`. The module uses `prepend` for class feat
|
||||
injections to keep generated policy rows before per-class authored rows.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
An implementation is considered correct if:
|
||||
|
||||
1. **Parity**
|
||||
- For any given class input, native output produces the same effective feats as legacy.
|
||||
|
||||
2. **Injection coverage**
|
||||
- All required feats (R1–R5) appear when conditions are met.
|
||||
|
||||
3. **Exclusion correctness**
|
||||
- No invalid injections occur (e.g., literacy conflict).
|
||||
|
||||
4. **Override dominance**
|
||||
- Any override affecting injected feats is correctly applied.
|
||||
|
||||
5. **Stability**
|
||||
- Repeated runs with identical input produce identical output.
|
||||
|
||||
---
|
||||
|
||||
## Non-Negotiables
|
||||
|
||||
- This is **parity-critical**, not a best-effort feature.
|
||||
- Native pipeline MUST be capable of fully replacing legacy output.
|
||||
- There MUST be **zero dependency** on legacy systems at runtime.
|
||||
- Any deviation from legacy-equivalent output is a **defect**.
|
||||
- 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.
|
||||
|
||||
Reference in New Issue
Block a user