Exact source used by this build. SHA-256: 64f853cd295d8c37f33a90eeda0f1f59506893c8e1a862c58bc1a0c992772a62.
1 # Architecture
2
3 A private, two-player, Stardew-Valley-style farming and life sim with entirely original content. Played on iPhones (Capacitor app via TestFlight; also playable in any browser). One shared world lives on a Cloudflare Durable Object.
4
5 ## Stack (decided)
6
7 - **Language:** TypeScript everywhere, strict mode. Package manager and test runner: **bun** (workspaces, `bun test`).
8 - **packages/sim:** pure, deterministic game rules. No DOM, no Phaser, no Cloudflare APIs, no `Date.now()` or `Math.random()` inside rules: time and RNG are passed in (seeded RNG, e.g. a small PCG/mulberry32 implementation). Used by both client (prediction, UI) and server (authority).
9 - **packages/content:** all game data as JSON (items, crops, fish, NPCs, dialogue, maps' metadata, recipes, shops), with zod schemas and a validator script. Code never hard-codes content; it reads ids from here.
10 - **apps/server:** Cloudflare Worker + one Durable Object class `FarmWorld` (SQLite-backed, free plan). WebSocket Hibernation API. Deployed with `wrangler`. Must stay within the Workers Free plan (no paid features: no KV-backed DOs, no Queues, no paid add-ons).
11 - **apps/client:** Vite + Phaser (current stable major on npm) + TypeScript. Tile maps authored in Tiled JSON format. Touch-first controls (virtual joystick, context action button, tap-to-target), keyboard for desktop testing. Wrapped with Capacitor for iOS later.
12 - **Hosting cost:** $0. Only ChatGPT/Codex credits are spent.
13
14 ## Authority and networking
15
16 - Server is authoritative for world state (tiles, crops, objects, chests, money, time, NPC state) and player state (inventory, skills, energy, relationships).
17 - Client sends **intents** over one WebSocket: `move` (position + facing, client-predicted, server checks speed/collision bounds), `useTool {tile}`, `interact {target}`, `placeItem`, `craft`, `buy`, `sell`, `sleep`, `chat`, etc.
18 - Server applies intents through `packages/sim` reducers, persists, and broadcasts compact **patches** to everyone in the same area; full **snapshot** of an area on join/area change.
19 - Protocol messages are typed in `packages/sim/src/protocol.ts` and validated with zod on the server.
20 - Auth: no accounts. A world has an invite code; each player device holds a random secret token issued on first join (stored in Capacitor Preferences/localStorage). Two player slots per world.
21
22 ## Persistence
23
24 - Durable Object SQLite: tables for `world` (globals: calendar, money, flags), `area_chunk` (per-area tile/object state as JSON, chunked), `player` (per-player JSON), `log` (recent events for the other player's "while you were away" summary).
25 - Durable gameplay transactions journal immediately, including any pending movement/clock state. Movement, progress animations and clock remainders coalesce into a checkpoint at most every 30 seconds. Disconnect and day end flush immediately; an unexpected object restart can lose at most that 30-second transient window, not minutes.
26 - Static area chunks and mutable `area_state` slices persist separately. Updating an animal/villager/mine slice writes one area-state row, without rewriting the map. Removed generated areas delete both their chunk and state rows.
27 - One registry cadence selector drives simulation. The durable alarm covers the seven-second clock and 30-second persistence deadlines; faster mine simulation uses an in-memory timer and does not write an alarm per frame. The timer stops when fast systems are inactive, paused, or everyone disconnects.
28 - Nightly logical backup: an export endpoint guarded by an admin token, pulled by a script on the Mac mini.
29
30 ## Repo layout
31
32 ```
33 packages/sim/ rules, types, protocol, reducers, tests
34 packages/content/ JSON data, schemas, validators
35 apps/server/ worker + FarmWorld DO, wrangler.toml
36 apps/client/ Phaser game, scenes, UI, input, net client
37 docs/ design docs (read before working)
38 tools/ scripts (cx wrapper, validators, exports)
39 ```
40
41 ## Engineering rules
42
43 - Every sim rule has unit tests (`bun test`). Content validators run in CI-style `bun run check`.
44 - `bun run check` = typecheck all packages + tests + content validation. Must pass before any task is "done".
45 - No new runtime dependency without a one-line justification in the task report.
46 - Original content only: no names, art, music, text, maps or code from Stardew Valley or other games. Placeholder art must be CC0 (record source in `apps/client/public/assets/CREDITS.md`).
47
48 ## Measured free-tier budget
49
50 The local production-DO replay sends two clients 10 movement intents/second each
51 for 60 virtual seconds, with real in-memory SQLite and deterministic socket/time
52 adapters. Counts include SQL inserts, updates and deletes (`total_changes()`),
53 plus alarm writes; they are local measurements, not deployed billing telemetry.
54 World creation, joins and the initial checkpoint are outside the replay window.
55
56 | Workload | SQL row changes/min | Alarm writes/min | Alarm invocations/min | Total writes/min |
57 | --- | ---: | ---: | ---: | ---: |
58 | Before hardening, stationary or moving | 6,229 | 399 | 199 | 6,628 |
59 | After hardening, stationary | 4 | 10 | 10 | 14 |
60 | After hardening, moving | 8 | 10 | 10 | 18 |
61 | Before fishing progress hardening (75 ms polls) | 7,155 | 10 | 10 | 7,165 |
62 | After fishing progress hardening (same 5 catches) | 51 | 10 | 10 | 61 |
63 | After combat hardening (two clients attack at 10 Hz) | 10 | 10 | 10 | 20 |
64
65 Continuous movement therefore projects to 4,320 writes in four active hours,
66 or 8,640 in eight hours. The regression cap is 36 writes/minute (17,280 over
67 eight hours), comfortably below the 20,000-write design target for recurring
68 traffic. Gameplay transactions, joins, day-end processing and map migrations
69 consume additional writes; those are not represented as free or included in
70 this movement-only projection. Dedicated fishing and combat regressions also
71 prevent their progress/swing frequency from determining write frequency. The
72 fishing replay makes five catches across two clients in one minute: 61 writes/min
73 projects to 14,640 over four active hours, including casts and catches. The
74 combat replay isolates repeated swings, with kill/loot durability tested
75 separately. These workload projections are not a universal daily spending cap.
76
77 At 1,200 incoming WebSocket messages/minute, the documented 20:1 billing ratio
78 plus 10 alarm invocations is approximately 70 Durable Object requests/minute:
79 33,600 over eight hours, before connection/gameplay overhead. Fast simulation
80 timers keep the 128 MB object active during combat; eight continuous hours are
81 approximately 3,600 GB-s, below the 13,000 GB-s daily allowance.
82 [Cloudflare pricing, checked 2026-10-22](https://developers.cloudflare.com/durable-objects/platform/pricing/)
83 lists 100,000 requests/day and 100,000 SQLite row writes/day; deletes and
84 `setAlarm()` count as writes. The design uses no paid-only service.
85
86 `apps/server/test/write-budget.test.ts` checks the movement and fast-timer budget,
87 flush/restart boundaries, immediate transaction durability, compact area slices,
88 retired areas and map migration. Server workerd tests allocate a temporary
89 persistence/config/log directory per suite, reuse it across intentional restarts,
90 and remove it during teardown; they never use the local development world.
91
92
93 ## Integration-2 workload budget
94
95 With animals, festivals, community and villagers enabled, the production-DO
96 replays still measure 14 stationary, 18 moving and 20 repeated-combat writes per
97 minute. Fishing measures 74 writes/minute including durable catches and their
98 new collection/quest hooks. The full-day mixed replay measures **1,054 writes**
99 over **848.04 real seconds**, including animal care, 24 harvest/replant/water
100 cycles, 15 catches, eight broken mine nodes, one kill, walking NPCs, travel and
101 the overnight checkpoint. Rounding four hours up to 17 whole replayed days and
102 including 300 setup/join writes gives **18,218 writes**, below the 20,000 target.
103 This is a measured workload projection, not a cap on arbitrary durable actions.
104 See [the integration-2 report](w3-integration2-report.md) and
105 `apps/server/test/mixed-day-write-budget.test.ts` for counts and assertions.
106