Generates the racial radial rows at build time from the UsableFeat column in the race feats tables, replacing the hand-maintained racial block in sow-topdatadata/classes/feats/global.json.
Why
Racial spell-like abilities reach the in-game radial only via an OnMenu row in every cls_feat_<class>.2da. Racial feats are granted by race, never by a class, so nothing adds them automatically — they were hand-written and prepended into every class table. The list drifts: this build already had 24 usable racial feats in the race tables but only 23 hand rows, so one activatable feat was silently missing.
What
racialUsableFeatRules() scans race_feat_*.2da datasets, collects UsableFeat=1 feats, emits one rule each: List=3, GrantedOnLevel=99, OnMenu=1. Deduped across races, sorted for deterministic output.
Row shape is exactly the in-game-verified hand rows. List=3 keeps it off every level-up selection list; GrantedOnLevel=99 is above the level cap so no class ever actually grants it (nLevelGranted is uint8_t); OnMenu=1 renders the button once the creature possesses the feat. Possession stays chargen / the login racial-feat sync (sow-codebase#359).
Reuses the existing globalRules injection path (same dedup, feat-existence check, label lookup) but applies unconditionally, so a leftover hand row in global.json deduplicates to a no-op — the sow-topdata cleanup lands separately.
Not sourced from feat.2da: a global feat flag would inject unrelated class abilities (e.g. a shadowdancer ability) into every class radial via multiclass.
Existing cls_feat global-injection build tests still pass with the new parameter.
End-to-end: with the racial block removed from global.json, a real build-topdata emits TieflingDarkness 3 99 1 once per table across all 21, 24 racial rows total.
Merge order
The sow-topdataglobal.json racial-row deletion depends on this — it must ship first (or the racial radial vanishes on the next topdata build with the released tool).
Closes #94.
Generates the racial radial rows at build time from the `UsableFeat` column in the race feats tables, replacing the hand-maintained racial block in `sow-topdata` `data/classes/feats/global.json`.
## Why
Racial spell-like abilities reach the in-game radial only via an `OnMenu` row in every `cls_feat_<class>.2da`. Racial feats are granted by race, never by a class, so nothing adds them automatically — they were hand-written and prepended into every class table. The list drifts: this build already had **24** usable racial feats in the race tables but only **23** hand rows, so one activatable feat was silently missing.
## What
- `racialUsableFeatRules()` scans `race_feat_*.2da` datasets, collects `UsableFeat=1` feats, emits one rule each: `List=3, GrantedOnLevel=99, OnMenu=1`. Deduped across races, sorted for deterministic output.
- Row shape is exactly the in-game-verified hand rows. `List=3` keeps it off every level-up selection list; `GrantedOnLevel=99` is above the level cap so no class ever actually grants it (`nLevelGranted` is `uint8_t`); `OnMenu=1` renders the button once the creature possesses the feat. Possession stays chargen / the login racial-feat sync (`sow-codebase#359`).
- Reuses the existing `globalRules` injection path (same dedup, feat-existence check, label lookup) but applies **unconditionally**, so a leftover hand row in `global.json` deduplicates to a no-op — the `sow-topdata` cleanup lands separately.
- Not sourced from `feat.2da`: a global feat flag would inject unrelated class abilities (e.g. a shadowdancer ability) into every class radial via multiclass.
## Tests
- `racial_feat_rules_test.go`: usable-only, non-usable excluded, cross-race dedup, non-`race_feat_` ignored, deterministic order.
- Existing `cls_feat` global-injection build tests still pass with the new parameter.
- End-to-end: with the racial block removed from `global.json`, a real `build-topdata` emits `TieflingDarkness 3 99 1` once per table across all 21, 24 racial rows total.
## Merge order
The `sow-topdata` `global.json` racial-row deletion depends on this — it must ship first (or the racial radial vanishes on the next topdata build with the released tool).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Racial spell-like abilities only reach the in-game radial when the feat
has an OnMenu row in every cls_feat_<class>.2da. Racial feats are granted
by race, not by a class, so nothing puts them there automatically - they
were hand-written as ~23 rows in sow-topdata data/classes/feats/global.json
and prepended into every class table. That list drifts: the build already
carried 24 usable racial feats in the race tables but only 23 hand rows,
so one activatable feat was silently missing from the radial.
Generate those rows at build time from the one source that already marks
them - the UsableFeat column in the race feats tables (race_feat_*.2da).
Every feat flagged UsableFeat=1 gets one cls_feat row (List=3,
GrantedOnLevel=99, OnMenu=1) injected into every class table, deduped and
sorted. List=3 keeps it off every level-up selection list, GrantedOnLevel=99
sits above the level cap so no class ever actually grants it, OnMenu=1
renders the button once the creature holds the feat (possession stays the
job of chargen / the login racial-feat sync, sow-codebase#359).
The rules reuse the existing globalRules injection path (same dedup,
feat-existence check and label lookup) but apply unconditionally, so a
leftover hand row in global.json deduplicates to a no-op and the
sow-topdata cleanup can land separately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review of #94 branch: tighten racialUsableFeatRules to match the spec's
'race_feat_*.2da' (prefix alone would admit a non-2DA dataset), and
replace the test's length-check-plus-loop with one reflect.DeepEqual.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
archvillainette
scheduled this pull request to auto merge when all checks succeed 2026-08-03 18:52:42 +00:00
xtul
approved these changes 2026-08-03 18:58:42 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #94.
Generates the racial radial rows at build time from the
UsableFeatcolumn in the race feats tables, replacing the hand-maintained racial block insow-topdatadata/classes/feats/global.json.Why
Racial spell-like abilities reach the in-game radial only via an
OnMenurow in everycls_feat_<class>.2da. Racial feats are granted by race, never by a class, so nothing adds them automatically — they were hand-written and prepended into every class table. The list drifts: this build already had 24 usable racial feats in the race tables but only 23 hand rows, so one activatable feat was silently missing.What
racialUsableFeatRules()scansrace_feat_*.2dadatasets, collectsUsableFeat=1feats, emits one rule each:List=3, GrantedOnLevel=99, OnMenu=1. Deduped across races, sorted for deterministic output.List=3keeps it off every level-up selection list;GrantedOnLevel=99is above the level cap so no class ever actually grants it (nLevelGrantedisuint8_t);OnMenu=1renders the button once the creature possesses the feat. Possession stays chargen / the login racial-feat sync (sow-codebase#359).globalRulesinjection path (same dedup, feat-existence check, label lookup) but applies unconditionally, so a leftover hand row inglobal.jsondeduplicates to a no-op — thesow-topdatacleanup lands separately.feat.2da: a global feat flag would inject unrelated class abilities (e.g. a shadowdancer ability) into every class radial via multiclass.Tests
racial_feat_rules_test.go: usable-only, non-usable excluded, cross-race dedup, non-race_feat_ignored, deterministic order.cls_featglobal-injection build tests still pass with the new parameter.global.json, a realbuild-topdataemitsTieflingDarkness 3 99 1once per table across all 21, 24 racial rows total.Merge order
The
sow-topdataglobal.jsonracial-row deletion depends on this — it must ship first (or the racial radial vanishes on the next topdata build with the released tool).🤖 Generated with Claude Code