DRIFTLANDS co-op survival on drifting tides — studio progress

Base Building

Status: Second re-review 2026-08-27 (fresh session, 4 specialists + CD, scoped to CS-1..3; verdict NEEDS REVISION, narrow — CS-1 closed clean; CS-4 (perimeterSegments row + AC-20b sweep) and CS-5 (placerHasExit staleness/edge/candidate semantics + Edge Case 21 pins) applied same day plus the recommended pass; see design/reviews/base-building-rereview-2-2026-08-27.md); third re-review 2026-08-27 (fresh session, 4 specialists + CD, scoped to CS-4/CS-5; verdict NEEDS REVISION, narrow — CS-6 (knob-row bounds 25–57 → 35–58, gate subject named) and CS-7 (AC-14b constructible, AC-14c BFS pinned, host-exactness mechanism, maze boundary) applied same day plus the recommended pass; see design/reviews/base-building-rereview-3-2026-08-27.md); fourth re-review 2026-08-27 (fresh session, 4 specialists + CD, scoped to CS-6/CS-7; verdict NEEDS REVISION, narrow — blocking CS-8 (wallCost 3–6 → 3–4), CS-10 (Edge Case 17 bound rebound to placementReach < 2 × gridCellSize, AC-64b), CS-11 (AC-14b companion pinned, AC-14c coordinates), CS-12 (Edge Case 21 pin (iii) fresh search); recommended CS-9/13–16; nice CS-17 — all applied same day; see design/reviews/base-building-rereview-4-2026-08-27.md); fifth re-review 2026-08-27 (fresh session, 4 specialists + CD, scoped to CS-8–CS-16 with the closure step — every numeric cell passed; verdict NEEDS REVISION, narrow — one blocking surface: CS-11's pinned AC-14c hop-2 GIVEN was unreachable under Edge Case 21's own clear-cells-only rule, and under that rule no search depth beyond one hop is reachable at all, so CS-18 retired the BFS hop bound (Edge Case 17 ratio row, AC-64b, pin (i)'s ⌊placementReach / gridCellSize⌋) in favour of a four-neighbour ring check with a binding reference block, rewrote AC-14c, added AC-14e, plus the recommended/nice tail (Teleport() claim → ADR input, CS-13 two-phase split, AC-14b fixture constraints, knob-row wording); user decisions: ring check over BFS, pseudocode block adopted; see design/reviews/base-building-rereview-5-2026-08-27.md; no sixth panel — CD gates the revision inline). Prior: re-review 2026-08-27 (design/reviews/base-building-rereview-2026-08-27.md, CS-1..3 applied); independent /design-review 2026-08-27 (design/reviews/base-building-review-2026-08-27.md), all 5 change-sets applied and held.
Author: Jeroen + agent panel
Last Updated: 2026-08-27
Creative Director Review (CD-GDD-ALIGN): CONCERNS (resolved) 2026-08-26 — C1 orbit test added to Rule 3 + schema-negative AC-7b; C2 staleness falsification criterion added (OQ8); C3 "no move verb" reclassified from locked rule to MVP scope decision with Tier 1 revisit trigger. Advisories A1–A5 routed (A1 producer sizing flag on OQ4, A2/A3 fantasy copy + AC-48b, A4 propagated to the phase brief, A5 art-bible fix-pass). Crafting OQ9 negative ruling APPROVED on merits: naming the MVP home-facing dead zone is pillar-compliant; all three available "fixes" violate an anti-pillar.
Implements Pillar: Pillar 2 — Home Is the Fixed Point (primary); serves Expression (#3) and Fellowship (#2) aesthetics

Overview

Base Building is how the group's permanent home island grows from camp to keep: a build mode in which any player places walls, crafting stations (workbench now, forge to come), and storage containers on the home island, paying material costs drawn from CARRY plus nearby storage at the instant of placement. It is the physical expression of Pillar 2 — the base is the thing that lasts while everything else drifts, and the concept names it the group's core motivation: progression on something that is your own and endures. Beneath the act sits a small data layer other systems read as fact: Crafting asks whether a station of the required tier is in range (stationPresent is this system's answer), Inventory's SharedStorage containers exist where Base Building placed them, and Save persists the structure list. The base is a host-authoritative structure registry plus placement rules — nothing simulates while nobody plays. Structures are never inventory items (Crafting Rule 10, peer contract): build mode draws materials directly at placement in one atomic host commit per structure. MVP ships the basic set — walls, workbench, storage — enough to stand the base up and prove the loop's final beat; fortification depth, station tiers beyond the workbench, and defense pressure belong to Tier 1+.

Player Fantasy

Base building's fantasy is the fixed point — "home doesn't move." The payoff isn't the structure; it's the contrast. The anchor moment: limping back from a bad run — the land nearly took you, half a bag, the coast already cooling violet behind you — and crossing the shore threshold onto home ground. Nothing is chasing you. The walls are exactly where you left them. Where the whole rest of the game is motion — lands arrive, lands leave, everything out there is on a clock — the base is the one thing that only ever changes because one of you changed it. That is Pillar 2 made physical, and it lands hardest at the worst moments.

Its secondary register is collective authorship: home doesn't move, and everyone's hands are on it. At MVP this reads as a **group artifact**: the layout is nobody's individual plan — walls, bench, and chests landed where different hands put them, and a new friend should read "this is theirs, collectively" from the shape — never from a plaque. (Shifting a badly-placed bench is a demolish-and-re-place round trip at MVP — free in materials by Rule 9, but a deliberate multi-step act, not a casual nudge; the low-friction fix-each-other's-placements beat matures with Rule 9's Tier 1 revisit trigger.)

Its necessary shadow is staleness: if "fixed" ever reads as "frozen" — wallpaper instead of pride — the fantasy dies. The fixed point must feel earned-static: still because you made it and it held, not still because nothing can happen there.

Deferred honestly (Tier 1+): the walls-as-memory fantasy — "that wall is from our first week", the base legibly encoding *who built what, when* ("all of us built this, at different times" read from the shapes alone) — needs structure variety, rotation, and visible age the MVP set (three uniform snap-aligned structures) cannot provide. Named here as the direction, not claimed as an MVP experience; the whole-shape test's clean silhouette deliberately wins that trade at MVP (design-review 2026-08-27, Axis 3 ruling). **This is one of two unverified-at-MVP admissions about the same "keep, not shelter" claim** — the other is perimeter length: Formulas' perimeter check states plainly that the 50-segment anchor is session-length-derived and whether it reads as a keep on the real island is unverified until the spatial budget is authored (third re-review 2026-08-27). Both route to OQ5/OQ8; read together, not as two small independent risks.

MVP honesty clause (rules crafting OQ9 in the negative): MVP base building is a finite stand-it-up project — walls, a workbench, storage, placed in the first sessions and then done. It is not a session-to-session creative outlet, and this GDD does not claim otherwise: after base-up, the home-facing dead zone crafting's review flagged is real and accepted at MVP. The ongoing-outlet role belongs to Tier 1+ building scope. (Crafting OQ9 updated to cite this ruling.)

**Tone and design tests this fantasy imposes (binding on future base-building features):**

Reference anchor: Valheim's first raised hall as the moment a camp becomes a home — minus the raids: at MVP nothing attacks the base; walls are shelter psychology and boundary-drawing, not (yet) fortification, and the doc says so plainly. **The MVP anchor is therefore the *quieter half* of the Valheim moment (CD A2)** — Valheim's hall lands partly because raids test the walls; that half arrives with Tier 1 defense. If the raised-hall feeling underdelivers at MVP playtest, read it as this deliberate scope decision before reading it as an execution failure.

Detailed Design

Core Rules

`StructureInstance = {instanceId (host-monotonic, never reused), structureId, position}` — the whole registry.

States and Transitions

From Event To Notes
Idle/Movebuild-mode toggleBuildMode (ghost)Client-local; no host involvement
BuildModeconfirm input, gates pass at hostBuildModeInstant atomic commit; structure appears; mode persists
BuildModeconfirm input, any gate failsBuildModeHonest no-op; ghost stays; reason string shown
BuildModetoggle / any non-build actionIdle/MoveGhost vanishes; nothing committed. Non-build action, defined (re-review 2026-08-27): any input from an Input System action map other than the Build map — attack, interact (chest/station), inventory, menu. Move and camera inputs live on the shared Player map and never exit (Rule 5). The structure picker is visible only in this state

Demolish is an instant targeted action from Idle/Move, not a state. Structures hold no state machine (Present only, Rule 8).

Feedback authority split (house pattern):

Effect Timing
Ghost preview, validity tint, snap/nudge alignment, grid displayInstant — client-side (outcome-independent)
Structure appears/disappears, cost debit, refund credit, SharedStorage creationHost-confirmed broadcast only

Interactions with Other Systems

System BB consumes BB provides Interface owner
InventoryAtomic CanRemove/Remove(cost) at placement; CanAdd/Add(refund) at demolish (Rule 6 shape)Siting + instantiation of SharedStorage containersInventory owns stacks/atomicity/contents; BB owns placement
CraftingMaterial item definitions (peer, crafting Rule 10)stationPresent(tier) — re-sampled at every craft/repair commit, falsifiable mid-beat (Rule 10)BB owns structures/costs/placement entirely; peer both ways
Tide SchedulerHome-island permanence + socket positions (for exclusion zones) — static facts, no runtime coupling—Scheduler's jurisdiction is docked lands; home is outside it
SaveReconcile predicate for StructureRegistryStructureInstance[] list; storage contents persist via Inventory's format, never duplicated hereQuick-spec ✅ 2026-08-26 — pipeline stage 'permanent reconcile' invokes BB's predicate
Enemy AI— none —— none —Home is hostile-free at MVP (stated so it doesn't read as an omission)
Land Dock/Undock— none —— none —Home is permanent, outside LDU jurisdiction
UI/HUD—Ghost validity state incl. which gate fails (per-cause reason strings, crafting Rule 3's pattern); structure picker dataUI owns presentation 📌 /ux-design
Base-Defense Events (Tier 1)——Explicitly out of scope; no rows designed for it — named so absence reads as intent

Formulas

Base-building math is deterministic and data-driven — no RNG anywhere (house rule). All costs and constants live in assets/data/structures.json, validated at load (Edge Cases).

Baseline structure table (MVP, ruling 2026-08-26)

Structure Category Footprint Cost Material logic
Wall segmentwall1×14 timberTimber-only — walls read as wood; the repeatable bulk sink stays mid-band
Workbenchstation (cloth)2×16 fiber + 10 timberLashings + frame; shore+mid only — buildable before the first sword, which it gates
Storage cheststorage (40 slots)1×18 fiberCheap, placeable off a single shore trip

No sinew in any structure (ruling 2026-08-26): sinew is the deep-band reward gating Reinforced Garb (crafting AC-55) — walls competing for it would throttle base-up on the scarcest band. Base-up requires no inland risk, ever: home stays the safe new-friend space (Pillar 5).

Bootstrap check (party-scoped, BB's first-session gate): workbench + storage + first sword + first garb = (6F+10T) + 8F + (6F+8T) + 10F = 30 fiber + 18 timber = 48 units, against harvesting's exported lowEndPartyHaul (165 — a first-class named constant in harvesting's Formulas section as of 2026-08-27) → ratio ≈ 0.29. Deliberately the biggest single material event of session one — base-up should feel like the session's project — while leaving ~71% of the haul spare for a rough first session. Bootstrap order: dock → harvest shore+mid → workbench (gates all crafting) → sword/garb → storage (ungated, whenever convenient). *(The pre-sword fight for the workbench's mid-band timber is defined: combat's unarmed strike does 14 — cross-GDD review D2, 2026-08-27 — so "no inland risk" means no deep-band risk; mid-band guards are fightable bare-handed.) (Terminology note: crafting's "base kit's materials" means exactly these session-one harvestable materials — no starting-inventory or pre-placed-structure system exists anywhere.)*

**Perimeter check (the finite project — anchor revised 2026-08-27 re-review CS-2: 50 segments, was 60):** a modest full perimeter = 50 segments × 4 timber = 200 timber. At a realistic mixed-haul wall-dedicated pace (mixedHaulTimberPerSessionLow/High = 58–138 — harvesting's exported party-scoped constants, derived there as session haul × mid-band timber share and cited here by name), that is 2–4 casual party sessions of wall-building at the exported extremes (200/138 → 2; 200/58 → ⌈3.45⌉ = 4) — the band the MVP honesty clause promises: a real multi-session project, never one trivial session, never a treadmill. A 25-segment minimal shelter (~100 timber, ~1–2 sessions) is a legitimate faster path — **the never-one-trivial-session guarantee applies to the full 50-segment perimeter only; the shelter is deliberately faster and does not carry it. The gate's subject is perimeterSegments — the full-perimeter anchor constant the CI gate consumes — not every perimeter a player might build (third re-review 2026-08-27, CS-6).** The 25-segment shelter is a player choice that sits below the knob's gate-safe floor by construction (gate-safe: a value at which the perimeterSessions CI gate passes at both exported haul extremes — defined at first use, fourth re-review 2026-08-27, CS-17) (25 × 4 = 100 timber → ⌈100/138⌉ = 1 session at the high haul extreme, which the gate rejects for the anchor and which is exactly the shelter's point); it is not a knob setting and the knob row does not govern it. **The 50-segment anchor is provisional** until home-island content authoring confirms the island fits it (Dependencies → home-island content authoring); the prior 60-segment anchor was dropped because harvesting's honest low end (58) put it at 5 sessions. **Stated plainly (second re-review 2026-08-27): 50 is a session-length-derived number, not a shape-derived one** — it was chosen to keep the 2–4 session band once harvesting's constants were corrected, and whether 50 segments reads as a keep rather than a shelter on the actual island is unverified until the spatial budget is authored. If it reads as a shelter, the CI gate's 2–4 sessions is an accurate number describing a fantasy failure, and the anchor moves (within the knob's 35–58 gate-safe range) or the island does. **Owner and trigger (third re-review 2026-08-27):** home-island content authoring (the Dependencies row that owes the spatial budget) makes this call during the spatial-budget authoring pass, with a procedural check, not a visual impression: does the authored placeable region admit ≥ perimeterSegments wall cells plus the MVP station, chest and walk-space footprints? (**"Walk-space" is not yet a defined quantity — fourth re-review 2026-08-27, CS-16:** until home-island authoring pins a walk-corridor width in cells and the perimeter's assumed shape, this check is a stated intent, not a runnable procedure; those two inputs are the fit check's own open items, owed by the same authoring pass.) If yes, the anchor stands and only OQ5's playtest can still fault it; if no, the anchor moves first (it is the cheaper edit) and the island only if 35 segments still does not fit. (If the anchor is revised after players have built, Edge Case 14's cost-drift precedent applies: no retroactive structure changes; only the anchor and the CI gate move — CS-17.) This compounds with the Player Fantasy's deferred walls-as-memory admission — the same fantasy, two unverified legs.

F1. placementLegality

`canPlace = withinReach AND withinBounds AND noOverlap AND noPlayerOccupancy AND placerHasExit AND validTerrain AND notInSocketExclusion AND CanRemove(cost, CARRY ∪ STORAGE-within-stationRange-of-placementSpot)`

Variables:

Symbol Type Range Description
withinReachbool—Placement spot within placementReach (6 u) of the player (Edge Case 12 ruling)
withinBoundsbool—Footprint entirely inside the home island's placeable region
noOverlapbool—Footprint intersects no existing StructureInstance's footprint
noPlayerOccupancybool—No footprint cell contains a host-known player position — defined as the host's most recently received owner-authoritative NetworkTransform state for that client (the host's own player: read from the live Transform the host owns — under NGO 2.x owner-authoritative NetworkTransform the owner holds one value, no delayed copy, so there is no transport hop and no remoteInterpBuffer in the read; this is exact for a structural reason, not because the host has fresher-than-RTT knowledge of anyone else — third re-review 2026-08-27, CS-7; exact means free of transport and interpolation staleness — whether the read sees the host player's current-frame position additionally depends on tick ordering this GDD does not pin, two unknowns: which execution phase the commit critical section runs in and which phase owner movement writes in: ⚑ netcode ADR input, Edge Case 21's list — fourth re-review 2026-08-27, CS-13; restated CS-18) — at the commit re-sample. Best-effort for remote players by construction (one RTT stale); Edge Case 21 holds the actual guarantee (added 2026-08-27, design-review BL-3a; reframed same day, re-review CS-3)
placerHasExitbool—Evaluated against the registry with the candidate footprint already applied: the placing player's own occupied cell has ≥ 1 orthogonally adjacent cell that (a) lies inside the home island's placeable region, (b) is buildable terrain, and (c) is covered by no structure footprint — existing instances or the candidate. A neighbour outside the placeable region is not an exit (island-edge cells are out-of-bounds, not merely non-buildable — the modal shoreline case). Evaluated for the placer only, against the placer's host-known position (a local one-hop neighbourhood check, not a path search; contrast Edge Case 21's N-hop escape search). Staleness parity with noPlayerOccupancy (second re-review 2026-08-27, CS-5): exact for the host's own player (same mechanism as the noPlayerOccupancy row: the host owns its own transform, local read, no interpolation buffer); one RTT best-effort for a non-host placer, whose host-known position is the last owner-authoritative state received. The solo guarantee is exact because a solo player is always the host; a non-host who self-encloses inside the RTT window has, by definition, another player in session, and Edge Case 22's social closure covers that case. (The placer standing inside the candidate footprint is not a separate case: noPlayerOccupancy already fails it, and the host's own position is exact.) Added 2026-08-27, re-review CS-3; Edge Case 23
validTerrainbool—All footprint cells are buildable terrain (not water/cliff)
notInSocketExclusionbool—No footprint cell within socketNoBuildRadius of any dock socket center (F4)
cost(itemId,count)[]per StructureDef, non-emptyPer the Baseline structure table
stationRangefloat (u)3 — Crafting-owned knob (safe 2–6), consumed here (ownership pinned 2026-08-27; was mislabeled "locked")Draw radius centered on the placement spot, not the player (ruling 2026-08-26); double duty with F3 — see Knob-interaction warnings
CanRemovebool—Inventory's atomic affordability check over CARRY ∪ STORAGE-in-range
canPlacebool—Result

Output Range: boolean; the ghost preview evaluates it provisionally every frame for validity tint, but only the host's **commit-instant re-sample** is authoritative (Rule 6). Any conjunct false at commit = honest no-op — ghost persists, nothing debited. Example: wall footprint 1×1 on buildable grass 4 u from the player (within placementReach 6 u), 6 u from the nearest socket (socketNoBuildRadius 4 u → clear), no structure underfoot, no player standing in the cell, the player's own cell keeps a free neighbour, 4 timber reachable from CARRY + a chest 2 u from the placement spot → all eight conjuncts true → canPlace = true → commit fires.

F2. demolishLegality

`canDemolish = structureExists AND (category ≠ storage OR storageEmpty) AND CanAdd(refund, demolisher.CARRY)`

Variables:

Symbol Type Range Description
structureExistsbool—Target instanceId still in the registry — re-sampled fresh, never cached from targeting time
categoryenumwall\station\storageFrom the instance's StructureDef
storageEmptybool—Evaluated only for storage: SharedStorage.contentCount == 0
refund(itemId,count)[]= 100% of costNo partial refund exists (Rule 9). Sole exception: inert relics refund exactly zero — their cost is undefined (Edge Case 13, revised 2026-08-27)
CanAddbool—Inventory's capacity check against the demolisher's CARRY only — refunds never route to STORAGE
canDemolishbool—Result

Output Range: boolean, re-sampled at the demolish action's own commit instant (F1's discipline). Examples: non-empty chest → storageEmpty = false → no-op, reason "empty the chest first". Wall demolish with a full bag → CanAdd = false → no-op, reason *"not enough room to carry the refund"* — the structure stays standing.

F3. stationPresent — two named predicates

`stationPresentExact(tier) = ∃ s ∈ StructureRegistry : s.category = station AND s.stationTier = tier AND dist(asker, s.position) ≤ stationRange`

`stationPresentAtLeast(tier) = ∃ s ∈ StructureRegistry : s.category = station AND s.stationTier ≥ tier AND dist(asker, s.position) ≤ stationRange`

Two named predicates rather than one parameterized call: the comparison is a crafting-owned fact about what crafting is asking (craft = exact tier; repair = downward-inclusive), and a mode argument would let a caller silently pick the wrong one. Each caller states its requirement by which predicate it invokes.

Variables:

Symbol Type Range Description
tierenumcloth\bronze\…The tier the caller requires
s.stationTierenum—A candidate station instance's tier
dist(asker, s.position)float (u)≥ 0Euclidean distance, asker to station
stationRangefloat (u)3 — Crafting-owned knob (safe 2–6)Same knob as F1's draw radius — one value, two semantics; moving it moves both (Knob-interaction warnings)

Output Range: boolean; the host commit-instant evaluation is never cached — falsifiable at any instant (Rule 10), including mid-craft-beat. Client-side advisory reads are stale-by-replication snapshots, display only (Rule 13's two-context split). Example: a bronze station 2 u from the asker: stationPresentExact(bronze) ✓; stationPresentExact(cloth) ✗ (crafting cloth still needs a cloth bench); stationPresentAtLeast(cloth) ✓ (repairing cloth gear at a bronze station — downward-inclusive).

F4. socketExclusion

`notInSocketExclusion(footprint) = ∀ cell ∈ footprint, ∀ socket ∈ Sockets : dist(cell, socket.position) > socketNoBuildRadius`

Variables:

Symbol Type Range Description
cell(float,float)—Each grid cell the candidate footprint occupies
socket.position(float,float)fixed, scheduler-ownedDock socket center
socketNoBuildRadiusfloat (u)> 0, tuning knobHard exclusion radius — walls can never enter it (Rule 3)

Output Range: boolean; a single failing cell against a single socket fails the whole footprint. Example: a 2×1 footprint with one cell 3.5 u from a socket at socketNoBuildRadius = 4 → that cell fails → placement illegal regardless of the other cell.

CI gates (live-recomputed — designed in from the start)

Both gates recompute from live named constants at test time — never hardcoded literals (the lesson of crafting AC-54's review retrofit, applied at authoring time). The cross-system inputs are **harvesting's exported constants** (lowEndPartyHaul, mixedHaulTimberPerSessionLow/High — first-class named values in harvesting's Formulas section as of 2026-08-27, owned there, cited here by name; design-review BL-1 closed the gap where these existed only as prose) plus crafting's live recipe costs and this table's live structure costs:

**Per-knob safe ranges are marginal, not joint (design-review 2026-08-27 BL-2).** Each Tuning Knobs safe range holds only for that knob moving alone; corners of the joint box FAIL these gates (all four cost knobs at +50% → bootstrapRatio 0.436). **A marginal range can also fail with nothing else moved (fourth re-review 2026-08-27, CS-9):** the prior example here — wallCost 6 at mixedHaulTimberPerSessionLow 58 → ⌈300/58⌉ = 6 sessions — was never a corner: 58 is harvesting's live exported low extreme, one of the two values AC-46 asserts at on every run, so wallCost 5 and 6 failed the gate at everyone else's defaults and the wallCost row's own 3–6 range was wrong for a session (corrected to 3–4, CS-8). Green gates prove pacing arithmetic only — never that the anchor reads as a keep on the authored island (Formulas' perimeter check; Player Fantasy's two unverified legs — CS-16). **The CI gates, not the per-knob ranges, are the joint authority** — any retune of any participating knob re-runs both gates before it lands.

Economy scope note (design-review 2026-08-27): every cost in this table is a one-time sink — BB contributes no recurring material sink, so after base-up (~session 3) the timber/fiber faucets have no BB destination while harvesting's faucet recurs unbounded. Accepted for MVP; whether the haul loop retains its bank-or-push tension after sink saturation is OQ11's playtest question (owner: economy + producer), and any lifetime-sink-vs-faucet invariant belongs to a cross-system economy pass, not this GDD.

Edge Cases

Commit races

Disconnects

Ghost / preview

Registry / persistence

Config validation (load-time, fail loudly)

Condition Outcome
cost references an unregistered itemIdloud reject
footprint.width ≤ 0 or footprint.height ≤ 0loud reject
Duplicate structureIdloud reject
Unknown category enum valueloud reject
Any cost[].count ≤ 0, or empty cost[]loud reject (a free structure is the placement-side free-item exploit)
category = station with no stationTier, or stationTier on a non-stationloud reject
category = storage with storageSlots ≤ 0, or storageSlots on a non-storageloud reject
stationTier not in Crafting's known tier enumloud reject (cross-system reference validation)
socketNoBuildRadius ≤ 0loud reject — would let walls block the tide's shoreline deposit point (tide Rule 9)
Any socket's authored depositPoint (LDU Rule 8; tide Rule 9 deposits caught players there) farther than socketNoBuildRadius from that socket, or not on buildable-adjacent shoreloud reject — "the shore must always be usable" is systemic (Rule 3) and is validated against the actual deposit point, not just > 0 (AC-63b; rebound 2026-08-27 re-review CS-2 — the prior "deposit landing cells" artifact never existed in tide)
structures.json fails to load on the async Addressables boot path (missing key, parse exception)routes to the same boot-blocking fatal-config state as every row above — never a silent fallback to defaults (owner: engine-programmer boot sequence; added 2026-08-27 re-review)
placementReach ≤ 0 or stationRange ≤ 0loud reject — stationRange is the shared knob already enforced at Crafting's load; BB confirms the shared value loaded rather than re-deriving it

Retired row (fifth re-review 2026-08-27, CS-18): the placementReach < 2 × gridCellSize reject (added CS-7, rebound CS-10, tested by AC-64b) is gone — Edge Case 21's check no longer has a hop bound, so no BB load-time relation between placementReach and gridCellSize exists. gridCellSize is OQ6's snap constant only.

Scope closures

Player/structure interaction (added 2026-08-27, design-review BL-3)

``` // pure; plain-data inputs only (Acceptance Criteria purity mandate) Cell? FindCorrectionCell(Cell seed, ITerrainQuery terrain, IReadOnlyList<Footprint> registry, Region placeable) foreach d in [N, E, S, W]: // fixed order — the only tie-break c = seed + d if !placeable.Contains(c): continue // out-of-region is never a destination if !terrain.IsBuildable(c): continue if registry.AnyCovers(c): continue // includes the footprint that fired the check return c return null // enclosed → Edge Case 22 ```

The check reads no player positions — a neighbour another player stands on is a legal destination (a fresh occupancy read on the correcting client would be the same one-RTT-stale read this edge case exists to survive; landing beside someone is ordinary movement's business). This is a discrete logical reposition, never a physics-solver push (walls are non-dynamic colliders that do not push players; Box2D contact resolution would give a contact normal, not a free cell). Owner-rail corrects owner; other clients see it as an ordinary NetworkTransform update through normal interpolation — nothing is predicted or rewound, so no forbidden pattern is touched. **Observer asymmetry, proposed (third re-review 2026-08-27, CS-7; owner and trigger named fourth re-review, CS-15):** the mover snaps (it is a teleport-snap event on the owner rail, per (iv) — teleport-snap names the owner-side discrete write, not the observer treatment); observers see a one-buffer slide, unlike tide deposit and death respawn, which cut the interpolation buffer for observers too. **Mechanically the asymmetry as proposed requires a plain interpolated NetworkTransform state update rather than Teleport() (the API the other two events use is what cuts the buffer); whether a one-cell discrete write actually survives NetworkTransform's own snap thresholds as an interpolated update, and what an observer sees along that one-cell path, are ADR questions, not settled facts (fifth re-review 2026-08-27, CS-18 — the prior sentence asserted the mechanism ahead of the ADR that owns it). Settled 2026-09-11 by ADR-0009 §5:** the correction is a plain owner-side position write; NGO 2.13.2's PositionThreshold gates sending only and the receiver has no snap threshold, so observers slide the one cell over the buffer (its V2 verifies in play); SnapReason does not grow. Accepted because a one-cell nudge sliding over remoteInterpBuffer (~1 tick) reads as motion the wall's own thunk already explains, and cutting the buffer would buy nothing visible. **Player-movement owns the teleport-snap list and must ratify this asymmetry in its Rule 6 (routed; BB does not declare it unilaterally). Owner and trigger (CS-15): the netcode architecture ADR ratifies or rejects it at the first NGO integration story that touches NetworkTransform; until then it is a proposal in both documents (⚑ ADR input, below). Pinned (second re-review 2026-08-27, CS-5): (i) the check is bounded by construction** — the four orthogonal neighbours of the player's own cell and no farther (CS-18; the prior ⌊placementReach / gridCellSize⌋ BFS-hop bound and Edge Case 17's ≥ 2 hops row are retired — gridCellSize remains OQ6's to pin for snap, but this check no longer consumes it) — the player's own cell is never a destination; (ii) the correction **re-checks the overlap immediately before writing** and aborts if the player has already walked clear — it never repositions on a stale trigger. **The window this closes is local and cross-frame, not a host race (third re-review 2026-08-27):** the replicated footprint's arrival and the correction's execution are not guaranteed to run on the same client frame as the trigger, and owner movement is instant, so the player's own input can clear the cell in between — the re-check catches exactly that gap and nothing else; (iii) a player has at most one in-flight correction — a second qualifying replication event while one is pending is re-evaluated after the first completes — **"completes" = the owner's local synchronous transform write in (ii) finishing on the owner's own frame; no host acknowledgement is awaited or defined (third re-review 2026-08-27), so "in-flight" spans at most one frame; the cap is therefore a closure against a future asynchronous acknowledgement, not an operative guard today, and no AC exercises it (fourth re-review 2026-08-27, CS-12) —** and the re-evaluation **invokes the free-cell check fresh, at the player's current (post-correction) cell, against the then-live registry — never a re-check of the first correction's stashed result** (CS-12; the cheaper reading breaks the accepted-race repair below — walk it: P at cell A, wall W1 lands on A, P moves to B; a second wall W2 whose noPlayerOccupancy read P's stale position at A commits onto B; a re-check of the A-rooted result finds nothing to do and P stays under W2, while a fresh check at B moves P off it — AC-14e). Idempotent under this reading only: if the player's current cell is itself clear at invocation, the (ii) re-check finds no overlap and nothing is written; (iv) this reposition is a **third authority teleport-snap event** alongside tide deposit and death respawn, registered in the player-movement quick-spec's Rule 6 enumeration — movement owns that list, BB does not add to it unilaterally. If **none of the four neighbours** is clear, the player is enclosed and Edge Case 22 applies — "enclosed" is a fact about the ring, not a path search: a clear cell two steps away behind a blocked neighbour does not count and cannot be reached without passing through a wall (CS-18). The displaced player's experience: being nudged out from under a friend's wall is world-friction (Pillar 4), not a bug — but it must read as one, so the reposition carries a tell (Visual/Audio Requirements) rather than a silent snap. **Accepted race:** during the correction window a second concurrent placement's noPlayerOccupancy may read the displaced player's stale pre-correction position; its outcome is honest either way (a false pass is corrected by this same path under (iii) — which is why (iii) must run the search fresh, CS-12 — a false fail is a harmless no-op). ⚑ netcode ADR inputs: wall-collider replication timing vs. owner-rail movement; whether this self-correction write passes through the owner rail's normal movement bounds checks; the write-ordering slot of the reposition relative to same-frame owner movement prediction (the re-check in (ii) is the design-level guard, not the ordering mechanism); **the read-ordering slot of F1's commit critical section relative to the host's own same-frame movement update — two unknowns: which execution phase the commit critical section runs in (Update / FixedUpdate / a transport callback) and which phase owner movement writes in (fourth re-review 2026-08-27, CS-13 — the F1 rows' "exact" covers transport, not tick order; split into its two inputs CS-18)**; the stale-read race above; **whether the correction write goes through NetworkTransform.Teleport() (which cuts observer interpolation, as tide deposit and death respawn do) or a plain interpolated state write — the observer asymmetry above requires the latter (CS-13/CS-15) — and whether a one-cell discrete write survives NetworkTransform's snap thresholds as interpolated at all (CS-18). Guardrail on every resolution (CS-18):** the owner rail stays the only writer of a player's transform — the host may re-broadcast the registry or re-signal the trigger, never write a remote player's position (Forbidden Patterns: no remote-player prediction, no host-side movement authority).

Dependencies

Upstream (Base Building depends on):

System Hard/Soft Interface consumed Status
Inventory & HaulingHardAtomic CanRemove/Remove (placement cost, CARRY ∪ STORAGE-in-range of spot) + CanAdd/Add (refund, CARRY-only) — Rule 6 shape; SharedStorage component instantiated per storage placementQuick-spec ✅
CraftingPeerMaterial item definitions (fiber/timber). StructureDef is BB's own parallel shape — RecipeDef reuse declined (Rule 1; crafting Rule 10 granted the choice)GDD Reviewed
Tide SchedulerSoft (static facts)Home-island permanence; socket positions for F4 exclusion zones — no runtime couplingGDD Reviewed
Save & PersistenceHardStructureInstance[] registry persistence + reconcile predicate (Edge Cases 13–16: inert relics, current-cost refunds, orphan posture)Quick-spec ✅ 2026-08-26 — adopts the EC 13–16 posture, lists BB as dependent; two-class rule: registry is permanent
HarvestingHard (named constants)lowEndPartyHaul, mixedHaulTimberPerSessionLow/High — the exported Formulas constants this GDD's CI gates AC-45/46 bind to by name (row added 2026-08-27, cross-GDD W5; harvesting's downstream table carries the mirror)GDD Reviewed
Player MovementSoftWall collision is movement's blocker set (movement Rule 4 ↔ AC-27); Edge Case 21's owner-rail correction is registered in movement Rule 6's teleport-snap list (row added 2026-08-27, cross-GDD W5)Quick-spec ✅ 2026-08-27
Art bibleConstraint sourceHome shape language (soft/rounded), structures never shape-harden, silhouette-first testsApproved

Downstream (systems that depend on Base Building):

Dependent Hard/Soft Interface provided
CraftingHardstationPresentExact(tier) / stationPresentAtLeast(tier) (F3) — re-sampled at every craft/repair commit, falsifiable mid-beat
Inventory & HaulingHardContainer siting: each storage placement instantiates one SharedStorage at BB's chosen position
UI/HUDSoftGhost validity state with per-cause reason strings; structure picker data
Enemy AI—None — home is hostile-free at MVP (stated so absence reads as intent)
Base-Defense Events (Tier 1)—Named as future dependent; no rows designed for it (first-to-slip discipline)

Bidirectional-consistency check: Crafting already names BB in both directions (Rule 10 + its dependency tables) ✓; Inventory's quick-spec forward-refs container placement to BB ✓; the Save quick-spec lists BB as a dependent and adopts the Edge Case 13–16 reconcile posture (Rule 5 permanent-reconcile stage, OQ7 resolved 2026-08-26) ✓; LDU has no runtime coupling either way (home is outside its jurisdiction) but authors the per-socket depositPoint AC-63b validates ✓.

**Forward references owned elsewhere (expanded 2026-08-27, design-review):** → netcode ADR: structure-registry replication — NetworkObject-per- structure vs. client instantiation from a replicated list, a load-bearing choice for what "instant/atomic" means operationally (BB story scoping is sequenced behind this decision); late-joiner sync (default: full-snapshot-on-join — the registry is small; snapshot ordering vs. in-flight commits must be decided); instanceId issuance/replication plus reissue-on-save-rollback; what "persisted CARRY" means for a mid-disconnect player under the owner-rail model (Edge Cases 8–9); single-ordered-channel host-arrival plus the batch-vs-arrival ruling (Edge Case 1); Edge Case 21 in full — wall-collider replication timing vs. owner-rail movement, the deterministic free-cell check, whether the self-correction write passes the owner rail's movement bounds checks, and the stale-read race for a second concurrent placement; placerHasExit's RTT-staleness exposure for non-host placers (same class as noPlayerOccupancy — the ADR either closes it or records it as the accepted residual routed to Edge Case 22); the write-ordering slot of Edge Case 21's reposition relative to same-frame owner movement prediction (its registration as a teleport-snap authority event in the player-movement quick-spec's Rule 6 is already recorded there and is not ADR-gated; only the ordering and bounds-check questions remain open — third re-review 2026-08-27); serialization of the correction when two structures land on one player inside a single correction window; a numeric or tick-based **staleness bound for the client's replicated registry copy** (Rule 13's advisory reads are currently "stale, unbounded"). → technical-director: "all host-rail commits are single non-yielding synchronous blocks on the host main thread" as a binding cross-cutting constraint (asserted throughout as "atomic"/"critical section" but stated as a requirement nowhere — it breaks silently in coroutine-heavy code); host-vs-client instant-commit feel asymmetry (AC-48c); Render-Graph-native shader cost of the dashed ghost outline and reach-following grid overlay vs. the <100 draw-call budget. → home-island content authoring: buildable-region/terrain authoring, plus the buildable-area spatial budget — nobody has yet computed whether the island minus socket exclusions and terrain actually fits a 50-segment perimeter + stations + chests + walk space; perimeterSessions guards a fiction until this is checked, and the perimeterSegments anchor stays provisional until it is. → Inventory + Save quick-spec: SharedStorage content serialization.

Tuning Knobs

Not tuning knobs (rules, locked by this GDD): full refund (never partial — inert relics are the sole exception and refund exactly zero, Rule 9 / Edge Case 13), empty-chest-first demolish, structure anonymity (no builder field), home-island-only building + the orbit test, instant commit (no placement timer), no queues/batches/pending-state (Rule 11a — repeat input cadence is 11b, /ux-design-owned and NOT locked), grid snap. Changing any is a design change requiring GDD revision + CD gate. *("No move verb" is deliberately NOT on this list — it is an MVP scope decision with a Tier 1 revisit trigger, Rule 9 / CD C3.)*

Knob Default Safe range Too low Too high Interacts with
wallCost4 timber3–4 (re-derived 2026-08-27, fourth re-review CS-8 — the prior 3–6 was refuted by this row's own "too high" cell: at the 50-segment anchor, 5 → ⌈250/58⌉ = 5 and 6 → ⌈300/58⌉ = 6 sessions, both outside the band. Worked bounds at both haul extremes: 3 → ⌈150/58⌉ = 3, ⌈150/138⌉ = 2 ✓; 4 → ⌈200/58⌉ = 4, ⌈200/138⌉ = 2 ✓. Marginal only — perimeterSegments at its default. Gate-derived: this range is a consequence of the perimeterSessions band at the current anchor, not an independent design judgement — CS-18)Below 3 the fast haul extreme drops to one session (⌈50 × 2 / 138⌉ = 1), breaking the band's lower bound — same structure as the perimeterSegments row. (The prior "trivial in < 1 session" was false in kind: at the slow end wallCost 2 is still ⌈100/58⌉ = 2 sessions; the floor is set by the fast end.)Above 4 the 50-segment perimeter exceeds 4 sessions at the honest timber low end (⌈50 × 5 / 58⌉ = 5) — treadmill territoryperimeterSessions CI gate; harvesting timber yields
perimeterSegments5035–58 (re-derived 2026-08-27, third re-review CS-6 — the second re-review's 25–57 was wrong at both ends: 25 fails the gate at the high haul extreme (⌈100/138⌉ = 1 < 2), and 57 was refuted by this row's own "too high" cell, which computed 58 as passing. The gate is two-sided at two extremes: the lower band bound forces segments × 4 > 138 → ≥ 35; the upper forces segments × 4 ≤ 232 → ≤ 58. Worked bounds: 35 → ⌈140/58⌉ = 3, ⌈140/138⌉ = 2 ✓; 58 → 232/58 = 4 exactly, ⌈232/138⌉ = 2 ✓; the set [35, 58] has no holes. This range governs the anchor constant only — the 25-segment shelter Formulas names is a player path below the gate-safe floor by design, not a knob value; see Formulas' gate-subject clause. The band carries no design preference within it — any value in range is CI-legal; 50 is chosen for headroom at the honest low haul, not for a felt pacing quality — CS-17. Marginal only — wallCost at its default (4), CS-18)Below 35 the high haul extreme drops to one session (⌈34 × 4 / 138⌉ = 1), breaking the band's lower bound — the floor is set by the fast end, not the slow one. (At the slow end alone, two sessions holds for 15–29 segments; a floor derived from 58 only is not a floor.)Above 58 the perimeter exceeds 4 sessions at the honest timber low end (⌈59 × 4 / 58⌉ = 5) — treadmill territoryperimeterSessions CI gate; home-island spatial budget (provisional until authored)
workbenchCost6 fiber + 10 timber±50%Affordable before the first real trip — the placement event is undercutFirst craft slips past session one — breaks crafting's first-session guaranteebootstrapRatio gate; crafting recipe costs
storageChestCost8 fiber4–12Trivial — acceptable; storage should be earlyDelays early banking, no upsidebootstrapRatio
storageSlots (per chest)4024–80Banking blocks at MVPNo harm, no point= Inventory's storage_slots_home — one value; Inventory owns the default
placementReach6 u5–8Backpedal-placement fiddlinessSight-unseen placement; whole-shape erosionstationRange (the draw radius stays 3 u, independent)
socketNoBuildRadius4 u3–8Walls crowd the tide's deposit shoreline (Rule 3's hazard)Shrinks the placeable shore areaSocket layout; re-check when Tier 1 adds 3–4 sockets ⚑

Knob-interaction warnings: structure costs + harvesting yields + crafting recipe costs form one balance system — bootstrapRatio and perimeterSessions are the shared invariants, both live-recomputed from the interacting systems' named constants; never retune one side without running both gates. **Per-knob safe ranges are marginal-only — the CI gates are the joint authority** (corners of the joint box fail; see the CI-gates section). **stationRange is Crafting's knob (safe 2–6), consumed here in two semantics at once** — F1's cost-draw radius (placement-spot-centred) and F3's station reach (asker-centred) move together by design; retuning it for crafting reach also retunes placement material-draw, and Crafting's knob row must carry the same warning. All BB values live in assets/data/structures.json, validated per Edge Case 17.

Visual/Audio Requirements

Base Building's visual contract is owned by the art bible; this section states what BB imposes and cites rather than restates.

Structures stay in home's rounded vocabulary, permanently: wall, workbench, and storage chest render in §3.2's guardrail shape language — soft, rounded, hand-built — and never shape-harden regardless of material tier (the gear-only hardening rule does not apply to structures). At MVP all three ship Camp-stage scale and material (§6.1: small-footprint, thatch/timber, Driftwood Grey/undyed tones). Keep-stage growth has no trigger defined anywhere; ⚑ **art-bible fix-pass item: §6.1 describes Camp→Keep growth but no system states what advances a structure between stages** — flagged, not invented here (MVP never leaves Camp stage).

Ghost grammar: outline-only (ruling 2026-08-26). Valid placement = the ghost renders with a solid ink outline; invalid = a **broken/dashed outline**; zero fill tint in either state. Maximally palette-pure (reuses §1's outline language, no new UI color pattern) and colorblind-safe by construction (shape-coded, never hue-coded, §4.6). The failing gate's reason string carries the specifics (UI Requirements); the outline carries only the binary. ⚑ **art-bible fix-pass item (design-review 2026-08-27): pin a minimum dash-segment/gap ratio and run §1's strip-to-black test on both ghost outline states at the locked 2 px outline weight and gameplay zoom** — solid-vs-dashed must survive the same silhouette discipline as final assets, or the invalid state is illegible in motion.

Ghost evaluation is a hot path: the client-side provisional F1 re-evaluation runs every frame in build mode (Rule 5) and must use non-allocating queries and cached buffers — no per-frame collection allocation (implementation note, re-review 2026-08-27).

**Grid: full overlay within placementReach, build mode only (ruling 2026-08-26).** The grid renders only inside placementReach of the player and only while build mode is active — legible for the multi-wall common case (Rule 5), with the diegetic-first cost (§7.1) bounded by the small radius and the mode gate. Never a full-canvas grid; never visible outside build mode.

**The placement thunk, made concrete (ruling 2026-08-26): squash-stretch + settle, paired.** On host-confirmed commit the structure lands 1 px high and eases down with a single impact-only squash-and-stretch beat (§5.3's reserved-for-impact budget — existing tech, no new VFX) plus the completion sound below. No particle flourish, no fanfare — "placement is an event" is delivered by motion and sound within existing budgets (a dust-puff was considered and rejected: the atmospheric-overlay budget is already spent on departure mist, §2, and §6.2 bans per-object bespoke VFX).

Inert-relic fallback (Edge Case 13, ruling 2026-08-26): a flat, undetailed grey silhouette box at the structure's original footprint **with a broken-outline edge accent — the ghost's invalid grammar reused** — so it reads as deliberately dead, never as a rendering bug. ⚑ art-bible fix-pass item: the relic treatment needs a proper section alongside the Camp→Keep gap; this is the floor, not the spec.

Silhouette-first test for BB assets (pass/fail): at solid black, 64–96 px per §8.A: a workbench never reads as a threat or a resource node; a wall segment reads as boundary, not prop, at gameplay zoom; storage reads as container, distinguishable from a station at a glance; and — BB's addition to the house pattern — the **base's cumulative silhouette stays legible as one shape** while placing (the whole-shape test made into an asset check): a screenshot of the base mid-build must not read as scattered objects on a field.

**Tide-tint: home structures are exempt by construction, not by carve-out.** §4.3's tint applies only to DriftLayer-tagged sprites on docked lands; home-island sprites are never tagged, and structures are home-island-only (Rule 3) — so structures are never tide-tinted, reaching combat sprites' outcome through category rather than exemption.

Displaced-player tell (Edge Case 21, second re-review 2026-08-27): when a wall lands on a player and the correction moves them, the move is a discrete step — always exactly one cell (Edge Case 21's ring check, fifth re-review 2026-08-27, CS-18; the prior "regardless of hop count" clause described the retired multi-hop search) — one settle beat on the moved sprite (the same impact-only squash-and-stretch budget as the placement thunk, reused, no new VFX) and no sound of its own — the wall's own thunk already marks the event. Silent snapping reads as a bug; a fanfare reads as punishment; one settle beat reads as "the wall got here first."

Audio: placement thunk host-confirmed with the commit (never predicted); demolish sound distinct and equally understated, host-confirmed; build-mode enter/exit cues brief and non-musical; **no ghost-move tick** (continuous cursor audio invites noise fatigue across multi-wall sessions). All understated per house voice — no fanfare anywhere.

Feedback authority split (audio/visual, extending the States table):

Effect Timing
Ghost outline state, grid display, build-mode enter/exit cueInstant — client-predicted
Placement thunk + squash/settle + structure appearance; demolish sound + removalHost-confirmed broadcast only

📌 *Asset Spec note: after this GDD is approved, run /asset-spec system:base-building.*

UI Requirements

Base Building contributes: the build-mode picker (the MVP set is three entries — a list, not a grid of cards; shows each structure's cost with a **live affordability read centred on the ghost's current position once build mode is active** — the same CARRY ∪ STORAGE-in-range-of-placement-spot predicate F1's commit gate evaluates, so preview and gate can never disagree on what is checked; they can still disagree on freshness (the picker reads the client's replicated copy, Rule 13), which resolves as Edge Case 11's honest no-op. The picker is visible only inside BuildMode (States table), so a ghost always exists while it is open and the read is always ghost-centred. *Fixed 2026-08-27 — the prior player-centred wording contradicted AC-19's placement-spot-centric draw; "never disagree by construction" narrowed to the predicate same day (re-review).* Greyed entries carry per-cause world-fact reason strings, crafting Rule 3's pattern); the ghost preview (validity outline + which gate fails as a short world-fact string — "too far away", "too close to the dock", "someone is standing there", "no way out", "not enough timber in reach", "blocked" — never a generic "invalid"). Reason-string precedence and stability (added 2026-08-27): when multiple F1 conjuncts fail at once, the displayed string is the **first failing gate in F1's stated order** (reach → bounds → overlap → occupancy → exit → terrain → socket → materials — ordered so that resolving an earlier failure is a precondition for the later check being meaningful: the materials gate's in-range STORAGE draw depends on the final spot, so it is judged last), held stable while that gate remains the first failure. That stability clause bounds flicker within one sustained failure only; flicker between gates at a cell boundary (e.g. noOverlap toggling at a wall edge) needs a **minimum display duration or snap hysteresis** — a named /ux-design deliverable, not implied by "held stable" (re-review 2026-08-27). Crafting's three reason strings need the identical contract; flagged there as a cross-system pattern gap. Also contributed: grid/snap feedback during build mode only (never outside it); the demolish prompt (target highlight + refund preview — showing what comes back is what makes full-refund legible, and AC-28b makes the preview a tested data contract; "empty the chest first" on a blocked storage demolish). **Demolish targeting is explicitly delegated to /ux-design** (a decision, not an omission — 2026-08-27): BB provides the candidate set (structures within interaction range) and requires gamepad parity, no hover-only path, and a deterministic disambiguation rule for dense clusters (suggested default: cursor-raycast on M+KB, nearest-in-reticle-cone on gamepad); the selection model itself is /ux-design's deliverable. **The gamepad stick-to-ghost transfer function** (cursor-velocity vs. cell-step, acceleration, deadzone) is likewise a named /ux-design deliverable — AC-8's device-parity claim covers snap outcome only, not input effort (OQ10). And **no build-log, no contribution stats, no per-player build history anywhere, ever** (Rule 7's no-ledger test restated as a UI ban, same shape as crafting's aggregation ban). Copy follows the world-facts tone rule throughout — a blocked placement is a fact about the world, never a scolding.

📌 UX Flag — Base Building: this system has UI requirements. In Pre-Production, run /ux-design for the build-mode/picker/demolish screens before writing epics; BB stories referencing UI must cite design/ux/, not this GDD. The ghost reason strings join crafting's three recipe reason strings and the harvesting no-op cues on the pre-playtest /ux-design list.

Acceptance Criteria

[UNIT] = EditMode NUnit over pure logic (placement/demolish legality gates F1–F2, station-presence predicates F3, socket exclusion F4, config validation, the material-softlock invariant) — the placement/demolish pipeline (Rules 6, 9–10, F1–F4) must be designed pure/DI-testable, no live NGO session required. **Purity mandate (added 2026-08-27, design-review BL-4): the pipeline consumes injected plain-data snapshots — terrain via an ITerrainQuery-style abstraction, the registry as a plain collection, player positions as a passed-in list — never live Tilemap/Physics2D/NGO queries. The production Tilemap/physics adapters are thin and are NOT unit-tested; only the pure predicates are. Without this factoring the [UNIT] label on F1–F4 is unachievable (EditMode has no reliable physics scene).** [INTEGRATION] = NGO runtime, Multiplayer Play Mode + Network Simulator — reserved for commit races under live timing, disconnect handling, and cross-system races with Crafting/Inventory. [MANUAL] = human feel/readability judgment, never automated — but scoped to a concrete, checkable claim. All criteria use locked baseline values (Baseline structure table, MVP) unless the vector specifies otherwise.

Structures as Data (Rule 1)

Anyone Builds, Always (Rule 2)

Home-Island-Only Building (Rule 3, F4)

Grid Placement, Snap-with-Nudge (Rule 4)

Build Mode Is Client-Local (Rule 5)

Placement Commit Is Instant and Atomic (Rule 6, F1)

Structure Anonymity (Rule 7)

Structure Lifecycle: Present Only (Rule 8)

Demolish Exists; Refund Is Total (Rule 9, F2)

No-Softlock Invariant (Edge Case 20)

Demolish Never Blocks on Use (Rule 10)

No Queues, No Batches (Rule 11)

Placing Storage Instantiates the Container (Rule 12)

stationPresent — Two Named Predicates (Rule 13, F3)

CI Gates — Live-Recomputed (Formulas section)

Placement Reach (Edge Case 12)

Registry / Persistence — Inert Relics, Current-Cost Refunds (Edge Cases 13–16)

Disconnects (Edge Cases 8–9)

Config Validation (load-time, fail loudly — Edge Case 17)

Scope Closures (Edge Cases 18–19)

Minimum smoke set (run first)

All five run as pure logic, headless, together under a minute — matching the sibling smoke-set shape and blast-radius framing.

Deliberately not criteria (and why)

Coverage note (revised 2026-08-27): Core Rules 1–13 and F1–F4 each have ≥ 1 criterion (Rule 1→AC-1–2; 2→AC-3; 3/F4→AC-4–7b — the orbit test's testable core at AC-7b, its unbounded half on the code-review / /architecture-review checklist, and Rule 3's shore-usability claim now validated at AC-63b (a previously undisclosed gap: only > 0 was checked); 4→AC-8–9b; 5→AC-10–12 incl. the exit half at AC-11b; 6/F1→AC-13–19 incl. noPlayerOccupancy and placerHasExit (AC-14b edge-of-region); the race criteria AC-20–23 are listed once, under Edge Cases 1–6 below; 7→AC-24–25; 8→AC-26–27; 9/F2→AC-28–35 incl. the refund-preview contract at AC-28b; 10→AC-38–39; 11→AC-40 (Rule 11a; 11b is /ux-design's); 12→AC-41; 13/F3→AC-42–44). Every row of Edge Case 17's validation table has a dedicated criterion (AC-55–64 + AC-63b). CI gates at AC-45–46 with named mutation-sensitivity tests AC-45b/46b. The material-softlock proof (Edge Case 20) at AC-36–37; spatial enclosure (Edge Case 22) is an accepted social closure with no automatable criterion, by ruling; occupied-cell placement (Edge Case 21) via AC-13/14's fourth conjunct, its free-cell check as a pure function at AC-14c, pin (iii)'s fresh re-check at AC-14e, and only its replication/write mechanics being netcode-ADR territory; self-enclosure (Edge Case 23) via the eighth conjunct placerHasExit in AC-14/14b (single-placement snapshots) and the maze-sequence boundary at AC-14d. Every row of Edge Case 17 now has a criterion for each clause (AC-63c covers row 10's shore clause). Rule 13's client-may-only-request contract at AC-39b. Anonymity (Rule 7) as reflection-based allow-list assertions at AC-24–25. Edge Cases 1–6 split UNIT/INTEGRATION per whether live timing is load-bearing (AC-20–23 incl. the AC-20b/20c split, AC-34–35); Edge Cases 7–10 at AC-12, AC-53–54 (now mocked-disconnect constructions, relabelled [UNIT]; Edge Case 10 — host-quit — is a stated closure with no BB-specific criterion, matching crafting Edge Case 6's posture). Edge Case 11 is a closure covered by AC-17's re-sample claim; Edge Case 12 → AC-47–48c (a line previously missing from this note); Edge Cases 18–19 at AC-65 and AC-37.

Open Questions

# Question Owner Target
1Camp→Keep growth trigger: art bible §6.1 describes the stages but nothing defines what advances a structure (material threshold? manual upgrade? pure Tier 1 scope?). MVP never leaves Camp — must be answered before Tier 1 building scope beginsArt-bible fix-pass + Tier 1 building passTier 1
2socketNoBuildRadius at Tier 1 socket counts: does 4 u hold when 3–4 sockets shrink the placeable shore? Owner confirmed 2026-08-27: LDU quick-spec Rule 8 authors socket layout and names this re-checkLDU (Rule 8) + Tier 1 passTier 1
3Inert-relic proper visual design (floor spec'd here: grey silhouette + broken-outline accent)Art-bible fix-passPre-Production
4The accepted MVP home-facing dead zone (Player Fantasy honesty clause; crafting OQ9's negative ruling): does it actually bite with the real friend group, or does base-up + hauling absorb it? If it bites hard, Tier 1 building scope is the pre-agreed answer — never MVP filler structures. ⚑ Producer (CD A1): "Tier 1 building scope" is an unpriced schedule commitment — it needs a sizing note before the MVP playtest, so if this fires the answer is a decision, not a discovery. Sizing note (2026-08-27, Technical Setup gate — PR concern 3; provisional, owner producer): Tier 1 building scope ≈ 2–3 weeks part-time — structure variety (3–4 new StructureDefs incl. a door/gate, ~3 days), rotation (schema + snap + ghost, ~2 days), visible age (createdAtCycle + wear-stage sprites, ~3 days incl. art), move verb (atomic demolish+place, ~1 day — Rule 9's revisit), Camp→Keep trigger design (OQ1, ~2 days), plus per-structure art. First-to-slip inside it: visible age. If OQ4 fires at playtest the decision is "commit these ~2–3 weeks in Tier 1 or accept the dead zone" — priced, not discoveredMVP playtest + producerMVP playtest
5Perimeter pacing: does the 50-segment / 2–4 session estimate match real group behavior? (The perimeterSessions band 2–4 is the guard, not the target experience; anchor provisional pending the spatial budget)MVP playtestMVP playtest
6~~gridCellSize / snap-tolerance constants: ghostSnapResult makes snap testable, but the cell size (1 u = 32 px?) and nudge tolerance are unpinned — pin before AC-8 can run~~ RESOLVED 2026-08-28 — ADR-0003: 1 cell = 1 world unit = one 32 px terrain tile at PPU 32 (the camera's 64 px/u is a 2× display scale, not a second PPU); Cell(int X, int Y), Cell.FromWorld = floor, Cell.Center = +0.5; every range in this GDD is already in cells; AC-14b's ≤ 3 u fixture constraint holds. Nudge tolerance stays an /ux-design deliverable (Rule 11b / OQ10)~~Implementation + /asset-spec~~ Resolved (ADR-0003)Done
7~~Save quick-spec must adopt the Edge Case 13–16 reconcile posture and list BB as dependent~~ RESOLVED 2026-08-26 — design/quick-specs/save-persistence-2026-08-26.md Rule 5 (permanent-reconcile stage) + Rule 6 (two-class rule) adopt the posture verbatim~~Save quick-spec~~ ResolvedDone
8Staleness falsification (CD C2 — distinct from OQ4): does the base still read as earned-static (pride) rather than frozen (wallpaper) by the end of MVP playtesting? Falsification signals: any player describing the base as "done" or "background", navigating past it without looking, or the group ceasing to comment on it entirely. A group can have plenty to do on the tide while the base has stopped meaning anything — OQ4 would return "no bite" while the Pillar 2 fantasy quietly failed. **Pre-agreed response if falsified: visible age / structure variety is the Tier 1 lever (the deferred walls-as-memory fantasy) — never activity generation; staleness is a reading problem, not a doing problem, and treating it as a doing problem is how filler structures get in through the back door**. Compounding note (re-review 2026-08-27): OQ9's missing arrival beat and OQ1's Camp→Keep gap both work against this bar — if OQ8 falsifies, check them as contributing causes before reaching for Tier 1 content levers.MVP playtestMVP playtest
9The arrival beat (design-review 2026-08-27): the fantasy's anchor moment — crossing the shore threshold onto home ground — has no implementing mechanism in any system: no threshold feedback, no arrival cue, nothing fires on the crossing. Placement got a full spec (thunk, AC-48b); arrival got none, and a [MANUAL] sign-off cannot evaluate a moment nobody built. Which system owns the beat (ambient shift? HUD suppression? light warmth?) — flagged, deliberately not invented hereBB + /ux-design + art-bible fix-passPre-Production
10Gamepad stick-to-ghost transfer function (cursor-velocity vs. cell-step, acceleration, deadzone, snap-boundary hysteresis): unspecified, and it — not AC-8's snap purity — determines whether gamepad build mode is viable (outcome parity ≠ effort parity)/ux-designPre-Production
11Post-saturation haul tension (economy review 2026-08-27 — distinct from OQ4 and OQ8): after ~session 3, timber/fiber/sinew have no remaining sinks (walls one-time, gear indestructible, repair free, chests cheap) while the harvest faucet recurs unbounded — does bank-or-push tension survive sink saturation with the real group? Also in scope (re-review): chest count is unbounded, 8 fiber each, fully refundable — the stated bet is that footprint, walk distance, and session-one fiber opportunity cost bound it in practice; if the group carpets the base in chests turn one and banks everything before every departure, that is this OQ firing, not a separate finding. A group could answer "no bite" on OQ4 and "still earned-static" on OQ8 while the haul loop quietly dies. Any lifetime-sink-vs-faucet invariant belongs to a cross-system economy pass, not this GDDEconomy + producerMVP playtest
12From /ux-design build-mode (2026-08-31): (a) the ghost reason-string family gained two instances — bounds "past the island's edge" (off-island: "not home ground", per AC-4 withinBounds) and terrain "not buildable here" — authored under this GDD's retitling grant; adopt into UI Requirements' list. (b) Rule 11b's repeat gesture is hold-to-repeat (pattern #31), drag-a-row rejected — Rule 11b's own prose ("AC-48c's playtest must include a drag-row segment") and the drag-row reasoning in the EC23 / self-sealing passages should be retargeted to "a held-repeat row"; AC-48c's current text carries no row segment. (c) Mode entry off-island is legal (every cell fails bounds) — a UX ruling filling a GDD silence; confirm or add a location gate to Rule 5.game-designerbefore the first BB story