Lane 11 PR 2b: labels from expressions, first/last/join, and two robustness fixes, in both engines (#737) #967

Merged
claude-bot merged 5 commits from build/lane11-2b-language into main 2026-09-30 10:16:52 +00:00
Collaborator

Lane 11 (#737) PR 2b: a follow-up to the sheet language (#961, #965). Both definition authors (#962, #963) hit the same gaps, which stopped the sheet showing what the drawing shows. Everything below is done in both engines at once, and pinned by shared fixtures.

Additions:

  1. Labels on derivation terms: a term can take a label_expr, evaluated in the field's scope. When it isn't null, its text replaces label, so a term can read "Dex 4" rather than "Attribute". note_expr works the same way. A null note is now left off rather than printed as "".
  2. Modifier labels: a modifier can take a label_expr, evaluated where its value is, so "Frightened 1" rather than "Frightened". It works for definition modifiers and for those carried by entries and conditions.
  3. first(list), last(list) and join(list, sep = ", "): these follow the same list rules as sum/count. Numbers go through the number-to-text rule. This is what lets the sheet show "Chain mail 16" and "d10 → 8, 7".
  4. Did-you-mean for unknown function names (jion → "join"), in both the validator and the compiler.

Robustness fixes: both were found porting to JS (#965), and both used to raise where the documented result is null.

  • An entry row whose kind isn't a string no longer raises.
  • A rank that isn't a finite number reads as "not a rank" (null).

Fixtures:

  • expr/first_last_join.json, 33 cases.
  • sheet/labels-from-expressions.json: "Dex 4", notes, null fallbacks, "Frightened 1", "Chain mail 16" and "d10 → 8, 7".
  • sheet/malformed-rows-and-infinite-ranks.json.
  • Totals: both engines run 342 expression cases and 15 sheet cases.
  • Fuzz: a differential fuzz extended to the new features (12,000 expressions, 2,040 sheets) found 0 mismatches.

Tests:

  • Backend: tests/sheets passes (486), and definition.schema.json is regenerated.
  • Frontend: eslint shows 0 errors, src/sheets passes (447), and so does the full vitest suite (3213).
  • Lint and changelog: ruff is clean, and changelog.py check passes.

The PF2e and 5e definitions adopt the new labels in a later PR (they validate either way).

Changelog: changelog.d/737-lane11-language-labels.added.md.

🤖 Generated with Claude Code

Lane 11 (#737) PR 2b: a follow-up to the sheet language (#961, #965). Both definition authors (#962, #963) hit the same gaps, which stopped the sheet showing what the drawing shows. Everything below is done in **both** engines at once, and pinned by shared fixtures. **Additions:** 1. **Labels on derivation terms:** a term can take a `label_expr`, evaluated in the field's scope. When it isn't null, its text replaces `label`, so a term can read "Dex 4" rather than "Attribute". `note_expr` works the same way. A null note is now left off rather than printed as "". 2. **Modifier labels:** a modifier can take a `label_expr`, evaluated where its value is, so "Frightened 1" rather than "Frightened". It works for definition modifiers and for those carried by entries and conditions. 3. **`first(list)`, `last(list)` and `join(list, sep = ", ")`:** these follow the same list rules as `sum`/`count`. Numbers go through the number-to-text rule. This is what lets the sheet show "Chain mail 16" and "d10 → 8, 7". 4. **Did-you-mean for unknown function names** (`jion` → "join"), in both the validator and the compiler. **Robustness fixes:** both were found porting to JS (#965), and both used to raise where the documented result is null. - An entry row whose `kind` isn't a string no longer raises. - A rank that isn't a finite number reads as "not a rank" (null). **Fixtures:** - `expr/first_last_join.json`, 33 cases. - `sheet/labels-from-expressions.json`: "Dex 4", notes, null fallbacks, "Frightened 1", "Chain mail 16" and "d10 → 8, 7". - `sheet/malformed-rows-and-infinite-ranks.json`. - **Totals:** both engines run 342 expression cases and 15 sheet cases. - **Fuzz:** a differential fuzz extended to the new features (12,000 expressions, 2,040 sheets) found 0 mismatches. **Tests:** - **Backend:** `tests/sheets` passes (486), and `definition.schema.json` is regenerated. - **Frontend:** eslint shows 0 errors, `src/sheets` passes (447), and so does the full vitest suite (3213). - **Lint and changelog:** ruff is clean, and `changelog.py check` passes. The PF2e and 5e definitions adopt the new labels in a later PR (they validate either way). Changelog: `changelog.d/737-lane11-language-labels.added.md`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Both engines (app/sheets and src/sheets):
- derivation term objects take label_expr (non-null text replaces label)
  and note_expr (the same as note; a null note is left off)
- modifiers take label_expr, evaluated where value is
- first(list), last(list), join(list, sep=", ") with the list rules
- rank() of a non-finite number is null; an entry row with a non-string
  kind no longer raises (no kind row defaults, not found by has())
- unknown function names get a did-you-mean in the validator
New conformance: expr/first_last_join.json (33 cases),
sheet/labels-from-expressions.json, sheet/malformed-rows-and-infinite-ranks.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into build/lane11-2b-language
Some checks failed
CI / Bot/backend version sync (pull_request) Successful in 1m1s
CI / Docker image build (pull_request) Successful in 1m17s
CI / Does this change touch the recording path? (pull_request) Successful in 52s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Backend lint (ruff) (pull_request) Successful in 2m1s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 2m38s
CI / Bot tests and audit (pull_request) Successful in 3m47s
CI / Feedback site tests (pull_request) Successful in 3m40s
CI / Frontend (shard 1/2) (pull_request) Successful in 11m45s
CI / Frontend (shard 2/2) (pull_request) Successful in 14m56s
CI / End-to-end journeys (phone) (pull_request) Failing after 5m36s
CI / End-to-end journeys (keyboard) (pull_request) Successful in 19m10s
CI / End-to-end journeys (desktop) (pull_request) Successful in 19m19s
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
5b44cb221d
Author
Collaborator

This PR now also carries PR 5c: the PF2e and 5e definitions adopt the new labels, so the sheet reads as drawn. It was stacked directly on this branch, and the previous run failed only on a runner fault (Docker "AlreadyExists" on the phone journeys, run 1701), so one fresh run covers both.

What PR 5c changes:

  • Only labels. Every value is unchanged, and all 19 PF2e and 5e fixtures pass in both engines and through the server.
  • PF2e:
    • Class DC, spell DC/attack and strikes read "Dex 4"/"Wis 4", not "Attribute 4 (Dex)".
    • HP reads "Ancestry 8 + 6 levels 60 (Class 8 + Con 2 each)".
    • Condition modifiers read "Frightened 1", "Stupefied 2" and "Drained 1".
  • 5e:
    • AC reads "Chain mail 16 + Shield 2".
    • HP reads "10 + 2 × (d10 → 8, 7) + Con 2 × 3".
    • Saves, skills and attacks read "Str 3 + Prof 2".
    • Spells read "Prof 3 · Wis 3".
    • Also "Exhaustion 2" and "Chain mail, needs Str 13".
  • Short ability names come from a tables.ability_short; armour names use first(...) and HP rolls use join(...).
  • Fragments: 737-lane11-pf2e-labels.changed.md and 737-lane11-5e-labels.changed.md.
**This PR now also carries PR 5c:** the PF2e and 5e definitions adopt the new labels, so the sheet reads as drawn. It was stacked directly on this branch, and the previous run failed only on a runner fault (Docker "AlreadyExists" on the phone journeys, run 1701), so one fresh run covers both. **What PR 5c changes:** - **Only labels.** Every value is unchanged, and all 19 PF2e and 5e fixtures pass in both engines and through the server. - **PF2e:** - Class DC, spell DC/attack and strikes read "Dex 4"/"Wis 4", not "Attribute 4 (Dex)". - HP reads "Ancestry 8 + 6 levels 60 (Class 8 + Con 2 each)". - Condition modifiers read "Frightened 1", "Stupefied 2" and "Drained 1". - **5e:** - AC reads "Chain mail 16 + Shield 2". - HP reads "10 + 2 × (d10 → 8, 7) + Con 2 × 3". - Saves, skills and attacks read "Str 3 + Prof 2". - Spells read "Prof 3 · Wis 3". - Also "Exhaustion 2" and "Chain mail, needs Str 13". - **Short ability names** come from a `tables.ability_short`; armour names use `first(...)` and HP rolls use `join(...)`. - **Fragments:** `737-lane11-pf2e-labels.changed.md` and `737-lane11-5e-labels.changed.md`.
Uses PR 2b's label_expr/note_expr: the varying attribute term is labelled
by its short name ("Dex" + value 4 renders "Dex 4") for class DC, spell
DC/attack and Strikes; HP reads "Ancestry 8 + 6 levels 60 (Class 8 + Con 2
each)"; valued conditions label their modifiers "Frightened 1" etc.
Values unchanged; fixtures' derivation and modifier labels updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AC 'Chain mail 16 + Shield 2', HP '10 + 2 × (d10 → 8, 7) + Con 2 × 3',
'Str 3 + Prof 2', proficiency '2 + ⌊(3 − 1) / 4⌋', 'Exhaustion 2',
'Chain mail, needs Str 13'. Values unchanged; fixture labels updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge branch 'build/lane11-5c-5e-labels' into build/lane11-5c-pf2e-labels
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 2m35s
CI / Backend lint (ruff) (pull_request) Successful in 1m18s
CI / Bot tests and audit (pull_request) Successful in 4m40s
CI / Feedback site tests (pull_request) Successful in 1m52s
CI / Bot/backend version sync (pull_request) Successful in 1m0s
CI / Docker image build (pull_request) Successful in 35s
CI / Does this change touch the recording path? (pull_request) Successful in 35s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Frontend (shard 2/2) (pull_request) Successful in 14m37s
CI / Frontend (shard 1/2) (pull_request) Successful in 19m37s
CI / End-to-end journeys (keyboard) (pull_request) Successful in 16m47s
CI / End-to-end journeys (desktop) (pull_request) Successful in 17m11s
CI / End-to-end journeys (phone) (pull_request) Successful in 15m47s
CI / Backend migration, tests, and audit (pull_request) Successful in 45m50s
0a19f3d12b
Sign in to join this conversation.
No description provided.