Both combat.js replay and Combat.scenario.test.js built character participants
inline via buildCharacterParticipant without persisting to campaign doc. Real
app stores chars in campaign doc players array; encounter participants built
from that. Replay/scenario diverged from real flow — campaign had zero chars.
Fix:
- replay.js: roster chars now have id, persisted to campaign doc players array
at setup_campaign step
- Combat.scenario.test.js: addCharacterViaUI writes char to campaign doc
players array (mirrors app), CAMPAIGN_PATH const added
togglePause no longer writes log entry (shared/turn.js). Lifecycle op, not
player action. Undo stack now targets last real action, not flag flip.
Removed dead pause/resume cases from expandUndo.
verify.js false positive: round wrap via non-nextTurn event (removeParticipant)
not detected — cycleActed never reset, actors flagged as acted_twice. Fix:
detect round change on ANY event, drain removed/inactive before finalize
(avoids false skipped when actor removed mid-round).
Tests updated: togglePause logging test asserts no log; undo test asserts
excluded. Repro confirmed via minimal verify case (removeParticipant r1->r2).
- Add status-driven death-save model:
- status: conscious | dying | stable | dead
- deathSaveSuccesses/deathSaveFailures as display counters
- remove old death-save fields as source of truth
- Add death-save actions:
- success, fail, nat1, nat20
- stabilize participant
- revive dead participant to 0 HP, stable, unconscious
- Apply 5e HP transition rules:
- characters and NPCs at 0 HP become dying
- stable/dying participants gain unconscious condition
- healing clears death saves and returns conscious
- massive damage kills
- non-NPC monsters at 0 HP become dead and inactive
- Split NPCs from monsters with type="npc":
- NPCs use death saves like characters
- NPCs remain DM-controlled/monster-colored in UI
- new NPCs no longer persist isNpc flag
- Keep dead PCs/NPCs in encounter and initiative until DM removes or deactivates
- Allow DM active/inactive toggle for dead participants
- Hide inactive participants from player display
- Improve DM and player death-state UI:
- show Dying/Dead/Unconscious states consistently
- add revive button for dead participants
- distinguish dead visual from inactive visual in DM view
- Fix initiative drag/reorder behavior:
- downward drag now changes order instead of no-op
- paused combat can reorder across current turn pointer
- turnOrderIds stay synced to participants order
- Persist campaign collapse state in localStorage
- Update death-save docs and encounter builder docs
- Add/expand tests for:
- death saves and HP transitions
- dead/active/inactive behavior
- NPC death-save behavior
- player display visibility
- drag reorder semantics
- logging expectations
Tests:
- npm run test:all
- app: 94 passed
- shared: 158 passed, 5 skipped
- server: 32 passed
feat(combat): add first-class undo and redo controls
Add undo and redo controls to the combat UI so the DM can recover from recent
actions without leaving the encounter view.
Undo and redo operate on the current encounter's combat history. Empty stacks
produce clear feedback instead of failing silently. Redo order follows normal
stack behavior after multiple undos.
This makes combat history actionable during play, not just visible in the log.
feat(logs): make combat logs replayable
Replace plain combat log messages with structured combat events that can be
used by the UI, exported as JSON, replayed, and verified.
Each new log entry records the action type, encounter identity, participant
identity, a small action delta, undo intent, and a turn snapshot. Download and
copy now export the event stream as JSON so a saved combat log is useful for
offline analysis and debugging.
Legacy logs remain viewable, but new logs use the structured event format.
feat(logs): make undo and redo transactional
Apply undo and redo as single storage operations so the encounter state and log
state cannot drift apart.
Server storage applies the encounter update and the log undone flag inside one
SQLite transaction. Firebase storage uses a batch write for the same behavior.
The storage contract now includes undo/redo semantics.
This replaces fragile multi-write undo behavior where a failure could update
the encounter without marking the log, or mark the log without updating the
encounter.
feat(combat): add unified replay and verification tool
Add one combat CLI for replaying live combat and verifying combat logs.
Replay drives the live backend through the same shared combat logic used by the
app, writes a JSON event log to an explicit output path, and automatically
verifies the result. Verification checks for DM-visible combat problems such as
skipped turns, double actions, bad round changes, and unexpected turn order
changes.
The tool uses the same JSON event stream produced by log downloads, supports
verbose turn output, and handles Ctrl-C by ending the encounter, writing the
partial log, and verifying what was captured.
fix(perf): keep long combat logging fast
Remove the combat-time log query bottleneck that made long replays slow as log
volume grew.
Combat controls no longer subscribe to the log collection just to keep undo and
redo state warm. Undo and redo now query the latest matching encounter log only
when clicked. Server collection queries support exact filters, ordering,
limits, and offsets, and SQLite indexes keep latest-log and per-encounter log
lookups fast.
Also fix duplicate WebSocket handler registration so realtime updates do not
double-fire under write load.
fix(turns): make toggle active a status change
Make toggle active a roster/status edit instead of a turn advance.
Deactivating the current participant no longer passes the turn or increments
the round. The current turn stays where it is until the DM explicitly clicks
Next Turn, and Next Turn skips inactive participants during normal rotation.
This matches the initiative design: slot order is stable, toggle active does
not move participants, and round changes only come from explicit turn advance.
chore(ci): make warnings and hangs fail fast
Tighten test and build checks so failures are visible instead of noisy or
silent.
Builds run with CI enabled so warnings fail production builds. The full test
command runs app, shared, and server suites with hard timeouts so hangs fail
quickly. Static eslint coverage fails on warnings as well as errors.
Tests were updated around the new async combat logging flow, structured log
events, transactional undo, replay verification, and toggle-active semantics.