DRIFTLANDS co-op survival on drifting tides — studio progress

UX Spec: Build Mode — Picker, Ghost, Placement & Demolish

Status: Reviewed — APPROVED (/ux-review re-pass 2026-08-31: 0 blocking, 3 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: Build Mode (client-local mode: picker + ghost + reach grid; Player map stays live) · Demolish (an instant targeted action from Idle/Move — never a mode)
Requirements input: base-building Rules 3–9, 11a/11b, 12, 13; the BB States & Transitions table; BB UI Requirements (picker, ghost + per-gate reason strings with precedence + hysteresis, grid within reach only, demolish prompt + refund preview, no build log); BB Edge Cases 11 (race no-op) and 21 (displaced-player one-cell correction); the two /ux-design-owned deliverables — repeat-placement cadence (Rule 11b) and snap-nudge tolerance (OQ6 residual); ADR-0007 §1 Build map + rules (a)/(f)/(g) + §3 BuildModeRouting; art bible §7; ui-hud quick-spec Rule 4 (BB row) + queue item ③
Patterns used: #2 List Picker · #6 Reason String · #7 Honest No-Op Cue · #8 Ghost Outline & Reach Grid · #9 Demolish Target Select · #19 Feedback Split · #24 Interact Prompt (suppressed in mode) · #31 Repeat Placement (lifted from this spec)
Related specs: design/ux/hud.md (APPROVED — HUD fully visible in build mode; E11 suppressed), design/ux/inventory.md (storage placement instantiates a chest), design/ux/crafting.md (stationRange draw radius shared)


Purpose & Player Need

Build mode is where the group's home stops being a campsite. The player enters it wanting to put a wall here — and, most sessions, put fifty walls along this line — with placement feeling deliberate (snap-with-nudge, a thunk per wall) but never fiddly (persistent mode, hold-to-repeat, no confirmation dialogs, no timers). It must be honest about refusals (one world-fact reason per ghost, first failing gate, held stable), and it must resist becoming a build tool: no rotation, no move verb, no blueprints, no build log, no per-player credit (Rules 7, 9, 11a). Demolish is the same design in reverse — instant, total refund, "empty the chest first" — a repair verb for collective authorship, never a punishment.


Player Context on Arrival


Navigation Position

Gameplay → Build Mode — a client-local mode, not a panel: no UI capture; the Player map stays live for movement and camera, the Build map is enabled alongside (ADR-0007 §3 BuildModeRouting). Top-level from gameplay via BuildToggle (B / D-pad Up), anywhere; entering on a docked land is legal — no GDD rule gates mode entry by location; opening the mode off-island is this spec's ruling (the mode is client-local and costs nothing) — and every cell fails withinBounds (BB AC-4), so the ghost is dashed everywhere with the bounds gate's off-island string "not home ground". Demolish is not in the navigation tree — an instant verb from Idle/Move (Demolish: RMB outside build mode / D-pad Down). Build mode and any panel are mutually exclusive: opening a panel exits the mode (the Inventory edge exits first, then opens the bag); a panel cannot open inside it.


Entry & Exit Points

Entry Source Trigger Player carries this context
Gameplay (Idle/Move, alive, not captured)BuildToggle press (B / D-pad Up)last-selected structure (session-sticky; default: wall); CARRY + in-range STORAGE for affordability; aim / pointer for the ghost
Gameplay (Idle/Move)Demolish press (RMB outside build mode / D-pad Down) — not a modenearest structure per #9's selector; refund preview
Exit Destination Trigger Notes
Idle/MoveBuildToggle again, or Build.Cancel (RMB / Esc on KB/M; East on gamepad)ghost vanishes; nothing committed
Idle/Move + the action appliedany non-Build Player action — Strike (RT on gamepad; LMB is Confirm on KB/M), Dash, Interact, Revive, Demolish (D-pad Down; RMB is Cancel on KB/M), Inventoryexits first, then the action applies in the same frame (ADR-0007 BuildModeRouting)
PauseMenu (Esc / Start)exits build mode then opens pause (ADR-0007 rule (g)) — the player never resumes into a live ghost
Idle/Movebecomes Downed / Deadmode drops; ghost vanishes; nothing committed
Idle/Movetide deposit / host quitsame
—Move, camera, aimnever exit (Rule 5)

Persistence: the mode survives every commit (multi-wall sessions), every refused commit (ghost stays, reason shows), and every race loss (EC11). No one-way exits; every commit is its own host transaction that has already resolved by the time anything changes on screen.


Layout Specification

Information Hierarchy

Deliberately absent: rotation control (Rule 7), move verb (Rule 9), blueprints / queues (Rule 11a), build log or per-player credit (Rule 7 / UI ban), any placement timer (Rule 6).

Layout Zones

All px at 100 % UI scale.

Component Inventory

Zone Component Type Content Interactive Pattern
W1Ghostworld outlinefootprint; solid / dashedfollows input#8
W2Placement reasonworld textfirst failing gate ("someone is standing there")No#6
W3Reach gridworld overlaycells within 6 uNo#8
W4Demolish highlight + refund previewworld outline + text"+4 timber" / "empty the chest first"No#9, #6
S1Picker entries ×3horizontal pickericon · name · cost ✓ / —Yes (PickPrev / PickNext, click)#2 variant (horizontal, in-mode — not a captured panel)
S2Affordability reasontext"not enough fiber in reach"No#6
—Hold-to-repeatgestureConfirm held → one atomic commit per cell along the last ghost-move directionYes#31 Repeat Placement (Rule 11b's deliverable; lifted 2026-08-31)

ASCII Wireframe

BUILD MODE (home, wall selected, ghost invalid)
┌──────────────────────────────────────────────────────────────┐
│ ◉ Jo ████░                                                    │
│                                                               │
│                 someone is standing there   ← W2 (16 px above)│
│                 ┆‾‾‾‾┆  ← W1 dashed ghost (1×1 wall)          │
│        ▪▪▪▪▪▪▪▪ └┄┄┄┄┘ ▪▪▪▪                                   │
│        · · · · · · · · · · · ·  ← W3 grid, 6 u radius         │
│        · · · ·  ☺  · · · · · ·     (fades at the edge)        │
│        · · · · · · · · · · · ·                                │
│                                                               │
│      ┏━━━━━━━━━━━━━━┓┌──────────────┐┌──────────────┐  ← S1  │
│      ┃▣ Wall  4 timb✓┃│▤ Bench 8t 4f—││▢ Chest  6t ✓ │        │
│      ┗━━━━━━━━━━━━━━┛└──────────────┘└──────────────┘        │
│ ♥♥♥♥♡ ◔ [⚔][🛡]        (S2 empty — wall affordable)  🎒 7/10 │
└──────────────────────────────────────────────────────────────┘
Q / D-pad ← : previous · scroll / D-pad → : next · LMB / South: place (hold to repeat) · RMB / Esc / East: exit

DEMOLISH (Idle/Move, RMB / D-pad Down targeting a wall)
        ┌ +4 timber ┐   ← W4 refund preview
        ┃ ▣ wall    ┃   ← solid highlight outline
        └───────────┘
   (a non-empty chest instead shows: "empty the chest first")

Costs shown are illustrative; live values come from StructureDef (wallCost 4 timber at MVP).


States & Variants

State / Variant Trigger What Changes
Build mode, validghost passes all 8 F1 gatessolid ghost; W2 empty; S2 empty; selected entry ✓
Build mode, invalid — one row per F1 gate, in orderfirst failing gatedashed ghost + W2: reach "too far away" · bounds "past the island's edge" (off-island: "not home ground") · overlap "blocked" · occupancy "someone is standing there" · exit "no way out" · terrain "not buildable here" · socket "too close to the dock" · materials "not enough timber in reach". Six are BB's UI Requirements strings verbatim; the bounds and terrain strings are new instances authored here under the GDD's /ux-design retitling right (BB EC23 / CS-17) — owed back to BB's list (OQ5). Precedence = F1 order
Unaffordable entry selected— on the selected entryS2 shows "not enough fiber in reach" only while W2 is empty; if the ghost's cell fails any gate (incl. materials), W2 shows and S2 is suppressed
Off home groundmode entered on a docked land / at seaevery cell dashed; W2 "not home ground" (the bounds gate — BB AC-4 withinBounds = false); picker live; nothing commits
Commit succeedshost confirmstructure appears with thunk + squash / settle; cost debited (bag flash); ghost stays; mode persists
Commit refused at host (EC11)a gate failed host-side although the ghost was solid (stale positions / someone took the timber)honest no-op: nothing appears, nothing debited; the ghost re-evaluates next frame and shows why; no dialog, no sound
Race lost (two ghosts, one footprint)the other client's commit landed firsttheir wall appears; your ghost turns dashed "blocked"; nothing debited
Hold-to-repeat runningConfirm held after a successful commitafter each confirm the ghost steps one cell in the last ghost-move direction (KB/M: the direction the pointer last crossed a cell boundary; gamepad: the right stick's dominant axis) and commits again; ≤ 1 commit in flight (Rule 11a) — the next fires only after the previous confirms
Hold-to-repeat refuseda cell in the row fails any gatestops on that cell (ruled 2026-08-31); ghost stays dashed with its reason; release and re-aim to continue — never skips a cell. Self-sealing (BB EC23): a row toward yourself stops on the sealing cell with "no way out" — no lookahead affordance at MVP, stated as a non-design
Hold-to-repeat, out of materialsmaterials gate failssame stop rule; S2 shows the shortfall
Storage placedchest commit confirmedchest appears; SharedStorage instantiated (BB Rule 12) — nothing else changes in-mode
Demolish targetingDemolish held with a candidate in rangeW4 highlight + refund preview while the button is down; the action resolves on release (#9). "Instant" (BB Rule 9) describes the commit — no timer, no dialog — not the gesture; the gesture is press → preview → release, resolving in one frame on the release edge
Demolish refusednon-empty chest / refund won't fit CARRY"empty the chest first" / "no room for the refund" (#6) — no-op
Demolish racestructure already gonenothing happens (#7); no cue
Panel / pause / Downed / Deadanymode exits (Entry & Exit table)
Loading—none: registry, defs and knobs are already-loaded / replicated state

Interaction Map

Mapping for KB/M (primary) and gamepad (partial); Build map per ADR-0007 §1, routing per §3 BuildModeRouting. This is the one screen where the two schemes deliberately differ (ADR-0007 rule (a)): on KB/M LMB / RMB are Confirm / Cancel inside the mode; on gamepad RT still strikes (and exits) while South places.

Component Action KB/M Gamepad Feedback Outcome
ModeEnter / exitB (BuildToggle)D-pad Upbrief non-musical cue; grid + ghost appear / vanish; picker strip slides 100–150 msclient-local
GhostMovemouse (PointerWorld)right stick cell-steps the ghost along its dominant axis (key-repeat 300 / 100 ms, #8); the left stick keeps moving the playersnap + 0.35 u nudge; solid / dashed per frame—
PickerPrevious / nextQ / scroll wheel (PickPrev / PickNext)D-pad Left / Rightselected outline moves; ghost footprint swaps; no tick soundclient-local; session-sticky (a UX ruling with no GDD basis: the selection survives mode toggles, death and tide deposits; resets to wall on host quit / new session; never saved or replicated)
Picker entrySelect by clickclick on the entry—same—
GhostPlaceLMB (Build.Confirm)Southpredicted: nothing; host-confirmed: thunk + squash / settle + structure + bag flashPlaceStructure command
GhostHold-to-repeat (Repeat Placement)hold LMBhold Southone thunk per cell; the ghost advances after each confirmone PlaceStructure per cell, serialised (≤ 1 in flight)
ModeCancelRMB / Esc (Build.Cancel)Eastghost vanishes; exit cueexit
ModeExit via actionany non-Build Player actionRT (Strike), etc.exits first; the action applies the same frameADR-0007 routing
Demolish (Idle / Move)Target + demolishRMB down = preview on the cursor-raycast target; release = demolishD-pad Down held = preview on the nearest-in-cone target; release = demolishhighlight + refund preview while down; host-confirmed removal + sound + refund flashDemolish command
Demolish pressed inside build mode—RMB is Build.Cancel (ADR-0007 rule (a)) — exits, no demolishD-pad Down Pressed exits build mode (§3 exit set); the subsequent Released demolishes only if a candidate is under the reticle at release — ordinarily nothing (ADR-0007 amendment: Demolish is consumed on Released, Pressed opens the preview)exit cue—

Repeat Placement — the gesture defined: Confirm.Held after a confirmed commit → when the confirm lands, the ghost advances one cell in the last ghost-move direction and, if the new cell is valid, commits again; direction resets when the player moves the ghost manually; a refused cell stops the row on that cell; release ends the row. Read as ButtonState.Held — not an asset Hold interaction (ADR-0007 rule (d)); its accessibility alternative is single presses (the same action with no hold).

Focus: the mode has no UI focus — nothing captures; the UI map is disabled in-mode; the picker is driven by PickPrev / PickNext and pointer clicks, never UI.Navigate. Exactly one selected picker entry at all times.


Events Fired

Player Action Event Payload Persistent?
Place (each cell, incl. every repeat step)PlaceStructure command (ADR-0002 CommandKind.PlaceStructure)structureId, origin Cell — intent-time data only (ADR-0003 §4 worked command)Yes — registry row + cost debit + structure instanceId counter (ADR-0004)
DemolishDemolish command (CommandKind.Demolish)instanceIdYes — registry removal + refund to CARRY
Enter / exit mode, ghost move, picknone — client-local (Rule 5: zero replicated state until commit)—no
Refused commit / racenone from this client—the host's refusal is the event

No analytics events exist in this project.


Transitions & Animations


Data Requirements

Data Source System Read / Write Notes
StructureDef table (3 at MVP: wall, workbench, chest — id, category, footprint, cost, stationTier?, storageSlots?)GameData Structures (ADR-0006 stage 6)ReadStatic after boot; picker order = data order
Ghost validity + reasonRules.Build.PlacementLegality — client-provisional evaluation over an ITerrainQuery snapshot, the replicated IStructureRegistry copy, replicated player positions, ISocketLayoutReadProvisional; the host re-samples all 8 gates at commit (Rule 6); a disagreement = EC11 honest no-op
Snap + nudgeRules.Build.ghostSnapResult (pure; 1 u cells; nudge 0.35 u)ReadReturns a Cell — byte-identical on both rails (ADR-0003 §2)
Affordability per entry (ghost-centred)Inventory — own CARRY ∪ ISharedStorage within stationRange of the ghostReadStorage side one RTT stale (stated)
placementReach, stationRange, socketNoBuildRadiusGameData Configs.Build / Configs.CraftReadGrid radius, draw radius, socket exclusion — never hardcoded
Place / demolishhost-channel commandsWrite via command onlyThe UI owns nothing; zero replicated ghost state (Rule 5)
Demolish candidates + refund previewreplicated IStructureRegistry + StructureDef.cost (current — no cost snapshot, EC14)ReadChest emptiness from ISharedStorage.ContentCount (host-confirmed copy)
Last-selected structureclient-local session memorylocalNot saved, not replicated
EC21 correctionRules.Build.FindCorrectionCell on the displaced client (owner rail)—Not a build-mode UI concern; listed for completeness

Architectural flag: the ghost is pure prediction — this spec depends on provisional and authoritative F1 disagreeing gracefully (EC11), never on them agreeing.


Accessibility

Tier: Standard.


Localization Considerations

Element English max Layout-critical Priority
Placement reasons (8 strings; longest "someone is standing there")≤ 28 charsone line above the ghost (world)HIGH
Affordability reason ("not enough fiber in reach")≤ 32 charsone line, S2HIGH
Picker entry — name line ("Wall") + cost line ("4 timber" / abbreviated "8t 4f" when two materials)name ≤ 12 chars English; cost ≤ 10 charstwo lines beside a 40 px icon in a 180 px entry (≈ 17 chars/line at Medium)HIGH
Refund preview ("+4 timber")≤ 14 charsone lineMedium
"empty the chest first" / "no room for the refund" / "not home ground"≤ 24 charsone lineHIGH

There is no on-screen input-hint bar at MVP (the wireframe's footer line is documentation). HIGH rows must survive 40 % expansion on one line.


Acceptance Criteria


Open Questions