# Robot Crossing — implementation plan

8 September 2026. Companion to [GAME_DESIGN.md](GAME_DESIGN.md). The user subsequently authorised beginning the core game; the milestones below distinguish that initial slice from the complete release.

**Current checkpoint:** the village now spans nine islands. Beacon Coast and Tinker’s Scrap Yard add Wick and Rivet, clue hunts, a light-routing challenge, a crane counterweight challenge, two furniture gifts, six discoveries and shared animated repair outcomes. There are five adventure repair arcs alongside Bloop, twelve finds and ten regional music themes including interiors. Five generated 256px textures are reused. The 69-test suite and browser checks cover reachability, both new repair loops, phone dialogs, rewards, reload and late peers. See [appinfo.md](appinfo.md) for current scope and limits. The remaining cast, Puzzle Ruins, multi-tier cliffs, progression gates and production persistence milestones remain planned.

The scenic-layout pass adds winding village and district loops, shared irregular shoreline geometry/collision, cottage gardens, a picnic meadow, an orchard, listening stones and a harbour lookout. The pocket map uses the authored routes and supports destination/home tracking. Existing saves remain unchanged; layout revision 3 requires all peers to refresh. The high multi-tier cliffs and expanded quest districts in milestone D remain future work.

The Garden & Wildlife chapter adds two northern islands, six moving small animals, three seed varieties in four shared beds, three regrowing mowing patches, two fishing spots with six species, and a personal nature journal. Activity timestamps and rewards pass through deterministic coordinator transactions; legacy saves gain a validated optional nature extension. Content revision 4 requires every peer to refresh. Production clock authority and history compaction remain future work.

The Sunpatch Woods scenery pass adds a seventh island south of Forest Grove, a connected woodland loop, two tracked scenic stops and a detailed picnic clearing. Four tree forms and low forest plants use deterministic placements; canopy fading, solid trunks/furniture and clear trails support exploration. Warm sun, cooler fill and four batches of soft authored ground shadows add depth without a real-time shadow pass or new textures. Content revision 5 keeps peers on the same collision layout. Actual-phone GPU profiling remains a release check.

The subsequent graphics pass adds live sun shadows, selective Phong highlights, animated water and shoreline foam, 935 instanced grass tufts and quarter-resolution bloom. Automatic/Rich/Light settings persist locally; automatic reduction and reduced-motion support are implemented. This pass changes no collisions, shared-state schema or content version. The earlier woodland contact shadows remain as a lighter base beneath the live lighting. See appinfo for current rendering costs and physical-device checks still required.

## 1. Delivery approach

The Beacon & Salvage chapter uses content revision 6. Three new boardwalks connect the two eastern islands and create a loop through Sunny Beach and Repair Quarter. Original collection bonuses remain intact for legacy saves; new sets pay independently. Tower/cab geometry joins the existing occlusion system, static detail is batched per district, and only the repaired beacon and crane mechanism animate. The new regions use the current flat walking layer; enterable tower floors and vertical terrain remain future work. Measured desktop test views used 100–116 draw calls and approximately 160–169k triangles with rich shadows/bloom; actual-phone profiling remains outstanding.

Prove an attractive, playable village corner before producing the whole island. The initial core should already connect exploration, a puzzle, a repair, currency, a house, local saving, and another player. Later milestones expand and harden those systems.

| Milestone | Deliverable | Exit evidence |
| --- | --- | --- |
| A — Core scene and complete small loop | Authored plaza/garden, cute articulated player and Bloop, movement, fixed camera, dialogue, component puzzle, repair reward, four cottage plots, simple furnishing, generated music, local save, initial room sync | Phone layouts work; complete the repair and purchase loop; two browsers agree on ownership and repair state |
| B — Multiplayer and recovery | Full transaction acknowledgement, orderly coordinator handoff, snapshot chunking, conflict recovery UI, duplicate-profile handling, proximity chat, eight-player validation | Concurrent purchase/reward tests; disconnect/rejoin tests; conflicting save preservation; no silent rollback |
| C — Production world and asset pipeline | Finished reference-based models, four initial districts, reusable prop kit, texture atlas processing, full occlusion/interior system, source/licence manifest | Representative real phones meet frame/memory/download budgets; all assets pass model and texture checks |
| D — Character and quest content | Twelve NPCs, all eight districts, twelve main and twelve follow-up jobs, four puzzle families, newcomer commissions, hints and friendship | Every quest solvable solo; newcomers can buy a home in an advanced village; content validator finds no broken references |
| E — Housing and social completion | Sixteen plots, forty furniture designs, upgrades, wall/tabletop placement, path validation, visit permissions | Simultaneous visitors/editing; impossible placements rejected; owner absence does not lose a home |
| F — Release polish | Final audio, accessibility, performance tuning, onboarding, save migrations/export/import, full guide | Device/browser matrix, sustained multiplayer session, corruption and storage-failure tests, audiovisual review |

Dependency order: A → B; production asset work can proceed after A's visual proof; D and E depend on B and C; F verifies the assembled result. Each milestone ends with a playable checkpoint, evidence, and a concise report of remaining limitations. No fixed calendar estimate should precede the first real-device and asset-production measurements.

## 2. Repository and dependency decisions

Keep changes inside `src/main/apps/app/robot-crossing/`, apart from test screenshots/scripts under the existing workspace `tmp/`. Finished versioned assets can later move to `src/main/immutable/app/robot-crossing/`. Preserve the inspiration images and neighbouring apps' existing changes.

The repository already provides `/assets/lib/three.js-r146/three.min.js` and a matching `GLTFLoader.js` under `src/main/immutable/assets/lib/three.js-r146/`. Reuse that matched version for the core. It supports the required camera, geometry, textures, materials, and instancing. Modern JavaScript syntax does not require upgrading the rendering library.

Pin the version explicitly. Current Three.js documentation describes a newer API; for example, r146 uses texture `encoding` and renderer `outputEncoding` where later releases use colour-space properties. Current WebGLRenderer has dropped WebGL 1 since r163, so upgrading also changes compatibility. Any future upgrade must be app-local, version-matched with loaders, and justified by a measured need. [Three.js renderer documentation](https://threejs.org/docs/pages/WebGLRenderer.html)

No frontend framework, physics engine, navigation package, live AI service, or backend change is required. Use native Pointer Events, Web Audio, DOM UI, and localStorage. Standalone scripts may use ES6; expose the networking entry point explicitly as `window.robotCrossing`. Load `/assets/core/comms.js` first, install hooks, then call `rc.connect()`.

Suggested file boundaries, introduced only as the implementation needs them:

```text
robot-crossing/
  index.html                 DOM interface and loading state
  style.css                  responsive HUD, dialogs and controls
  script.js                  startup and game orchestration
  state.js                   data validation and deterministic transactions
  world.js                   authored scene, robots, collision and visibility
  network.js                 room coordinator, intents and snapshot recovery
  audio.js                   music sequencing and sound playback
  content/                   later NPC, quest, furniture and district definitions
  assets/                    selected runtime textures and model references
  appinfo.md                 actual implemented features and user guide
  GAME_DESIGN.md
  BUILD_PLAN.md
  state.test.cjs             behavioural transaction/puzzle tests
```

The coordinator, transaction rules, and renderer are separate enough to test without WebGL. A large generic entity framework or editor is unnecessary for a single authored village.

## 3. Scene, camera, and interaction

### Scene composition

Use metres with Y up. Static district data defines ground surfaces, props, interaction points, colliders, spawn points, doors, and navigation connections. Reuse geometry/materials and batch distant repeated trees, rocks, fences, and flowers. Build the first robot from bevelled primitive forms with separate articulated joints, then refine the model against generated references.

Terrain uses a small set of walkable height tiers with explicit ramps/stairs. Collision uses player circles against simple XZ circles/rectangles and a floor/layer identifier. Bridge surfaces and the terrain beneath must have distinct navigation layers; an XZ-only collision test cannot distinguish them. Use a spatial grid for nearby candidates. Avoid a general rigid-body simulation.

Run movement on a fixed simulation step, capped catch-up after suspension, with visual interpolation in requestAnimationFrame. Player heading follows movement; the camera never adopts that heading. Local walking responds immediately; remote movement is interpolated from timestamped position packets. Gameplay checks interaction distance and zone against the coordinator's recent validated position.

### Fixed orthographic camera

Use `THREE.OrthographicCamera`, a diagonal azimuth of about 45° and elevation around 35.26°. Orthographic projection preserves apparent object size with distance. Translate camera and target together; smooth follow with a time-based exponential response, not frame-dependent easing. [Three.js orthographic camera](https://threejs.org/docs/pages/OrthographicCamera.html)

Derive the frustum from the actual unobscured canvas area. Target roughly 16–22 visible metres across a phone portrait scene and 24–32 in landscape, then tune character readability. Portrait may show less horizontal world; do not simply squeeze a landscape view into it. Keep camera pitch/yaw fixed during orientation changes, dialogue, entry, and furniture editing. Clamp follow near the island boundary and favour the area ahead of the player slightly.

Translate joystick screen X/Y through the camera's ground-plane right/forward vectors. Normalise diagonals, apply a dead zone, capture the active pointer, and release on cancel/blur/visibility loss. Interact uses range, zone, facing, line of sight, and a stable target preference. UI input never propagates into walking.

### Occlusion and interior cutaways

Register visual occluder groups separately from collision geometry. Test line segments towards the player's feet, torso, and head using the orthographic viewing direction. Use a broad-phase screen/bounds test before raycasting a small candidate set; a perspective-style single ray from the camera position is incorrect away from the view centre.

Any blocking canopy, building section, or tall furniture group transitions to approximately 20–30% opacity over 150–250 ms. Keep it faded for a short grace period after it stops blocking to prevent flicker. Fade all relevant blockers, not only the first intersection. Restore their original material properties on clearance.

Give independently fading groups their own cached material instances. Changing a shared material must not fade every tree or house. Translucent groups need deliberate depth-write and sorting treatment; separate opaque trunks/base walls from fading canopies/upper walls. For mass instancing, either implement per-instance fade or keep the few nearby occluders in a separate batch. Do not clone materials each frame.

Buildings have separate roof, front-wall, rear-wall, floor, and furnishing groups. A validated door transition sets the player's zone to `house:<plotId>` and loads that interior. The transition fades the exterior roof, reveals the indoor scene, and cuts front upper walls. Peers see only occupants in their own zone. Exterior viewers can retain a closed roof because visibility is camera-local. Interiors share the same camera direction and can unload the exterior renderer content for performance.

Core A may use small interiors at their exterior coordinates, provided the roof and front walls demonstrably reveal occupants. Production C changes to independently loadable zones before large houses and all sixteen plots are added.

Fallback on a weak device: hide a blocking group after a short fade and show a simple player marker. Never require expensive full-screen outlines or blur. Verify repeated entry/exit, edge-of-roof positions, adjacent canopies, furniture blockers, and multiple players on opposite sides of a wall.

## 4. Asset production and audio

### Imagegen → usable 3D robots

Imagegen produces 2D concept art and textures. A single image does not contain clean topology, an unseen back, UVs, joints, or animation. The default production route is explicit modelling from those concepts; an external image-to-3D service is optional and not assumed available.

1. Select each robot's supplied reference and generate consistent front/side/back model sheets with plain backgrounds and relaxed A-poses.
2. Resolve inconsistent proportions between views. Freeze silhouette, palette, accessory, joint positions, and the missing-part attachment.
3. Build a shared low-poly robot body kit in Three.js geometry or an offline modeller. Distinct heads, ears, aprons, hats, and antennas provide character identity.
4. Prefer rigid articulated groups for these mechanical bodies: head, torso, upper/lower limbs, hands, feet, and component sockets. Skinning is optional, not necessary for good robot animation.
5. Use shared shell materials and a small face atlas. Blink, happy, curious, concerned, and speaking expressions change UVs or simple face elements; never redraw large text canvases every frame.
6. Create idle, walk, wave, talk, repair, celebrate, sit, and pre/post-repair activity cycles. Drive deterministic timing from state; interpolate remote playback.
7. If authored outside code, export GLB with transforms applied, correct scale, named pivots, and separate detachable components. Blender's glTF exporter supports the relevant mesh/material/animation workflow. [Blender glTF documentation](https://docs.blender.org/manual/en/5.1/addons/import_export/scene_gltf2.html)
8. Inspect the actual exported render from every movement heading. Check feet, pivots, transparent face edges, material count, attachment fit, and phone-scale expression readability.

Initial budgets: 1,500–3,000 triangles for a close robot, 500–900 for a distant variant; aim for no more than 6–8 draw submissions per visible articulated robot through sensible part/material grouping. Exact counts need measurement with the chosen articulation approach. Reuse the base model; do not load one high-resolution texture per NPC.

### Texture workflow

Generate small sets of flat, evenly lit painted albedo materials. Required starter surfaces: grass, sandstone path, timber, roof shingles, plaster, cliff stone, sand, and interior floor/rug patterns. Faces, flowers, leaves, and icons use separate atlases where necessary. Use geometry and tint for large colour areas.

Generate at a useful source resolution, visually inspect, then make runtime derivatives: 128–256 px surface tiles, 512 × 512 core atlases, at most 1024 × 1024 for the complete environment or shared character atlas. WebP is suitable for download size; PNG is a fallback where alpha quality demands it. Pack padded tiles and inset UVs to prevent mip bleeding. Inspect repeat seams after reduction; a generation prompt does not guarantee seamless pixels.

Do not tile an atlas subregion by simply enabling RepeatWrapping on the entire atlas. Repeat individual extracted tiles, or use repeated geometry/UV cells with padded atlas coordinates. Test all mip levels for seams. Keep colour textures in the version-appropriate sRGB path; data textures stay linear.

Download size and GPU memory are different budgets. A conventional uncompressed RGBA 1024² texture with mipmaps uses about 5.33 MiB; 512² uses about 1.33 MiB. WebP compression alone does not reduce that decoded cost. [Three.js texture memory guidance](https://threejs.org/manual/en/textures.html)

Keep generated masters, prompts, chosen references, and derivative settings alongside a manifest. Production assets must live in the workspace, not only a tool's generated-image directory. Give immutable assets versioned names. Do not introduce Draco/KTX2 decoder dependencies in the initial core; reconsider only after measuring the uncompressed asset set.

### Music and effects

Core A generates a cheerful original arrangement in Web Audio from authored note sequences, with bounded polyphony and scheduled notes using AudioContext time. Reuse short synthesised voice buffers where practical, ramp gain to avoid clicks, and stop scheduling while hidden. A look-ahead scheduler can run around every 25 ms and queue roughly 100 ms of audio; the audio clock, not rendering frames, determines note timing.

The current regional-music pass implements eight original 16-bar scores, one for each island plus cottage interiors. `music.js` holds deterministic scores and shoreline-based location selection. `audio.js` checks a short scheduling horizon every 30ms, holds area changes for 800ms, and crossfades at most two mix buses over 2.4 seconds. Retired buses disconnect, hidden tabs suspend, and missed scheduling windows are skipped rather than replayed. Complete score renders and live location/mute/refresh tests cover the implementation; physical-speaker listening and longer arrangements remain later checks.

Limit simultaneous effect voices to about 12–16, reduce distant sounds, and avoid playing footsteps for every remote player at full volume. Keep music/effects volume independent and persist mute preferences. Unlock/resume audio from Start or another explicit gesture and surface failure without blocking the game.

Production effects are shortlisted from the three CC0 Kenney packs linked in the game design. Download only selected clips, retain the actual package licence and file provenance, audition on phone speakers, trim silence, and normalise perceived loudness. Generated placeholder effects are labelled as such until that sourcing step is complete. No online audio generation service is available in the current tool set; the native composition route avoids that dependency.

## 5. Multiplayer and persistence

### What RCWeb supplies

The local guide and `comms.js` confirm that room peers receive JavaScript function calls through a WebSocket relay. `rc.onConnected()` is the first safe point to use `rc.room` and `rc.client`. `rc.sendFunctionCall` serialises arguments but returns no application acknowledgement. The relay supplies neither a database nor game transactions nor authenticated player ownership.

`rc.onUpdateClients` currently receives an array of client IDs from `Room.java`; it does not include app identities. Combine that listing with Robot Crossing hello/heartbeat packets to identify game peers. It is periodic membership evidence, not an instantaneous failure detector. The animal coordinator in astra-craft is a useful example for cosmetic simulation, but its timeout election is insufficient by itself for currency and house ownership.

### State boundaries

| Data | Owner / authority | Persist? |
| --- | --- | --- |
| Island seed/version, repaired NPCs, world mechanisms | Room coordinator | Yes |
| Player ID, wallet, quest claims, owned items | Room coordinator validates player intents | Yes, in village snapshot |
| Plot ownership, house upgrades, furniture | Room coordinator validates owner intents | Yes |
| Position, heading, active zone, animation | Player sends; coordinator validates important interactions | Last safe position only |
| NPC movement and shared daylight | Coordinator simulates; peers interpolate | Coarse checkpoint |
| Occlusion, camera, UI, audio preference | Local browser | Preference only |
| Chat and typing | Transient nearby events | No |

Persistent `playerId` is separate from `rc.client`, which identifies a connection. Use a random ID stored locally, with room-specific profile ownership. Display name changes never transfer property. Two tabs claiming the same player must not simultaneously spend: make the second read-only or explicitly create a new guest profile. Cross-device return requires a profile/village export/import or a future account system; sharing a room URL does not transfer browser identity.

### Ordered transactions

Clients send **intents**, such as `collectPart`, `operatePuzzle`, `completeRepair`, `buyHouse`, `buyItem`, or `placeFurniture`, to the active coordinator. That browser validates the current state, zone, distance, balance, item ownership, object version, and request identity, then publishes an ordered accepted operation or a specific rejection.

Use an envelope containing `protocolVersion`, `worldId`, `epoch`, `revision`, `senderClientId`, `playerId`, `operationId`, `type`, and bounded payload. Keep operation IDs stable through retries. Retries and deduplication are deterministic code with finite limits. An old-epoch queued command is rejected or explicitly revalidated; it is never blindly applied after reconnection.

One repair transaction consumes the component, restores the NPC, records eligible claims, and credits their rewards together. One purchase transaction debits the wallet and assigns the plot/item together. Never merge wallets by timestamp or add balances from divergent snapshots. Keep durable reward claim IDs and action sequence watermarks when compacting the operation history, so old retries cannot pay twice.

The application validates its own messages, but RCWeb executes room peers' JavaScript. This remains a trusted-friends room model, not a secure competitive economy or private account system. Application checks prevent ordinary bugs and accidental invalid commands; they cannot make the existing transport cheat-proof.

### Join, snapshots, and presence

1. Connect, announce protocol/version and local save identity, then collect compatible peers and save heads during a short settling period.
2. If a compatible coordinator exists, request its snapshot. A new room creates a `worldId`; never silently combine two independent worlds that reused the same room code.
3. Receive a snapshot at revision R, verify schema/checksum and chunk completeness, then apply accepted operations newer than R in order.
4. Only enable mutations after synchronisation. Send an acknowledgement and the local persistence result.
5. A gap or checksum mismatch requests missing data/resnapshot rather than guessing.

Use bounded snapshot chunks, initially around 12 KiB of serialised text, after confirming transport behaviour. Pace them so movement and UI traffic continue. Cap pending bytes/chunks and expire incomplete transfers. Only the selected source responds; avoid every peer broadcasting a full save to every newcomer.

Presence target: 8–10 updates/second while moving, 1/second idle, quantised positions/headings, sequence numbers, and a maximum extrapolation of 200 ms. Remote rendering buffers roughly 100–150 ms. Stale avatars settle/fade instead of running forever. NPC batches can update around 5 Hz. Chat checks same-zone proximity; permanent state is unaffected by chat packet loss.

Do not call `rc.sendFunctionCall` with movement or new purchase commands while disconnected, because `comms.js` queues sends and flushes them on reconnect. Gate sending on socket readiness and application sync state. Receiver-side epochs, sequences, and operation IDs still guard the race where a disconnect occurs immediately after that check.

### Coordinator loss and save conflicts

Prefer a visible, connected, synchronised browser as coordinator. Core A can implement deterministic selection among known peers and freeze transactions whenever authority or save history is ambiguous. It must label this an initial recovery mechanism rather than a guarantee of partition-safe consensus.

For B, add an orderly handoff: pause new transactions, replicate the current head, get acknowledgements from the successor, publish a new epoch, and resume. On abrupt disappearance, wait for server membership and heartbeat evidence, exchange saved heads, and continue only when histories are compatible prefixes and the successor has the accepted state. Preserve the old coordinator's epoch in history to reject stale traffic when it returns.

If histories diverge, pause durable mutations and show a recovery choice describing the available branches: saved time, revision, residents, houses, and repair count. Export/preserve both before choosing one; report exactly which newer actions are excluded. Do not invent an automatic money/property merge. A history hash detects divergence; it is not authentication or consensus.

Without a persistent authority or quorum protocol, browser-only peers cannot promise automatic conflict-free progress through every network partition, suspended tab, or simultaneous cold start. The chosen product tradeoff is to pause mutations or recover explicitly. Ordinary isolated browsing may continue, but it does not mint rewards or buy shared property. A deliberately separate offline village could be a future feature with its own world ID and no automatic economy merge.

### localStorage policy

Save only compact game data, not textures, GLBs, audio, raw chat, or per-frame positions. Suggested keys include a profile/settings record and room/world-scoped current and previous snapshots. Use schema and content versions, world ID, revision, lineage identity, saved time, and integrity checksum. Keep both current and previous validated generations; set the new complete serialised record before updating any active pointer.

localStorage is synchronous and origin-scoped. Coalesce routine saves and avoid frame-loop writes; critical transaction checkpoints should remain small. A browser visit on another hostname, scheme, port, or device does not automatically see the same stored data. [MDN Web Storage](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)

Save status distinguishes synchronised in memory, saved on this browser, and copied to another browser. An acknowledgement from the relay is not proof of a persisted game save. Handle quota/security errors and corrupted JSON visibly, preserve the previous valid data, and offer export/retry. Never overwrite an unrecognised future schema with an empty new village.

A reconnect takes compatible state from peers. If everybody closes the game, the village persists only in browsers that saved it. A fresh browser cannot recover a village from the RCWeb room code alone when those browsers are absent. A stale lone browser can resume its last copy; if a newer divergent copy appears later, the explicit conflict flow applies. This limitation belongs in the actual user guide.

Target a current snapshot below 250 KiB and the app's active-room storage below 2 MiB including backups and a bounded operation tail. Other RCWeb apps share origin quota, so these are targets, not reserved capacity. Do not automatically delete old villages to create space.

## 6. Mobile budgets and rendering tiers

These are acceptance targets to measure, not performance claims about unbuilt code.

| Budget | Core / low tier | Standard tier |
| --- | --- | --- |
| Sustained frame rate | Stable 30 fps on selected older/mid-range phone | Aim for 60 fps on recent phones/desktops |
| Frame time | 95th percentile below 40 ms during walking after warm-up | 95th percentile below 22 ms where device permits |
| Device pixel ratio | Start at 1, adapt between 0.75 and 1.25 | Cap around 1.5 |
| Visible triangles | ≤100,000 | ≤200,000 |
| Draw submissions | Aim ≤100; hard review at 140 | Aim ≤160 |
| Resident texture allocation | ≤24 MiB | ≤48 MiB |
| Core startup download | ≤4 MiB including engine and first scene | Same essential payload; stream later zones |
| Complete optional asset set | Target ≤15 MiB | No requirement to download all on entry |
| Decoded sound buffers | ≤8 MiB | Stream longer recordings; generated music remains small |
| Village save | ≤250 KiB current snapshot | ≤2 MiB total active-world footprint |

Use one hemisphere light and one warm directional light, mostly matte Phong materials with selective glossy highlights, vertex shading, and cheap contact-shadow discs. The current rich tier uses one bounded 1024/2048 shadow map, updated at up to 10 Hz while idle, and quarter-resolution LDR bloom. Light uses a 512 map updated at 2 Hz while idle, fewer grass instances and no bloom. Both tiers refresh shadows every rendered frame when a local or visible remote player is moving or settling after a stop. Neither tier uses SSAO, depth of field or reflection render targets. Validate real low-powered phones before deciding whether an additional tier with no live shadows is needed.

Water is a tinted low-poly surface with a slow repeated ripple pattern and sparse foam geometry, not the complex reflected sky/water shader from astra-craft. Keep translucent overdraw small. Animate only nearby visible props; lower far NPC update frequency. Dispose of geometry, materials, and textures when replacing a district, while retaining intentionally shared resources.

Sample frame times with hysteresis before lowering pixel ratio or decoration density. Avoid oscillation and do not upgrade quality during an active drag or transition. Background tabs stop rendering/audio scheduling and give up coordination safely. Handle WebGL context loss with a visible recovery overlay and rebuild from game state without awarding anything again.

## 7. Verification plan

| Behaviour | Required check |
| --- | --- |
| Movement | Screen-relative directions; normalised diagonal speed; no walking through trees/walls, drifting after pointer loss, or jumping after resume |
| Camera | Fixed yaw/pitch in all gameplay; consistent scale; orientation changes preserve state and usable controls |
| Puzzle | Valid rotation/path logic; incomplete states rejected; hints solve a reachable state; one reward despite repeated completion |
| Repair | Part and reward change atomically; missing part is visible before and attached after; late join sees the restored activity |
| Ownership | Two requests for one plot produce one owner and one debit; rejected purchase leaves the other wallet unchanged |
| Furniture | Owned item required; invalid overlap/door blockage rejected; move/store/return does not duplicate or destroy items |
| Social | Two to eight independent browser contexts; house visits; same-zone nearby chat only; typing disables movement |
| Reconnection | Loss during puzzle, purchase, and house placement; duplicate/out-of-order/stale epoch packets; coordinator closure; late join |
| Save | Reload exact accepted state; changed display name retains house; same-profile second tab; corruption, quota failure, old/new schema |
| Visibility | Two overlapping trees; behind every building edge; repeated house entry/exit; roof and front walls reveal player/furniture |
| Audio | Start gesture, mute persistence, resume, zone change, no doubled jingle, 10-minute listening test for fatigue/clicks |
| Performance | Ten-minute traversal on actual phones; busiest plaza with eight avatars; no increasing resources after repeated interior visits |

Use pure behavioural tests for transactions, puzzle validation, save parsing, ordering, and collision. Browser tests should use separate contexts for distinct people, plus a deliberate same-profile-tab test. Simulate network delay/loss in the test transport where possible; do not require backend modifications. Take screenshots at 390×844, 450×960, 844×390, 960×540, 1280×720, and representative 1080/1920 orientations. Headless timing is diagnostic, not evidence of real smartphone GPU performance.

Use the live RCWeb server at `/robot-crossing/?r=robo-001` with the same valid room in each peer. App-only edits require refresh, not Maven compilation or server restart. A phone needs the server's reachable LAN/host URL; `localhost` on that phone refers to the phone itself.

## 8. Principal risks and review points

| Risk | Mitigation / decision point |
| --- | --- |
| Detailed concept illustrations produce an unreadable or slow game | Approve the actual phone-scale core scene before multiplying assets |
| Image-to-3D assumptions hide major production work | Use explicit model sheets and a reusable articulated kit; optional external conversion only after one asset passes budgets |
| Host loss duplicates rewards or loses purchases | Ordered idempotent transactions, persistence acknowledgement, lineage checks, visible conflict recovery; prove B before content expansion |
| Fixed camera hides interactions | Author paths/doors for the angle; tag occluders; test all approach directions |
| Late arrivals miss earning opportunities | Per-player tutorial and equivalent service commissions independent of completed world repairs |
| Mobile browsers suspend coordinator/audio | Visibility handling, bounded catch-up, handoff/recovery, gesture-based audio resume |
| localStorage cannot act as a cloud save | Small redundant browser snapshots, export/import, explicit save status and guide |
| Content grows faster than the tested systems | Twelve-character/forty-furniture release cap; expand through measured milestones |

The initial implementation should leave an honest `appinfo.md` listing the playable core and the remaining milestones. A beautiful preview is not evidence that recovery, all districts, or the final asset pipeline are complete.
