Files
ttrpg-initiative-tracker/scripts/repro-pause-bug.js
T
david raistrick 13490fe3de tests: round-rotation audit, dup-id fail, replay rewrite
- turn.round-rotation.test.js: 7 tests, full round visits each active
  participant once (pure nextTurn clean). Green.
- turn.characterization.test.js: RED 'addParticipant rejects duplicate id'.
  Validates current behavior allows dup ids (self-inflicted in audit via
  loop spin-while-paused re-adding same id; unreachable in app via
  crypto.randomUUID, but documents gap).
- audit-rotation.js: pure turn.js simulation of replay op sequence.
  Detects rotation violations (skip/dupe per round). Pause disabled = 0
  violations across 100 rounds. Pause enabled = 56-77 violations starting
  round 20. Pinpoints addParticipant+pause interaction.
- repro-pause-bug.js: minimal repro scripts.
- replay-combat.js: rewritten for real rounds (full initiative cycles),
  visible damage each turn, all conditions, toggleActive, remove,
  reinforce, edit, pause/resume, reorder, endEncounter. HP bumped for
  100-round sustain + revive dead each round.

No feature code changed.
2026-06-29 15:49:39 -04:00

77 lines
3.5 KiB
JavaScript

// scripts/repro-pause-bug.js
// Minimal repro: pause+resume causes nextTurn to repeat same participant forever.
'use strict';
const shared = require('../shared');
const { makeParticipant, startEncounter, nextTurn, togglePause, addParticipant } = shared;
function p(id, init) {
return makeParticipant({ id, name: id, type: 'monster', initiative: init, maxHp: 100, currentHp: 100 });
}
let e = {
name: 't', participants: [p('a', 20), p('b', 15), p('c', 10)],
isStarted: false, isPaused: false, round: 0, currentTurnParticipantId: null, turnOrderIds: [],
};
e = { ...e, ...startEncounter(e).patch };
console.log('start:', { current: e.currentTurnParticipantId, order: e.turnOrderIds, round: e.round });
// advance 1 turn
e = { ...e, ...nextTurn(e).patch };
console.log('turn1:', { current: e.currentTurnParticipantId, round: e.round });
// pause then resume immediately
e = { ...e, ...togglePause(e).patch };
console.log('paused:', { isPaused: e.isPaused, current: e.currentTurnParticipantId, order: e.turnOrderIds });
e = { ...e, ...togglePause(e).patch };
console.log('resumed:', { isPaused: e.isPaused, current: e.currentTurnParticipantId, order: e.turnOrderIds });
// advance 5 turns — should visit b, c, a, b, c
const visited = [e.currentTurnParticipantId];
for (let i = 0; i < 5; i++) {
e = { ...e, ...nextTurn(e).patch };
visited.push(e.currentTurnParticipantId);
}
console.log('5 turns after resume:', visited);
// now repro with addParticipant while paused
let e2 = {
name: 't', participants: [p('a', 20), p('b', 15), p('c', 10)],
isStarted: false, isPaused: false, round: 0, currentTurnParticipantId: null, turnOrderIds: [],
};
e2 = { ...e2, ...startEncounter(e2).patch };
e2 = { ...e2, ...nextTurn(e2).patch }; // current=b
const newP = makeParticipant({ id: 'x', name: 'x', type: 'monster', initiative: 25, maxHp: 100, currentHp: 100 });
e2 = { ...e2, ...addParticipant(e2, newP).patch };
console.log('\nadded x while running:', { current: e2.currentTurnParticipantId, order: e2.turnOrderIds });
e2 = { ...e2, ...togglePause(e2).patch };
e2 = { ...e2, ...togglePause(e2).patch };
console.log('after pause/resume:', { current: e2.currentTurnParticipantId, order: e2.turnOrderIds });
const v2 = [e2.currentTurnParticipantId];
for (let i = 0; i < 5; i++) {
e2 = { ...e2, ...nextTurn(e2).patch };
v2.push(e2.currentTurnParticipantId);
}
console.log('5 turns after add+pause/resume:', v2);
// repro 3: addParticipant WHILE paused, then resume
let e3 = {
name: 't', participants: [p('a', 20), p('b', 15), p('c', 10)],
isStarted: false, isPaused: false, round: 0, currentTurnParticipantId: null, turnOrderIds: [],
};
e3 = { ...e3, ...startEncounter(e3).patch };
e3 = { ...e3, ...nextTurn(e3).patch }; // current=b
console.log('\n--- add while PAUSED ---');
e3 = { ...e3, ...togglePause(e3).patch }; // pause
console.log('paused:', { current: e3.currentTurnParticipantId, order: e3.turnOrderIds });
const np = makeParticipant({ id: 'x', name: 'x', type: 'monster', initiative: 25, maxHp: 100, currentHp: 100 });
e3 = { ...e3, ...addParticipant(e3, np).patch };
console.log('add-while-paused:', { current: e3.currentTurnParticipantId, order: e3.turnOrderIds });
e3 = { ...e3, ...togglePause(e3).patch }; // resume (rebuilds order)
console.log('resumed:', { current: e3.currentTurnParticipantId, order: e3.turnOrderIds });
const v3 = [e3.currentTurnParticipantId];
for (let i = 0; i < 5; i++) {
e3 = { ...e3, ...nextTurn(e3).patch };
v3.push(e3.currentTurnParticipantId);
}
console.log('5 turns after add-while-paused+resume:', v3);