DisplayView called sortParticipantsByInitiative() on visibleParticipants,
ignoring DM drag order. 1-list model = participants[] IS display source.
After cross-init drag, player view diverged from AdminView/turnOrderIds.
Repro: round 4 replay. [reorder Summon1(10)→before Merchant(11)] made
turnOrderIds = [...,Summon2,Summon1,Merchant,OrcBoss]. AdminView correct.
DisplayView re-sorted = Summon2,Merchant,Summon1 (init order) = visually
Merchant appeared between Summon2 and Summon1, NOT at end. DM confused.
Fix: removed sort. DisplayView now renders participants[] order directly
(filter inactive monsters only), matching AdminView line 1222.
Test: RED → GREEN (src/tests/DisplayView.drag-order.test.js). Seeds 3
monsters in drag order [High:20, Low:10, Mid:11]. Asserts DOM order =
participants[] order, not init-sorted. No DisplayView regressions.
TODO = backlog from user. REWORK_PLAN = milestones/infra. M4 (dead-not-
skipped) is a milestone, stays in REWORK_PLAN. Removed false 'bugs only'
and M4 references from TODO header.
shared/tests/turn.jump.test.js: desired manual turn override behavior.
- jump sets currentTurn, future nextTurn continues
- jump to first stays same round
- jump invalid throws
2 RED (shared.jumpTurnTo not a function - feature missing).
1 green (invalid throws via TypeError).
TODO: JUMP_TURN_TO test refs added.
server/tests/ws-reconnect.test.js: subscribe, write (fires), force-drop WS,
write again (must still fire). RED on current. wsReady=null after drop,
no reconnect, subscribers dead forever. Display frozen.
src/storage/ws.js: added _test accessor (getWs, forceDrop, getReady,
docSubs, collSubs) for reconnect test. Test-only, no behavior change.
TODO: BUG-7 (reorder no undo), BUG-8 (ws reconnect) added.
Desired behavior locked:
- dead PC not removed from turnOrderIds
- dead PC turn still comes up (nextTurn visits them)
- dead PC on their turn can deathSave
- dead PC not auto-set isActive=false by applyHpChange
All 4 RED on current code. Root cause: nextTurn filters isActive,
applyHpChange sets isActive=false on death, computeTurnOrderAfterRemoval
drops dead from turnOrderIds.
TODO BUG-3/M4 updated with test refs.
turn.reorder.test.js: 4 green (swaps, throws-diff-init, throws-missing-id,
documents current no-turnOrderIds-touch) + 1 RED (BUG-6: should update
turnOrderIds to reflect new order).
Found: reorderParticipants changes participants[] array but not turnOrderIds.
nextTurn rotates via turnOrderIds only → mid-combat drag-drop = no effect.
replay-combat.js calls with wrong signature (swallowed by try/catch), so
real path never exercised either.
TODO: BUG-6 added.
REAL test audit should have been. jest, seeded RNG, mirrors replay-combat.js
op sequence exactly. Asserts per-round invariants: rotation-dupe, turnOrder
dup-id, currentTurn valid+active, HP bounds.
Result: 13 rotation-dupes / 100 rounds. First at round 4 (Cleric twice).
Deterministic, reproducible every run. BUG-5 locked.
Deprecate tests/audit/*.js: random sim gave false 0-violations while
this exact test reproduces bug. Commented early-return. Kept for reference,
delete later when log analyzer + unit tests cover ground.
TODO: BUG-5 added (mid-round addParticipant/revive corrupts rotation).
Root cause hypothesis: computeTurnOrderAfterAddition appends id to
turnOrderIds end. Round wrap re-sorts by initiative. currentTurn pointer
stale after sort → drifts → nextTurn revisits.
Test RED by design (documents live bug). Pre-push will block on push.
Audit bug: pause fired turn%12, no resume in same iter. nextTurn then
called on paused encounter → threw 'Encounter not running'. Throw is
correct feature behavior (nextTurn refuses when paused); audit misuse.
Fix: togglePause twice (pause+resume) in one iteration, plus guard
'advance-while-paused' check before nextTurn call.
Result: 6 audit artifacts → 0 violations / 100 rounds.
Confirms BUG-1 resolved as side effect of BUG-2 dup-id fix.
Replay verify: 10 rounds, 103 turns, no skip/dupe.
TODO: BUG-1 + BUG-2 marked RESOLVED/FIXED.
scripts/replay-combat.js: drives full combat via live backend REST, computes
turns through shared/turn.js. Player display (subscribed via WS) live-updates.
Usage: node scripts/replay-combat.js [rounds] [delayMs]
TODO.md: tracks M4 work.
- Dead participants must NOT be skipped (still occupy initiative slot,
death saves resolve on their turn). Saw in game Saturday.
- JUMP_TURN_TO manual turn override.