UX Spec: Crafting — Station Screen, Repair & Examine
Status: Reviewed — APPROVED (
/ux-reviewfinal pass 2026-08-31: 0 blocking, 0 advisory) · v0.4
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: Station (opened withInteractat a workbench: recipe list + craft + repair) · Examine (read-only look at another player's equipped gear — AC-13's third inspection context)
Requirements input: crafting Rules 1–3, 5, 7–9, 12; crafting UI Requirements (all known recipes visible, three per-cause reason strings, maker's mark everywhere, craft-beat progress with true-no-op cancel, glanceable wear, aggregation ban); EC1/EC2 commit races, EC5 disconnect at commit; harvesting no-op tone; base-building Rule 12 (stations are structures BB places;stationPresentExact/stationPresentAtLeast); art bible §7; ui-hud quick-spec Rule 4 (Crafting row) + queue item ②
Patterns used: #2 List Picker · #3 Panel/Modal · #4 Focus & Nav · #5 Tooltip/Inspect · #6 Reason String · #7 Honest No-Op Cue · #11 Craft-Beat Progress · #12 Maker's-Mark Inspect · #19 Feedback Split · #24 Interact Prompt · (new candidates: Repair action; Examine)
Related specs:design/ux/inventory.md(APPROVED — equip lives there; the rack context is its storage tooltip),design/ux/hud.md(APPROVED — E3 wear accent, E11 interact prompt)
AC-13 joint coverage: crafting AC-13's three inspection contexts are satisfied across two specs — tooltip + storage rack byinventory.md, equipped-on-another-player by this spec's Examine card; test AC-13 across both.
Purpose & Player Need
The station is where the group's hauls become the group's gear — the one screen that delivers the headline fantasy ("the smith who forged the party's iron era"). The player arrives wanting to make the thing the bag now allows, or to put a dulled tool back to spec before the next trip. It must be honest about why something can't be made (three distinct world-fact reasons — never "locked"), unhurried but not slow (a 2 s hand-tended beat, never a menu-click factory), and impossible to misread as a factory or a scoreboard: no queues, no batches, no craft counts, no "made by" grouping (crafting Rules 5, 8; CD C2). Examine exists for the other half of the fantasy — the mark is read off the item on your friend, without asking (AC-13's third context).
Player Context on Arrival
- First encounter: end of the first trip — a bag of fiber, a fresh workbench, curiosity: "what can I make?"
- Typical: (a) at home after banking, relaxed, deciding what to spend the haul on; (b) between trips, a quick repair of a worn axe — wants it to take one press; (c) at a friend's bench, elbow-to-elbow (Rule 8: no occupancy lock) — two people crafting at once. Examine: idle moments at home ("who made your hat?").
- Emotional assumption: calm — home is hostile-free at MVP; tide pressure is felt only as "is there time for one more trip", never on this screen.
- Voluntary: always; nothing sends the player here.
Navigation Position
Gameplay → Station (context-dependent: within stationRange 3 u of a workbench, Interact with the bench as the nearest target). Gameplay → Examine (context-dependent: a player within interaction range as the nearest target, Interact). One panel at a time (#3): opening the station replaces an open bag. The bag never opens inside the station — materials are shown through the affordability read, and CARRY ∪ in-range STORAGE draw is automatic (Rule 7).
Entry & Exit Points
| Entry Source | Trigger | Player carries this context |
|---|---|---|
| Gameplay near a workbench | Interact (E / West) with the bench as the nearest target (#24) | station tier; CARRY + in-range STORAGE for affordability; worn-gear list (repair candidates) |
| Gameplay near a player | Interact with the player as the nearest #24 target (players are #24 candidates; at equal distance a structure wins — a chest is the likelier intent); the prompt reads "examine" with no name (hud.md E4: names only in event copy — the card carries the name) | that player's equipped weapon / armor + marks (read-only, replicated) |
| Downed | Interact | An ordinary station panel (ruled 2026-08-31, mirroring inventory.md OQ 7): ADR-0015 suppresses only Strike / Dash / Demolish, and no crafting rule gates on Downed. Craft is available; if the player crawls, the beat cancels by Rule 7's existing movement rule — no new mechanic, no reason string. |
| Exit Destination | Trigger | Notes |
|---|---|---|
| Gameplay | Menu / UI.Cancel (Esc / Start / East) | closes; nothing debited |
| Crafting beat (world) | Craft (Primary) on an available recipe | panel closes, the stance begins (#11); commit at craftTimeSeconds; movement / dash cancels — true no-op |
| Gameplay | walking out of stationRange | panel closes (no bag half to degrade to, unlike the chest) |
| Gameplay | station demolished while open (another player) | panel closes; an in-flight beat cancels as a no-op |
| Gameplay | Examine: Menu / Cancel / target leaves range / target disconnects | card closes |
| Gameplay | tide deposit / death / host quit | force-close; nothing persists |
No one-way exits; the only persistent changes are a committed craft or repair (host-confirmed, after the panel has already closed).
Layout Specification
Information Hierarchy
- Recipe list with availability at a glance — every known recipe, filled vs hollow marker (Rule 3: known-but-uncraftable is always visible)
- The focused recipe's inputs vs what you have in reach —
have / needwhere have = CARRY ∪ STORAGE withinstationRange(Rule 7) - Repair row(s) — worn gear this station's tier (or lower) can repair (Rule 9); absent when nothing is worn
- Reason string for a focused unavailable recipe — one of exactly three causes (station / world / affordability)
- Discovered: maker's mark, tier, wear via tooltip (#5 / #12)
Deliberately absent: sort, filter, search, favourites, craft counts, "made by" grouping, queue or batch controls (Rules 5, 8; CD C2).
Layout Zones
- Z1 — Station panel (single panel; anchored right-of-centre at the same position as the bag's Z1 in
inventory.md; 640 px = 16 pad + 280 Z1a + 24 gutter + 300 Z1b + 20 pad @100 % scale): header = station name · tier ("Workbench · cloth") — a display-only read of the client's replicated structure registry for the nearest station (BB Rule 13 advisory context; may be one RTT stale; never an input to any gate). - Z1a Recipe list (left column, 280 px): pinned Repair row(s) at the top when applicable, a divider, then every known recipe in
RecipeDefdata order — available rows filled marker; unavailable rows hollow marker with the reason string (#6) beneath. No sorting control exists. - Z1b Detail pane (right, 300 px): the focused recipe's output icon (tier-shaped, art bible §7.4), name, class · tier, input rows
have / needwith ✓ / — glyphs, and the Craft Primary button labelled with the beat ("Craft · 2 s") — pointer-only (clickable on KB/M; a visual affordance on gamepad; never in the keyboard / gamepad focus graph — ruled 2026-08-31, review Q3). When unavailable it is dashed only; its reason reaches the player via the focused row's trailing slot and the Z1c footer — #1's "reason on focus" clause is satisfied by the row, which is the focus owner. The button is 200 px wide, centred in Z1b; for a Repair row: item icon + maker's mark + Repair Primary, same rule. - Z1c Footer: the focused row's reason string at full length (if unavailable); the verb hint.
- Z2 — Examine card (separate variant): a ≈ 300 px world-space card anchored above the examined player — hat glyph + name, one row per equipped slot (icon · tier · wear) with its maker's-mark line (#12). Read-only; no buttons.
- Z3 — Tooltip layer (#5) over Z1 rows and input lines.
- Background: world live; HUD never occluded (#3 layering).
All px at 100 % UI scale (pattern-library scale convention).
Component Inventory
| Zone | Component | Type | Content | Interactive | Pattern |
|---|---|---|---|---|---|
| Z1 hdr | Station header | Text | "Workbench · cloth" | No | — |
| Z1a | Repair row(s) | Pinned list rows | worn item icon + name + "(worn)"; one per repairable worn item in CARRY / equipped; absent when none | Yes | #2 + Repair action (new — defined here) |
| Z1a | Recipe rows | List Picker | ● / ○ marker, name; unavailable rows carry the reason line | Yes | #2, #6 |
| Z1b | Output card | Icon + text | icon, name, class · tier | No | #5 tooltip host |
| Z1b | Input rows | Text | fiber 12 / 20 ✓ · timber 0 / 4 — (have = CARRY ∪ STORAGE within stationRange) | No | — |
| Z1b | Craft button | Primary, pointer-only | "Craft · 2 s"; dashed + reason when unavailable; mirrors the focused row's action | Yes (mouse) | #1 |
| Z1b | Repair button | Primary, pointer-only | "Repair" (Repair rows only) | Yes (mouse) | #1 |
| Z1c | Reason / verb hint | Text | full reason string; "Enter: craft" / "Enter: repair" | No | #6 |
| Z2 | Examine card | Card | name + per-slot gear rows with marks | No | Examine card (new — defined here) + #12 |
New patterns flagged for interaction-patterns.md: Repair action (an instant, stateless station verb) and Examine card.
ASCII Wireframe
STATION
┌─ Workbench · cloth ──────────────────────────────────────────┐
│ ▸ Repair: Cloth Sword (worn) │ Cloth Garb │
│ ─────────────────────────── │ [icon] armor · cloth │
│ ● Cloth Garb* │ │
│ ● Cloth Sword │ fiber 12 / 20 ✓ │
│ ○ Cloth Garb, Reinforced │ sinew 2 / 2 ✓ │
│ not enough sinew in reach │ │
│ │ ┌───────────────┐ │
│ (MVP set = the three cloth │ │ Craft · 2 s │ │
│ recipes; a Tier 1 forge row │ └───────────────┘ │
│ would read "you need a Forge")│ pointer-only │
├─────────────────────────────────┴─────────────────────────────┤
│ Enter: craft * = focus │
└───────────────────────────────────────────────────────────────┘
have = bag + chests within 3 u · rows in recipe-data order · no sort
EXAMINE (world-space card above the player)
┌─ ◉ Mara ────────────────────────────┐
│ ⚔ Cloth Sword · cloth · fresh │
│ Jo — forged 4 tides ago │
│ 🛡 Cloth Garb · cloth · worn │
│ Mara — forged 11 tides ago │
└─────────────────────────────────────┘
Z1 shares the bag panel's anchor so the two never coexist and spatial memory carries over. Focus starts on the first Repair row if present, else the first available recipe, else the first recipe.
States & Variants
| State / Variant | Trigger | What Changes |
|---|---|---|
| Station (default) | Interact at a bench in range | Z1 with Repair rows (if any) + all known recipes |
| No repairs | nothing worn in CARRY / equipped that this tier can repair | Repair block absent (no empty header) |
| All recipes available | cloth tier, materials in reach | every row ●; footer shows the verb hint only |
| Unavailable — station | recipe needs a station of another tier | ○ + "you need a Workbench" (Rule 3 cause a — canonical string; Tier 1 instance: "you need a Forge") |
| Unavailable — world | an input's source template has never docked | ○ + "no land has offered copper yet" (cause b) |
| Unavailable — affordability | inputs short in reach | ○ + "not enough timber in reach" (cause c); the short input row shows — |
| Empty recipe list | impossible at MVP (every cloth recipe is known from session 1, Rule 2) | defined for Tier 1: empty list; footer "no recipes known yet" (knowledge is group-level and station-independent, Rule 2 — no "here") — never a lock icon |
| Craft pressed | Primary / Submit on an available recipe | panel closes; #11 stance + station fill begins; host commits at craftTimeSeconds |
| Beat cancelled | movement / dash / damage | fill vanishes — true no-op (Rule 7); no cue, no string |
| Commit race lost (EC1 / EC2) | inputs consumed by another commit / withdraw first | fill completes, no thunk, stance ends; on next open the row is ○ affordability (#7 world-family) |
| Repair pressed | Primary / Submit on a Repair row | instant, host-confirmed: row disappears, the item's wear accent clears (hotbar E3 / bag strip), one thunk; panel stays open (ruled 2026-08-31) |
| Repair race (item transferred away mid-press) | rare | row disappears without a thunk (#7) |
| Downed | opened while Downed | No change — an ordinary station panel; a crawl during the beat cancels it under Rule 7 |
| Two players at one bench | Rule 8 — no occupancy lock | independent panels; contention resolves at commit — the loser sees the race-lost state |
| Examine | Interact on a player | Z2 card; content updates live if they swap gear; closes on range / disconnect |
| Examine — nothing equipped | target unarmed / unarmoured | rows read "— · unarmed" / "— · no armor"; no mark line |
| Loading | — | none: recipes and marks are already-loaded data; affordability is a local read of replicated state |
Interaction Map
Mapping for KB/M (primary) and gamepad (partial); UI capture semantics per ADR-0007 §3.
| Component | Action | KB/M | Gamepad | Feedback | Outcome |
|---|---|---|---|---|---|
| Recipe / Repair row | Focus | hover / ↑ ↓ | UI.Navigate | 3 px outline + inset tick (#4); detail pane updates instantly; tooltip after 150 ms on the output icon (#5) | — |
| Available recipe row | Craft (the single keyboard / gamepad commit) | Enter / click on the row | UI.Submit | click sound; panel closes; stance + station fill (#11, predicted) | CraftCommit at the beat's end (host commits at craftTimeSeconds) |
| Craft button (Z1b) | Craft — pointer only | click | — (not focusable) | same as the row; the button always mirrors the focused row, and on KB/M hover == focus (#4), so the row under the pointer and the button's target are the same recipe — no misfire path | same |
| Unavailable recipe row | Submit | click / Enter | UI.Submit | nothing moves; footer reason string re-asserts (≥ 250 ms, #6) | none — no attempt is made |
| Repair row | Repair (single keyboard / gamepad commit) | Enter / click | UI.Submit | click; on host confirm: row gone, thunk, wear accent clears | repair command |
| Repair button (Z1b) | Repair — pointer only | click | — | mirrors the focused Repair row | same |
| Input row | Inspect | hover | focus-dwell (rows focusable for tooltip only) | tooltip: material name + where it is (bag / chest) | — |
| Panel | Close | Esc | East / Start | 100–150 ms fade (#3) | — |
| Any | Secondary | RMB (UI.RightClick) | — | no-op | — |
| List | Focus traversal | ↑ ↓ | UI.Navigate | list-only graph (buttons are not focusable) — full adjacency below | — |
| Examine card | none | — | — | read-only; Menu / UI.Cancel closes | — |
Focus adjacency (complete; no wrap; buttons are outside the graph):
| From | Up | Down | Left | Right |
|---|---|---|---|---|
| Repair row n | row n−1; first Repair row → stop | next Repair row; last Repair row → first recipe row if any, else stop | stop | stop |
| Recipe row n | previous row; first recipe row → last Repair row if any, else stop | next row; last row → stop | stop | stop |
Numbered order (QA traversal): Repair rows (if any) → recipe rows top to bottom. First focus = first Repair row, else first available recipe, else row 1. Exactly one focused element at all times; the Z1b button is never it. Submit on an unavailable row is a no-attempt (reason re-asserts) — there is no dead end because nothing leaves the list.
Divergence from #2 List Picker, stated: this list's Submit commits rather than selects — the panel has no selected-but-uncommitted state (focus drives the detail pane directly). Recorded in interaction-patterns.md #2 as an allowed variant.
Events Fired
| Player Action | Event | Payload | Persistent? |
|---|---|---|---|
| Craft (beat completes) | CraftCommit command (ADR-0002 CommandKind.CraftCommit) | recipeId, station instanceId, crafter PlayerKey | Yes — inputs consumed, output + InstanceMetadata row + gear instanceId counter (ADR-0004) |
| Repair | repair command — must be a new CommandKind on HostCommitChannel (it mutates host-rail InstanceMetadata.durability; ADR-0002 §1's one-channel invariant; the enum's own comment names Repair as an ADR-0010 extension). Name + payload → ADR-0010 / crafting runtime ADR | instanceTag, station instanceId | Yes — durability reset in InstanceMetadata |
| Beat cancelled | none — client-local; the host never received a commit | — | no |
| Open / close / focus / examine | none | — | no |
| Race lost | none from this client | — | the host's refusal is the event |
No analytics events exist in this project.
Transitions & Animations
- Enter: instant (#3); exit: 100–150 ms fade. Craft press: the panel fades as the stance begins (same frame) — no gap, no confirmation.
- Detail pane swap on focus: instant content swap, no slide.
- Repair: the row collapses 100 ms after the host thunk (reduced-motion: instant); the hotbar E3 accent clears the same frame.
- Craft fill: on the station silhouette (#11), never in the panel; reduced-motion: three discrete steps.
- Examine card: appears instantly, closes with the standard fade; content updates in place if the target swaps gear.
- Nothing gates input (art bible §7.5); no motion-sickness vectors.
Data Requirements
| Data | Source System | Read / Write | Notes |
|---|---|---|---|
Known recipes (RecipeDef[]) | Crafting IKnownRecipes (host-auth group set, Rule 2) + GameData Recipes | Read | All known at MVP; Tier 1 union via Save |
| Station presence | Base Building IStationPresenceProvider — bool predicates for a supplied tier: stationPresentExact(recipe.station, asker) for craft availability; stationPresentAtLeast(item.tier, asker) per worn item for repair eligibility (crafting Rule 9 "tier or higher") | Read | Never cached; the host re-samples at commit. No "tier of this station" accessor exists in BB's contract — the UI never reads one |
| Station header label ("Workbench · cloth") | Client's replicated StructureRegistry copy — nearest station's structureId → StructureDef.stationTier | Read, display-only | BB Rule 13 advisory context; may be one RTT stale; feeds no gate |
| Availability cause per recipe | Crafting F1 legality — provisional client evaluation over replicated state | Read | Provisional at open, authoritative at commit (Rule 7); a disagreement resolves as the race-lost honest no-op |
have counts | Inventory — own CARRY (owner-predicted) ∪ ISharedStorage within stationRange (host-confirmed copies) | Read | The storage side may be one RTT stale — stated, not hidden |
| Worn-gear list | Crafting IInstanceMetadata durability via instanceTag for CARRY + equipped | Read | Repair candidates = worn ∧ stationPresentAtLeast(item.tier) |
| Craft / repair | host-channel commands | Write via command only | The UI owns nothing |
| Examine target's gear + marks | Inventory equipped slots (replicated) + IInstanceMetadata | Read | Marks are permanent data (never stale-dropped) |
craftTimeSeconds, stationRange | GameData Configs.Craft / per-recipe data | Read | The Craft button label reads the recipe's own value |
cycleIndex for "N tides ago" | ITideClock (presentation clock is fine — display only) | Read | — |
Architectural flag: the provisional legality read and the authoritative commit run the same F1 predicate over different snapshots — this spec depends on the honest no-op, never on the two agreeing.
Accessibility
Tier: Standard.
- Keyboard-only:
Interactopens; ↑ ↓ the list, Enter commits, Esc closes. Gamepad:UI.Navigate/Submit/Cancelover the same list-only graph; the Z1b buttons are pointer-only and never take focus, so there are no dead ends (list ends stop). - Focus: 3 px outline + inset tick (#4); exactly one focused element; first-focus rule stated in the Interaction Map.
- Text: names Medium 16–18 px; reason strings and marks Small 13–14 px; contrast ≥ 4.5:1 on slate; ✓ / — glyphs are always paired with the numeric
have / need(never glyph-only). - Colour-independence: available vs unavailable = ● / ○ marker + reason text + dashed button, never hue; worn = "(worn)" text + shape accent; the three causes are distinguished by words, not colour.
- Reduced motion: row collapse instant; craft fill in discrete steps; no other motion exists.
- No hover-only: input tooltips reachable by focus-dwell; Examine needs no pointer.
- Scale: all px at 100 %; at 150 % the 640 px panel is 960 px — fits 1080p at the bag's anchor (AC).
- Screen reader: out of scope (documented limitation). Motor: single presses only; the 2 s beat needs no held input.
Localization Considerations
| Element | English max | Layout-critical | Priority |
|---|---|---|---|
| Reason strings — the three causes ("no land has offered copper yet") | ≤ 32 chars in the list (2 lines allowed); ≤ 48 in the footer (one line) | list wraps to 2 within the 280 px column; footer one line under 640 px | HIGH |
| Craft button ("Craft · 2 s") | ≤ 14 chars after localisation | one line in a 200 px button | HIGH |
| Recipe names | ≤ 24 chars | one line in a 280 px column | HIGH |
| Station header ("Workbench · cloth") | ≤ 24 chars | one line | Medium |
| Input rows ("fiber 12 / 20") | material ≤ 12 chars + numbers | one line | Medium |
| Maker's-mark line | ≤ 48 chars; pluralised template; ≤ 2 lines | tooltip / Examine card wraps | HIGH (plural rules) |
| "(worn)", "fresh", "unarmed", "no armor" | ≤ 10 chars | inline | Low |
HIGH rows must survive 40 % expansion within their stated line cap.
Acceptance Criteria
- [ ]
Interactwithin 3 u of a workbench opens the station panel within 1 frame at the same screen position as the bag panel; Esc / East closes it. - [ ] Every known recipe is listed; with fiber 12 / 20 in reach the Cloth Garb row is ○ with "not enough fiber in reach" and its Craft button is dashed; adding 8 fiber to a chest within 3 u flips the row to ● without reopening.
- [ ] Three unavailable recipes with different causes (test config) show three different strings (station / world / affordability); no string contains "locked" or addresses the player in the second person.
- [ ] With the pointer over recipe B and keyboard focus previously on recipe A, pressing Enter crafts exactly one recipe — the one under the focus outline (which on KB/M is B, since hover moved focus); clicking the Craft button crafts the focused row.
- [ ] Pressing Craft closes the panel the same frame the crafting stance starts; walking during the beat ends the stance with no material change and no message; standing still for 2 s produces the item, one thunk, and a bag flash.
- [ ] Two clients craft the last-materials recipe within one tick: exactly one item exists afterwards; the loser's stance ends silently and their reopened panel shows the affordability string.
- [ ] A worn axe shows a pinned "Repair: Cloth Sword (worn)" row; pressing it removes the row, clears the hotbar wear accent within one frame of host confirm, and leaves the panel open; with nothing worn, no Repair block renders.
- [ ] A cloth-tier item is repairable at a cloth or higher station; a higher-tier item shows no Repair row at a cloth station (the filter calls
stationPresentAtLeast(item.tier)per item — verified by a fake provider in EditMode). - [ ]
Interacton a nearby player opens a card showing their name, each equipped item's tier and wear, and a maker's line "Name — forged N tides ago"; it closes when they walk out of range. - [ ] Gamepad-only: every row is reachable with a visible 3 px focus outline; the Craft / Repair buttons never take focus; ↑ at the top and ↓ at the bottom do nothing; ← / → do nothing;
Submiton a ○ row does nothing but re-assert its reason. - [ ] No sort, filter, search, count, or "made by" control exists on the panel (reflection + screenshot); list order equals
RecipeDefdata order. - [ ] At 150 % UI scale nothing clips; all HIGH strings fit their line caps at +40 % length.
Open Questions
- Player journey map not yet created (
.claude/docs/templates/player-journey.md) — arrival contexts assumed; validate at the playtest. - ~~Downed at a station~~ Ruled 2026-08-31: an ordinary station panel — if a designer ever wants Downed to gate crafting, that is a combat / crafting GDD rule via
/quick-design, not a UI decision (same ruling asinventory.mdOQ 7). - Repair and Examine → lift into
interaction-patterns.mdas #29 Repair Action and #30 Examine Card (#25–28 are reserved). havefor storage is one RTT stale by construction — if the playtest shows confusing flips, prefix storage-sourced counts with "~". Playtest item.- Tier 1: recipe-list length, and whether a station implies a filter (does a Forge list only forge recipes?) — currently every known recipe lists everywhere with the station cause; revisit when a second station type exists.
- Repair
CommandKindname + payload — a required new kind (Events table); belongs to ADR-0010 / the crafting runtime ADR (architecture review's proposed Required-ADR row 16). - Examine vs Revive at a Downed ally: both ride
Interact(E / West —Reviveis the same control read asHeldFor, ADR-0007). Proposed: at a Downed ally the revive channel (#10) owns the control and Examine is unavailable until they are up (a downed friend's gear is not the question). Needs combat's assent; logged as a #24 / #30 gap in the pattern library. - Examine competes with chests / stations for #24's single nearest-target slot — step closer to the player to examine. Stated, not a bug; watch at the playtest.