Generate racial radial rows from race UsableFeat (#94) #95

Merged
archvillainette merged 2 commits from feat/generate-racial-radial-rows into main 2026-08-03 18:58:44 +00:00
Owner

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

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)
archvillainette added 1 commit 2026-08-03 18:32:34 +00:00
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>
archvillainette added 1 commit 2026-08-03 18:46:22 +00:00
review: require .2da suffix on race feats tables, shrink rule test compare
ci / ci (pull_request) Successful in 3m28s
build-binaries / build-binaries (push) Canceled after 0s
cbfce696b2
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
archvillainette merged commit b47a8a7afd into main 2026-08-03 18:58:44 +00:00
archvillainette deleted branch feat/generate-racial-radial-rows 2026-08-03 18:58:44 +00:00
Sign in to join this conversation.