a9f190ed27
- 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).