Skip to content

Equipment & inventory: item schema, weapons/armor/gear catalog, character items, sheet panel#47

Merged
StreamDemon merged 4 commits into
mainfrom
feat/equipment-inventory
Jul 7, 2026
Merged

Equipment & inventory: item schema, weapons/armor/gear catalog, character items, sheet panel#47
StreamDemon merged 4 commits into
mainfrom
feat/equipment-inventory

Conversation

@StreamDemon

@StreamDemon StreamDemon commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Closes #43.

Items become first-class: a page-stamped catalog, character inventory with per-instance state, armor as a wearable ablative pool, and the dossier's equipment surface.

Rules layer

  • schema/items.ts — discriminated union over kind:
    • Weapons: category (future W.P. wiring), structured damage (formula dice + sdc/md type, with the printed multi-setting sentence in note beside the headline dice, like ppeNote), range/payload as printed strings.
    • Armor: S.D.C. armor = ar + sdc pool; M.D.C. armor = mdc.mainBody as a dice formula, because the book prints both constants (Gladiator "70", p.267) and per-suit rolls (Ley Line Walker concealed: "2D6+32 M.D.C. main body", p.113). Refinements reject the contradictions at load: armor must be exactly one of the two shapes, and an M.D.C. shell cannot declare an A.R. (there is no A.R. on M.D.C. armor).
    • Gear: name + printed notes, no mechanics.
  • content/items/items.json — 34 items transcribed from rendered pages this session: 8 armors (pp.113/267–268), 11 weapons (energy pp.268–270; conventional damage from the W.P. sections pp.326–329), 15 gear entries from the LLW Standard Equipment list (p.116). Counts pinned in tests.
  • engine/items.ts — catalog load + id index, armorNeedsRoll/rollArmorMdc/armorMaxPool for per-suit capacity, and damageArmor implementing the printed absorb rule: "the armor absorbs the attack — subtract the damage from the armor's S.D.C." (p.287) — the suit takes the whole hit, floors at 0, and nothing spills onto the body; a depleted suit stops protecting future hits.
  • Character: items[] (itemId + worn + rolledMdc) and current.armor — the living-vitals pattern (feat: living vitals — current vs. max, P.P.E. spend on cast, damage tracking #40/Recovery rates & healing spells: rest/meditation/ley draw, treatment days, spell healing with targets #41): maximum on content (or the per-suit roll), live remainder on the character, illegal states rejected in deriveSheet (worn/rolledMdc are armor-only, one worn suit, roll within printed dice, pool ≤ max). The sheet surfaces equipment and the worn armor layer.

Backend

Web

  • EQUIPMENT — MANIFEST panel: kind-tagged rows, weapons roll their printed dice into the telemetry log client-side (like skills/saves — table moments, not records), armor rows wear/doff, everything discards, and a catalog picker acquires.
  • Worn-armor bar beside the vitals in rust (amber family — armor is NOT magic), current / max with the printed dice range on hover.
  • The Damage control gains an Armor toggle routing hits to the suit; telemetry distinguishes DAMAGE from DAMAGE (ARMOR). All new per-character control state resets on route-id change.

Verification

  • vp check and root vp test green: 241 tests (was 219), including pinned catalog counts, printed-value assertions with page cites, armor-shape refinements, absorb/floor/no-spill semantics, and backend equip/damage flows.
  • Driven live in the browser: acquired the LLW concealed suit (telemetry reported the per-suit roll, 39, inside the printed 34–44), wore it, dealt body damage (armor untouched), armor damage (body untouched), overkill (floored at 0, no spill), rolled weapon dice into the log, full-restored, doffed, and navigated to another character (fresh log, no leaked state, zero console errors).

Out of scope, per the issue: strike-vs-A.R. resolution and M.D.C. combat mechanics, W.P. proficiency bonuses, encumbrance, credits bookkeeping.


Summary by cubic

Adds a first-class equipment system with a page-stamped catalog and character inventory, plus a worn-armor layer that absorbs damage with no spill. Adds an equipment panel, a rust-toned armor bar, and an Armor toggle to route hits to a suit.

  • New Features

    • Rules/catalog: item schema (weapon/armor/gear), 34-item catalog with page cites, and helpers (getItem, itemsByKind, armorNeedsRoll/rollArmorMdc/armorMaxPool, damageArmor per p.287).
    • Character model/sheet: inventory entries with per-instance worn and rolledMdc; deriveSheet enforces armor-only fields, one worn suit, roll within printed dice, and current.armor ≤ max; sheet shows equipment and the worn-armor bar.
    • Backend: addItem (rolls dice-capacity suits at acquisition), removeItem (clears pool if the worn suit is dropped), equipArmor (exclusive; resets pool on change), and applyDamage with toArmor to land hits on the suit.
    • Web UI: EQUIPMENT — MANIFEST (click weapon dice to roll; wear/doff; discard; acquire from catalog) and a rust-toned armor bar beside vitals; Damage control gains an Armor toggle; per-character control state resets on character switch.
    • Scope guardrails: strike-vs-A.R./full M.D.C. combat remain resolver/GM-adjudicated (use the toArmor toggle); encumbrance/credits are out of scope.
  • Bug Fixes

    • applyDamage(toArmor) now refuses hits on a depleted suit; GMs route those to the body per p.287.
    • equipArmor is a no-op if the requested suit is already worn, so repeated clicks can’t “refill” armor.
    • Inventory writes (removeItem, equipArmor) verify the client’s instance snapshot (index + itemId/worn/rolledMdc) to avoid stale-index races.
    • Unequip also verifies the worn-suit snapshot (equipArmor(null, expect)), and is a no-op when nothing is worn.

Written for commit 11920c7. Summary will update on new commits.

Review in cubic

StreamDemon added 2 commits July 7, 2026 14:41
…layer

Rules layer: schema/items.ts (weapon/armor/gear discriminated union; M.D.C.
armor rejects A.R., S.D.C. armor requires A.R. + pool), 34-item catalog
transcribed from RUE pp.113/116/267-270/326-329, engine catalog with per-suit
M.D.C. rolls (LLW concealed 2D6+32, p.113) and the p.287 absorb rule (armor
takes the whole hit, floors at 0, never spills onto the body).

Character: items[] with per-instance worn/rolledMdc state; current.armor is
the worn suit's remaining pool (living-vitals pattern). deriveSheet resolves
items, enforces armor-only worn/rolledMdc, one worn suit, roll-in-printed-
range, and pool-below-max; surfaces equipment + worn armor on the sheet.

Backend: addItem (rolls dice-capacity suits at acquisition, one transaction),
removeItem (clears the pool with the worn suit), equipArmor (exclusive worn,
fresh pool on change), applyDamage toArmor flag (strike-vs-A.R. adjudication
stays with the GM until the combat resolver, like elapsed time in #41).

PAGE_MAP.md gains the equipment chapter index.
The dossier grows the inventory surface: an EQUIPMENT — MANIFEST panel
(rows with kind tags; weapons roll their printed dice into the telemetry
log client-side, like skills and saves; armor rows wear/doff and discard),
a catalog picker to acquire items, and a rust-toned worn-armor bar beside
the vitals (amber family — armor is NOT magic) showing the live pool over
the per-suit maximum with the printed dice range on hover.

The Damage control gains an Armor toggle that routes the hit to the worn
suit via the applyDamage toArmor flag; telemetry distinguishes the layers.
All per-character control state (armor toggle included) resets on route-id
change, per the navigation-state rules.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 14 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/web/src/pages/character-sheet.tsx">

<violation number="1" location="apps/web/src/pages/character-sheet.tsx:344">
P1: A depleted worn armor suit can still keep body damage from landing when the Armor toggle remains on. The client keeps sending `toArmor: true` without checking the live armor pool, while the server floors a 0 armor pool back to 0 instead of treating future hits as body hits or refusing them.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/web/src/pages/character-sheet.tsx
Comment thread packages/backend/convex/characters.ts
Comment thread apps/web/src/components/sheet-view.tsx Outdated
Comment thread packages/rules/src/schema/items.ts Outdated
…entory writes, honest refine message

- applyDamage(toArmor) now refuses hits on a depleted suit: "it no longer
  affords protection. Any future attacks will hit the character''s body"
  (RUE p.287) — soaking at zero silently protected the body forever.
- equipArmor is a no-op when asked for the state the character is already
  in, so a repeated [wear] click can never repair the worn suit by
  resetting its pool to max.
- removeItem/equipArmor take the client''s instance snapshot (itemId + worn
  + rolledMdc) and refuse when the manifest shifted under an in-flight
  click — closes the whole stale-index bug class, not just discard.
- The M.D.C.-armor refine message no longer says "both": either field
  alone is equally illegal on a Mega-Damage shell.
@StreamDemon

Copy link
Copy Markdown
Owner Author

All four validated as real; fixed in 19105fb.

  1. Depleted armor keeps soaking (P1) — confirmed, and it was a fidelity bug, not just UX: p.287 says a depleted suit "no longer affords protection. Any future attacks will hit the character's body." The server now refuses a toArmor hit when the pool is already 0 (with the page cite in the error), so the GM explicitly routes it to the body; the client surfaces the refusal in telemetry like every other refused action. Chose refusal over silent rerouting to keep the client's stated intent and the applied effect in agreement. Covered by a new backend test (hit on 0-pool suit → /depleted/) and verified live.

  2. Re-equip refills the pool (P2) — confirmed. equipArmor now compares the requested index against the currently worn one and no-ops when nothing would change, so a repeated [wear] can never repair the suit. Pool still resets on a real change of worn suit (documented: per-suit damage memory across swaps is future scope). New test: damage to 40/70 → re-equip same suit → still 40/70.

  3. Stale-index inventory writes (P2) — confirmed, and fixed as a bug class: both removeItem and equipArmor now take the client's instance snapshot (itemId + worn + rolledMdc) and refuse with "manifest changed" when the entry at that index no longer matches. Snapshot verification rather than stored instance ids keeps the schema unchanged while making every mismatch (including a same-item different-state instance) detectable. New test exercises both mutations with stale snapshots and asserts nothing was written.

  4. Misleading refine message (P3) — confirmed; the second refine rejects either field, so "cannot declare both" was wrong. Now: "M.D.C. armor is a Mega-Damage shell — it cannot declare an A.R. or an S.D.C. pool."

243 tests green, vp check clean, refused-hit and re-wear flows re-verified in the browser.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 5 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/backend/convex/characters.ts
Unequip (index: null) now requires and verifies the client''s snapshot of
the suit it saw worn — a doff racing a swap refuses instead of blindly
unequipping whatever became worn since. Doff with nothing worn stays a
no-op (the desired state is already true, and there is nothing to verify
against). Completes the instance-verification contract across all three
index-based inventory writes.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 3 files (changes from recent commits).

Requires human review: Auto-approval blocked by 1 unresolved issue from previous reviews.

Re-trigger cubic

@StreamDemon StreamDemon merged commit 19a5c85 into main Jul 7, 2026
2 checks passed
@StreamDemon StreamDemon deleted the feat/equipment-inventory branch July 7, 2026 07:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Equipment & inventory: item schema, weapons/armor/gear, character items, sheet panel

1 participant