UX Spec: Inventory — Bag & Shared Storage
Status: Reviewed — APPROVED (
/ux-reviewre-pass 2026-08-31: 0 blocking, 4 advisory — applied) · v0.3
Author: Jeroen + ux-designer
Last Updated: 2026-08-31
Journey Phase(s): unknown — no player journey map yet (see Open Questions)
Template: UX Spec
Variants: Bag (opened withInventoryanywhere) · Bag + Storage (opened withInteractat a storage chest)
Requirements input: inventory quick-spec Rules 1–7 + Tuning Knobs; harvesting UI Requirements (full-bag cue, no-op cue family OQ6); crafting Rule 12 / AC-13 / AC-33 (equip swap-in-place, maker's mark in tooltip + rack, aggregation ban); base-building Rule 12 (storage instantiated per chest); art bible §7.1–7.5; ui-hud quick-spec Rule 4 (Inventory row) + queue item ①
Patterns used: #3 Panel/Modal · #4 Focus & Nav · #5 Tooltip/Inspect · #6 Reason String · #7 Honest No-Op Cue · #12 Maker's-Mark Inspect · #13 Storage Who-Took-What · #17 Carry Meter · #19 Feedback Split · #21 Stack Grid (new — defined here)
Purpose & Player Need
The bag is where the loop's tension becomes a decision. Every harvest swing, every demolish refund, every craft lands here, and the tide makes the bag's fill level the one question that matters on a docked land: bank or push? The player arrives at this screen wanting to see what I'm carrying and decide what to do with it — check the fill, equip the thing Mara just made, empty the haul into the chest so the next trip starts clean. If this screen is slow, fiddly, or lies about what's in the chest, the co-op loop breaks in the most annoying way possible: two friends hauling the same timber, or a full bag discovered by a failed swing instead of a glance. The quick-spec names this the "chronically underestimated" risk; the ui-hud queue puts it first, before the first friend playtest.
What it is not: a management game. No weight, no sorting, no filtering, no favourites, no "made by" grouping (aggregation ban). Ten slots, two equipment slots, one chest at a time.
Player Context on Arrival
- First encounter: minutes into the first session, after the first harvest swing lands a stack in the bag and the carry meter ticks — curiosity, "what did I get?"
- Typical encounters: (a) mid-trip on a docked land, glancing at the bag to judge remaining room — calm-to-time-pressured, the violet is rising; (b) at home at a chest, dumping a haul — relieved, sorting out what to keep; (c) at home after a craft, equipping new gear — pleased, wants the swap to be instant.
- Voluntary: always. Nothing sends the player here; the bag-full no-op nudges via the HUD carry meter (#17), never by opening the panel.
- Emotional assumption: the design assumes the player may be under tide pressure in variant Bag (open/close must cost nothing) and relaxed in variant Bag + Storage (home is hostile-free at MVP, BB Interactions).
Navigation Position
This screen lives at: Gameplay → Bag (top-level, always reachable while alive) and Gameplay → Chest → Bag + Storage (context-dependent: within interaction range of a storage structure at home). Never reached from pause or the main menu; no inventory tab in Settings. Only one panel is open at a time (pattern #3): opening the bag while another panel is open replaces it via the panel stack.
Entry & Exit Points
| Entry Source | Trigger | Player carries this context |
|---|---|---|
| Gameplay (anywhere, alive) | Inventory press (Tab / North) | Own CARRY (owner-predicted), equipped weapon/armor, current carry-meter fill |
| Gameplay at home, in range of a storage chest | Interact press (E / West) with the chest as the nearest interactable | Own CARRY + that chest's host-confirmed contents + its live who-took-what feed (#13) |
| Gameplay (Downed) | Inventory press | An ordinary bag — Downed does not affect the bag (quick-spec Rule 8) and ADR-0015 suppresses only Strike/Dash/Demolish, so every verb here stays legal (ruled 2026-08-31; the earlier read-only variant invented a mechanic no GDD owns) |
| Exit Destination | Trigger | Notes |
|---|---|---|
| Gameplay | Inventory press — closes the whole panel in both variants (chest half included; ADR-0007 §3 grants the bag its opening-key close) | No state change on close; transfers already committed or already failed |
| Gameplay | Menu (Esc / Start) — closes via the panel stack, does not open pause | Same |
| Gameplay | UI.Cancel (East) | Same |
| Gameplay (Bag variant) | Walking out of chest interaction range (Bag + Storage only) | Degrade, not close: the storage half closes; the bag half stays open as the Bag variant |
| Gameplay | Tide soft-close deposit / death respawn / host disconnect | Panel force-closes; CARRY untouched (tide AC-23, combat AC-28) |
| — | Gamepad disconnect while open | Panel stays open; focus stays where it is; prompt glyphs switch to KB/M (ADR-0007 §3) |
No exit is one-way; no exit changes persistent state by itself (state changes are per-transfer host commits that already resolved).
Layout Specification
Information Hierarchy
- The bag grid — what I'm carrying, at a glance (the reason the panel exists)
- Equipment slots — weapon + armor, visually distinct from bag slots (not bag space — quick-spec Rule 4)
- Chest grid (Bag + Storage variant) — host-confirmed contents + the who-took-what feed (#13)
- Capacity read —
n/Nslots (mirrors the HUD carry meter #17) - Discovered, not shown: per-item detail — maker's mark, wear, count vs
maxStack(tooltip #5/#12) - Contextual only: reason strings on a blocked action (#6)
Deliberately absent: sort buttons, filters, search, weight, categories, favourites, any per-crafter view — the ban list (quick-spec Rule 3/Pillar 4; aggregation ban).
Layout Zones
Arrangement: fixed-bag (ruled 2026-08-30) — the bag never moves; spatial memory holds across variants.
- Z1 — Bag panel (both variants; anchored right-of-centre, identical position always): bag grid 5×2 (
bagSlots10), equipment strip below it (Weapon · Armor — heavier outline, visually offset from the grid), capacity readn/Nin the panel footer. Width ≈ 380 px @1080p/100 % scale. - Z2 — Chest panel (Bag + Storage only; slides in left of Z1, 24 px gutter): chest grid 8×5 (
storageSlots40), header "Chest" (no chest naming at MVP), who-took-what tags (#13) inline on the affected stack. Width ≈ 560 px. - Z3 — Tooltip layer (#5): anchored to the focused/hovered stack, above both panels.
- Z4 — Reason-string slot (#6): one line in the footer of whichever panel refused the action.
- Background: gameplay stays visible and live around the panels (the world never pauses); the closed-list HUD (party HP, Downed indicator, tide glint, carry meter) is never occluded (#3 layering rule).
All px values at 100 % UI scale; multiply with the accessibility scale option (pattern-library scale convention).
Component Inventory
| Zone | Component | Type | Content | Interactive | Pattern |
|---|---|---|---|---|---|
| Z1 | Bag grid | Stack grid 5×2 | Own CARRY stacks: icon + count (count hidden when 1) | Yes | #21 Stack Grid (new — defined by this spec's Interaction Map) |
| Z1 | Equipment strip | 2 special slots | Equipped weapon / armor icons; wear read: the glanceable indicator is the HUD hotbar's frayed-corner accent (hud.md E3, 2026-08-31); the equipment strip here mirrors that same accent on its two slots, and the tooltip (#12) carries the worded state | Yes | #21 variant; swap per crafting Rule 12 / AC-33 |
| Z1 | Capacity read | Text | 7/10 used slots | No | mirrors #17 |
| Z2 | Chest grid | Stack grid 8×5 | Chest stacks — host-confirmed only, never predicted | Yes | #21 + #13 tags |
| Z2 | Chest header | Text | "Chest" | No | — |
| Z3 | Tooltip | Tooltip | name · tier · maker's mark · wear · count/maxStack | No | #5 + #12 |
| Z4 | Reason string | Text | "chest is full" / "bag is full" / "bag is full — deposit something first" (Small 13–14 px). Lifetime: persists until the next focus move or action, minimum 250 ms (#6) — a discrete-event string, not auto-dismissing | No | #6 |
New pattern flagged: #21 Stack Grid — interaction rules defined here, to be lifted into interaction-patterns.md after this spec is approved (its reserved number).
ASCII Wireframe
Bag + Storage variant (Bag variant = Z1 alone, same position):
gameplay visible & live ······································ HUD stays visible
┌─ Chest ────────────────────────────────┐ ┌─ Bag ──────────────────┐
│ ┌────┬────┬────┬────┬────┬────┬────┬────┐ │ │ ┌────┬────┬────┬────┬────┐ │
│ │timb│timb│fibr│ │sinw│ │ │ │ │ │ │timb│fibr│fibr│ ⚔︎* │ │ │
│ │ 20 │ 12 │ 20 │ │ 4 │ │ │ │ │ │ │ 7 │ 20 │ 3 │ │ │ │
│ ├────┼────┼────┼────┼────┼────┼────┼────┤ │ │ ├────┼────┼────┼────┼────┤ │
│ │ −12 · Alex (2 s tag, #13) │ │ │ │ │ │ │ │ │ │
│ ├────┼────┼────┼────┼────┼────┼────┼────┤ │ │ └────┴────┴────┴────┴────┘ │
│ │ …4 more rows (row 2 shown above; │ │ │ ⚔ Weapon 🛡 Armor │
│ │ 8×5 = 40 slots total) │ │ │ ┌──────┐ ┌──────┐ │
│ └────┴────┴────┴────┴────┴────┴────┴────┘ │ │ │ axe │ │cloth │ │
│ │ │ └──────┘ └──────┘ │
│ │ │ Bag 7/10 │
└────────────────────────────────────────┘ └────────────────────────────┘
[reason-string line appears under the refusing panel] * = the ONE focused element
focused slot: 3 px outline + inset tick (─▸); tooltip anchors to it
Z1 is pixel-identical between variants; Z2 slides in/out left of it. Focus starts on the bag's first occupied slot.
States & Variants
| State / Variant | Trigger | What Changes |
|---|---|---|
| Bag (default) | Inventory | Z1 only; Submit on gear = equip / swap |
| Bag + Storage | Interact at a chest | Z2 slides in; Submit = transfer in both grids; who-took-what tags live (#13) |
| Empty bag | New character / fully banked | 10 empty slots; capacity 0/10; no placeholder text (an empty grid is self-explanatory; first-run tutorialisation is deliberately absent) |
| Empty chest | Fresh chest | 40 empty slots; header unchanged |
| Full target | Transfer refused by CanAdd | Nothing moves (all-or-nothing, quick-spec Rule 6); reason string "bag is full" / "chest is full" in the refusing panel's footer (#6); no sound beyond the ordinary click |
| Withdraw pending | Submit on a chest stack, host verdict not yet in (~1 RTT) | The source cell keeps its stack; further input on that stack is ignored until the outcome lands (no cue — one press = one transaction, never a queued pair); focus stays on the cell; other cells remain live |
| Race loss | Stack vanished before your click resolved | Stack disappears when the host update lands; who-took-what tag shows who; your bag unchanged (#7 world-family — withdraws are never predicted) |
| Unequip unavailable | Bag full and an equipment slot focused | The equipment slot is not actionable (verb hint blank); footer shows "bag is full — deposit something first" (#6). Never a refused action — nothing is attempted |
| Downed | Opened, or player becomes Downed while open | No change — an ordinary bag (quick-spec Rule 8); the panel does not force-close |
| Degrade | Walked out of chest range | Z2 slides out; Z1 stays; focus jumps to the bag grid if it was in the chest |
| Chest demolished while open | Another player demolishes it (empty-first rule — it was empty) | Same as Degrade |
| Loading | — | None: CARRY is local; chest contents are already-replicated state — the panel never waits on the network to open |
Interaction Map
Mapping for KB/M (primary) and gamepad (partial, twin-stick) per technical-preferences; UI capture semantics per ADR-0007 §3. One verb per container, context-labelled (provisional, playtest item): Submit on a bag stack = equip (gear) when no chest is open, deposit when one is; Submit on a chest stack = withdraw; the footer hint names the active verb for the focused cell. Escape hatch stated: to equip while banking, step out of chest range (Degrade) — no second verb exists at MVP (OQ 2).
| Component | Action | KB/M | Gamepad | Feedback | Outcome |
|---|---|---|---|---|---|
| Any stack | Focus | hover / arrows | UI.Navigate | 3 px outline + inset tick (#4); tooltip after 150 ms (#5) | — |
| Bag stack (material) | Transfer → chest | click / Enter | UI.Submit (South) | Predicted removal from bag; appears in chest on host confirm; click sound | InventoryTransfer(bag→chest, stackRef) |
| Chest stack | Transfer → bag | click / Enter | UI.Submit | Not predicted: stays until host confirms, then moves; bag flash (#17) | InventoryTransfer(chest→bag, stackRef) |
| Bag gear (no chest open) | Equip / swap | click / Enter | UI.Submit | Two cases (ruled 2026-08-31): target equipment slot empty → the bag cell simply empties; target occupied → swap: the outgoing item lands in the exact bag slot the incoming item vacated (crafting AC-33 — the one non-fungible placement in the grid) | equip command |
| Bag gear (chest open) | Deposit | click / Enter | UI.Submit | As material transfer; footer hint reads "deposit" | InventoryTransfer |
| Equipment slot | Unequip → bag | click / Enter | UI.Submit | Item moves to the first free bag slot (empty-handed removal has no vacated slot to reuse). With a full bag the verb is not offered (see States) — never a refused attempt, so "equipment never requires bag slots" (quick-spec Rule 4) and crafting Rule 12 "free and placeless" hold: swap is always available; only empty-handed removal waits for room | unequip command |
| Panel | Close | Tab / Esc | North / East / Start | 100–150 ms fade (#3) | — |
| Any cell | Secondary | RMB (UI.RightClick) | — | No-op at MVP (reserved by OQ 2 for a possible equip verb); never a context menu | — |
| Grid ↔ grid / strip | Focus traversal | arrows | UI.Navigate | Complete adjacency set below | — |
Focus adjacency (complete — every edge defined; no wrap anywhere):
| From | Left | Right | Up | Down |
|---|---|---|---|---|
| Bag cell (5×2) | previous column; from column 1 with a chest open → chest column 8, same row; without a chest → stop | next column; from column 5 → stop | row 1 → stop; row 2 → row 1 | row 1 → row 2; row 2 → equipment strip (columns 1–2 → Weapon, 3–5 → Armor) |
| Weapon slot | stop (with a chest open → chest column 8, row 5) | Armor | bag row 2, column 1 | stop |
| Armor slot | Weapon | stop | bag row 2, column 5 | stop |
| Chest cell (8×5) | previous column; column 1 → stop | next column; column 8 → bag column 1, same row (rows 1–2) / bag row 2 (rows 3–5) | previous row; row 1 → stop | next row; row 5 → stop |
Numbered order (the QA traversal sequence, not the entry point): Bag variant — bag 1…10 row-major, then Weapon (11), Armor (12). Bag + Storage — chest 1…40 row-major, then bag 1…10, then Weapon, Armor (matches #13). First focus is always the bag: first occupied bag slot (slot 1 if empty). Empty cells are focusable. Exactly one element is focused at all times.
Gutter asymmetry — intentional (ruled 2026-08-31): the bag has 2 rows, the chest 5, so crossing clamps: chest rows 3–5 → bag row 2, and Left from bag row 2 returns to chest row 2, not the row departed; Weapon.Left → chest row 5, whose Right lands on bag row 2. Nothing is trapped; it may read as odd in play — OQ 8 (a sticky last-chest-row is the fallback).
No drag, no split, no multi-select, no modifier-click at MVP (ruled 2026-08-30 — one press = one whole stack = one host transaction, transferBatchWindow 0). Focus starts on the bag's first occupied slot (slot 1 if empty).
Stack Grid (#21) rules distilled for the pattern library: fungible slots (a stack fills the first free slot) except the AC-33 swap, which reuses the vacated slot; the adjacency table above with no wrap; Submit = the container's single verb; icon + count (count hidden at 1); tagged stacks (gear) never merge; empty slot = outlined cell; no drag/split at MVP; input on a stack with a pending host verdict is ignored.
Events Fired
| Player Action | Event | Payload | Persistent? |
|---|---|---|---|
| Transfer either direction | InventoryTransfer command on the host channel (ADR-0002 CommandKind.InventoryTransfer) | direction, stackRef (itemId, count, instanceTag?), containerId | Yes — CARRY / STORAGE tables dirty on commit (ADR-0004) |
| Equip / unequip | equip command — owner-rail CARRY mutation + equipped-slot change (exact command shape → crafting runtime ADR / ADR-0010) | instanceTag, slot | Yes — equipped instances persist in carries (ADR-0004 §1) |
| Open / close panel, focus moves, tooltip | none | — | Deliberate: pure presentation |
| Refused transfer / race loss | none from this client | — | The host's refusal is the event; silence is the honest no-op |
No analytics events exist in this project (no telemetry at MVP).
Transitions & Animations
- Enter: instant (≤ 1 frame, #3) — the world keeps moving behind it. Exit: 100–150 ms ease-out fade; input released on the close edge.
- Z2 slide (chest open / degrade): 120 ms ease-out horizontal; reduced-motion: appears / disappears in place.
- Stack move: no travel animation — the stack leaves one grid and appears in the other on confirm (a travel tween would imply a world event that isn't); the bag flash (#17) marks arrival.
- Equip swap: instant icon exchange + one 150 ms outline brighten on both slots; reduced-motion: no brighten.
- Who-took-what tag: per #13 (2 s, fade over the last 200 ms; reduced-motion: static 2 s).
- Nothing here gates input (art bible §7.5). Motion-sickness risk: none (no parallax, no scaling).
Data Requirements
| Data | Source System | Read / Write | Notes |
|---|---|---|---|
Own CARRY stacks (ItemStack[], ≤ bagSlots) | Inventory — ICarryInventory (owner rail) | Read; write via commands only | Owner-predicted for own mutations; the UI never edits stacks directly |
Equipped weapon / armor (instanceTag) | Inventory equipment slots (Combat owns hook signatures; Crafting owns definitions) | Read; write via equip command | Swap-in-place is the command's rule, not the UI's |
Chest contents (ItemStack[], ≤ storageSlots) | Inventory — ISharedStorage (host rail), replicated | Read only, host-confirmed | Representation → ADR-0010; the UI never predicts it |
| Who-took-what deltas | Inventory — confirmed InventoryTransfer outcomes broadcast from the drain | Read, event-driven | Transient; never stored (no-contribution-comparison rule) |
Item icon, name, tier, maxStack | GameData IGameData.Items (ADR-0006) | Read | Static after boot |
| Maker's mark (crafter, forged cycle, tier), wear state | Crafting IInstanceMetadata via instanceTag; current cycleIndex from ITideClock for "N tides ago" | Read | Per-item only (#12) |
| Interaction-range chest id | Base Building / interact system | Read | Drives the Bag ↔ Bag + Storage variant and the Degrade exit |
bagSlots, storageSlots, transferBatchWindow | GameData Configs.Inventory | Read | Grid dimensions derive from these — never a hardcoded 5×2 / 8×5 |
Architectural flag: the UI owns nothing. Every write is an ICommand on the host channel (ADR-0002) or an owner-rail equip; "CARRY predicted, STORAGE confirmed, withdraws not predicted" is the split ADR-0010 must implement.
Accessibility
Tier: Standard (design/accessibility-requirements.md).
- Keyboard-only path: Tab opens; arrows traverse bag grid → equipment strip → (chest grid when open) in the declared order; Enter acts; Esc / Tab closes. Every element reachable without a mouse.
- Gamepad order: the numbered order + adjacency table in the Interaction Map (no wrap; no focus trap — every edge defined); first focus = first occupied bag slot. Visible focus = 3 px outline + inset tick (#4). Gamepad disconnect: focus stays, glyphs switch.
- Contrast (per element): counts / header / footer text
#E8E6DEon slate#3A3D42≥ 4.5:1; cell outlines ink on slate ≥ 3:1 (non-text); focus outline slate-light on slate ≥ 3:1 plus the weight/tick channel — all verified before theme lock. - Text: counts Medium 16–18 px; tooltip Small 13–14 px floor; contrast ≥ 4.5:1 on slate (verified before theme lock). All values scale with the 100 / 125 / 150 % option.
- Colour-independent: empty vs occupied = outline + icon; gear vs material = shape (tier hardening) + tooltip; fresh vs worn = shape accent (frayed edge), never hue; who-took-what = text + sign; refused = text.
- Reduced motion: slide → in place; brighten / flash off; tags static.
- No hover-only: tooltips also on focus-dwell; the footer verb hint is text.
- Screen reader: out of scope (documented limitation).
- Motor: one press per action; no holds, no drag, no timing windows.
Localization Considerations
| Element | English max | Layout-critical | Priority |
|---|---|---|---|
| Footer reason strings ("chest is full", "bag is full — deposit something first") | ≤ 32 chars at Small 13–14 px (≈ 7 px/char → 45 chars expanded ≈ 315 px < 380 px) | Yes — one line under the 380 px panel | HIGH |
| Footer verb hint ("Enter: deposit") | ≤ 20 chars | Yes | HIGH |
| Chest header ("Chest") | ≤ 12 chars | No | Low |
| Maker's mark line ("Mara — forged 4 tides ago — cloth") | ≤ 48 chars; pluralised template | Exempt from the no-wrap rule — tooltip max width 320 px @100 %, ≤ 2 lines | HIGH (plural rules) |
| Who-took-what tag ("− 12 · Alex") | name ≤ 16 chars + delta | Inline on a 64 px slot — truncate the name with an ellipsis | Medium |
Counts 7/10, 20 | numeric | Locale digits only; no separators needed (max 80) | Low |
Any element marked HIGH must survive 40 % text expansion without wrapping to a second line, except where a line cap is stated instead. Layout at 150 % UI scale: (380 + 24 + 560) × 1.5 = 1446 px ≤ 1920 — fits 1080p (AC 11).
Acceptance Criteria
- [ ]
Inventorypress opens the bag panel within 1 frame (≤ 16.7 ms) with the world still moving behind it; a second press closes it. - [ ] With ten distinct stacks the bag grid shows exactly 10 occupied cells and
10/10; with none, 10 outlined empty cells and0/10; no placeholder text. - [ ] At a chest,
Interactopens Bag + Storage with the bag panel in the same screen position as the Bag variant (pixel-identical Z1). - [ ] Clicking /
Submiton a bag material stack with a chest open moves the whole stack to the chest; the bag cell empties immediately and the chest cell appears when the host confirms. - [ ] Clicking a chest stack when the bag is full moves nothing and shows "bag is full" under the bag panel; no dialog, no error sound.
- [ ] With a full bag, focusing an equipment slot shows no verb hint and "bag is full — deposit something first";
Submitdoes nothing; after depositing one stack the unequip verb returns. - [ ] A second
Submiton a chest stack before its withdraw confirms sends no second transfer (host log shows oneInventoryTransfer). - [ ] Gamepad: from the Armor slot, Up reaches bag row 2 col 5, Left reaches Weapon; from Weapon with a chest open, Left reaches chest col 8 row 5 — no focus position is a dead end.
- [ ] Two clients click the same last chest stack: exactly one bag gains it; on the other client the chest cell disappears and shows a "− N · [name]" tag for 2 s; the loser's bag never showed the item.
- [ ]
Submiton a gear stack with no chest open equips it instantly and the previously equipped item lands in the exact slot vacated (crafting AC-33); bag count unchanged. - [ ] Walking out of chest range slides the chest panel out and leaves the bag panel open in place.
- [ ] Gamepad-only: every slot, both equipment slots, and close are reachable via
UI.Navigate/UI.Submit/UI.Cancelwith a visible 3 px focus outline; no action requires a pointer. - [ ] Tooltip on any gear item shows crafter name, "forged N tides ago", and tier; no grid offers any sort, filter, or "made by" control (reflection + manual check).
- [ ] At 150 % UI scale on 1080p no text clips and both panels fit without overlap.
Open Questions
- Player journey map not yet created (
.claude/docs/templates/player-journey.md) — this spec assumed arrival contexts (a)–(c); validate at the playtest. - Context-labelled
Submit(equip when no chest / deposit when a chest is open) — playtest: does anyone deposit gear they meant to equip? Fallback: RightClick / a second gamepad verb for equip. - Stack splitting — none at MVP; a Tier 1 question once more materials and recipes make partial withdraws useful.
- ~~Unequip destination~~ Ruled 2026-08-31: swap → exact vacated slot (AC-33); empty-handed unequip → first free slot; with a full bag the unequip verb is not offered (no refusal). Watch at the playtest whether "deposit something first" reads clearly.
- Chest naming / multiple chests — header is "Chest" at MVP; a name field would be text entry, and no input field exists anywhere (pattern-library note).
- Who-took-what name truncation at 16 chars — check against the friends' actual display names.
- ~~Downed read-only~~ Ruled 2026-08-31: dropped — Downed is an ordinary bag (quick-spec Rule 8; ADR-0015 suppresses only Strike/Dash/Demolish). If a designer ever wants Downed to gate inventory verbs, that is a combat-GDD rule via
/quick-design, not a UI decision. - Gutter-crossing asymmetry (chest rows 3–5 ↔ bag row 2; Weapon ↔ chest row 5) — intentional clamp; if the playtest reads it as a bug, add a sticky "last chest row" so the return edge restores it.