- BLOCKER 1: PhaserGame.tsx boot path now runs unwrap(env) → migrate(raw, env.schemaVersion).
Casting unwrap(record.envelope) directly to V1Payload silently accepted any
future-shape payload as the current shape; only migrate() walks the schema
version chain.
- BLOCKER 2: Settings.tsx onImport now correctly orders importFromBase64 →
unwrap (CRC verify) → migrate. Previous code discarded migrate's result
and then read v1.payload as if unwrap returned an envelope rather than
the payload itself — runtime crash on every import.
- BLOCKER 3: documented the lastTickAt invariant as wall-clock milliseconds,
written ONLY at saveSync time (never by the sim). Added acceptance_criteria
greps proving (a) saveSync writes clock.now(), (b) Garden scene does not
overwrite lastTickAt with a tick counter, (c) sim/garden/Garden.ts (if it
exists; the Garden scene actually lives at src/game/scenes/Garden.ts)
contains no lastTickAt: this.* writes.
- W2: D-29 keyboard shortcut wired in App.tsx — comma toggles Settings,
'j' dispatches a window CustomEvent the JournalIcon picks up.
- W5: lifecycle handle now stored in useRef and detached in the OUTER
useLayoutEffect cleanup (the previous IIFE-internal return was a closure
return, never reaching React's effect cleanup contract).