# Log System Redesign Plan **Status:** DRAFT — not started **Branch:** `feat-turn-writes-logs` **Goal:** Lean mutation deltas. Kill bloated payload/undo duplication. ## Problem Current events = 10-16KB each. `add_participant` row = 16KB to say "Fighter added, init 22." Root cause: full participant roster stored 3x (payload + undo_payload.updates + undo_payload.redo). DB: 2486 docs, most bloated. Replay trace 1.6-5.5MB for ~250 steps. Old log (pre-refactor) = message string + small undo object. Proven worked for undo/redo + viewer. Just wanted MORE STRUCTURE, not MORE DATA. Overbuilt instead. ## Intention Each event = mutation delta + lean snapshot. Structured for filter/group/analyzer. No roster arrays. ### Canonical lean event shape ```js { id, ts, type, // identity + filter key message, // human text (viewer displays) encounterId, encounterName, // which encounter participantId, participantName, // who (nullable: pause/nextTurn have none) // type-specific scalar delta (one of these clusters per type): // add: { init, maxHp, type: 'character'|'monster' } // remove: { dead: bool } // damage/heal:{ amount } // condition: { condition } // toggleActive:{ revive: bool } // reorder: { draggedId, targetId } // update: { changedFields: ['notes', ...] } // nextTurn: (none — snapshot carries new round/pointer) // pause/resume/start/end: (none) snapshot: { // lean, ALREADY GOOD — keep as-is round, currentTurnParticipantId, turnOrderIds, activeIds }, undo: // minimal } ``` ### Undo inverses (id-based, minimal) | type | undo | |------|------| | add_participant | `{ participantId }` → removeParticipant by id | | remove_participant | full participant obj (must restore — only place full obj needed) | | damage | `{ amount: -amount }` or heal back | | heal | `{ amount: -amount }` or damage back | | condition | `{ condition }` (toggle is its own inverse) | | toggleActive | `{ revive: !revive }` | | reorder | `{ draggedId, targetId }` (swap back) | | update | `{ changedFields: [...], oldValues: {...} }` | | nextTurn | `{ currentTurnParticipantId, round }` from snapshot delta | | pause/resume | toggle | | start/end | full prior scalar state | **No `redo` field.** Redo = re-apply forward delta from event itself. **No `undo_payload` wrapper.** Just `undo` object. ## 4 Consumers 1. **Viewer** — shows `message` + `type` + `participantName` + `undone` flag. Legacy logs (no type) display message only. 2. **Undo/redo** — apply `undo` inverse by id. Id-based, minimal. 3. **Replay** — turn.js funcs write own lean events. Trust func return. NO read-back. JSONL trace = lean snapshots + types. 4. **Analyzer** — reads `snapshot` sequence + `type`. Reconstructs roster from event stream (add/remove by id). NO payload arrays needed. ## Legacy Support - **Viewer:** yes — show old message-only logs, no type parse - **Undo:** no — old logs unundoable (acceptable, sit inert) - **Analyzer:** best-effort — snapshot may be absent on legacy → skip invariant check for that run ## Why Lean - `add_participant` 16KB → ~150 bytes - Replay trace 5.5MB → ~250KB - Undo still works (proven old log did same) - Analyzer unaffected (uses snapshot+type, not payload) ## Migration Plan ### Phase 1: Redesign event writers (shared/turn.js) - [ ] Rewrite `buildEntry()` to produce lean shape (delta fields per type) - [ ] Rewrite `commit()` — no patch storage, write lean event - [ ] Per-func: compute minimal delta + inverse from enc before/after - [ ] Drop `payload`, `undo_payload`, `undo_payload.redo` ### Phase 2: Update undo executor - [ ] Find undo consumer (App.js `applyUndo` or similar) - [ ] Switch to id-based inverse application - [ ] Handle each type's undo shape - [ ] Drop `transactionalUndo` full-patch path if exists ### Phase 3: Update replay trace - [ ] `replay-combat.js` callStep: trust return, snapshot only (no read-back) - [ ] Trace = `{step, ts, type, pre:leanSnap, post:leanSnap}` — no participant arrays ### Phase 4: Update analyzer - [ ] `analyze-turns.js` already uses snapshot+type only — verify no payload dep - [ ] Reconstruct roster from event stream for skip/double-act checks (id-level) ### Phase 5: Legacy compat - [ ] `logEvent.js normalizeEvent` — lift legacy into lean shape (type='legacy', message only) - [ ] Viewer handles legacy gracefully - [ ] Download = lean events only (legacy excluded or message-only) ### Phase 6: Cleanup - [ ] Delete old bloated docs from DB? Or leave (inert, viewer reads message)? - [ ] Verify build CI=true passes - [ ] Verify analyzer clean on fresh replay - [ ] Verify undo works on new events ## NOT in scope - Fixing 5 pre-existing initiative bugs (logged in TODO.md, separate) - Changing snapshot shape (already lean) - Changing viewer UI (just data source) ## Open Questions - Undo for `remove_participant`: must restore full obj (only place full obj needed). Acceptable? - Should legacy logs be deleted from DB or left inert? - Download format: lean JSON array (no legacy) — confirm