tests: M4 dead-participant skip RED (4 tests, turn.dead-skip.test.js)
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.
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## M4 — Initiative skip bug + dead-participant handling
|
## M4 — Initiative skip bug + dead-participant handling
|
||||||
|
|
||||||
### Dead participants must NOT be skipped in turn order
|
### Dead participants must NOT be skipped in turn order (BUG-3 / M4)
|
||||||
- Current: dead (HP=0) → `isActive=false` → removed from turn order → skipped
|
- Current: dead (HP=0) → `isActive=false` → removed from turn order → skipped
|
||||||
- WRONG. Dead participants still occupy initiative slot.
|
- WRONG. Dead participants still occupy initiative slot.
|
||||||
- PCs (unconscious): death saves still resolve on their turn
|
- PCs (unconscious): death saves still resolve on their turn
|
||||||
@@ -14,7 +14,12 @@
|
|||||||
(sets isActive=false on death), `computeTurnOrderAfterRemoval`.
|
(sets isActive=false on death), `computeTurnOrderAfterRemoval`.
|
||||||
- Characterization tests (`src/tests/Combat.characterization.test.js`) lock CURRENT
|
- Characterization tests (`src/tests/Combat.characterization.test.js`) lock CURRENT
|
||||||
(buggy) behavior — those tests must be UPDATED to desired behavior, not
|
(buggy) behavior — those tests must be UPDATED to desired behavior, not
|
||||||
preserved. Red desired-test first, then fix.
|
preserved.
|
||||||
|
- RED test locked: `shared/tests/turn.dead-skip.test.js` (4 tests).
|
||||||
|
- 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
|
||||||
|
|
||||||
### JUMP_TURN_TO(participantId) manual turn override
|
### JUMP_TURN_TO(participantId) manual turn override
|
||||||
- DM clicks participant → cursor jumps → that participant's turn now.
|
- DM clicks participant → cursor jumps → that participant's turn now.
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// M4 desired behavior: dead PC stays in turn order, turn still comes up,
|
||||||
|
// deathSave fires. Current code filters isActive (set false on death) so
|
||||||
|
// dead participants are SKIPPED. Test asserts desired state = RED on current.
|
||||||
|
|
||||||
|
const shared = require('@ttrpg/shared');
|
||||||
|
const { makeParticipant, startEncounter, nextTurn, applyHpChange, deathSave } = shared;
|
||||||
|
|
||||||
|
function p(id, init, extra = {}) {
|
||||||
|
return makeParticipant({
|
||||||
|
id, name: id, type: 'monster',
|
||||||
|
initiative: init, maxHp: 100, currentHp: 100,
|
||||||
|
...extra,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function pc(id, init, extra = {}) {
|
||||||
|
return makeParticipant({
|
||||||
|
id, name: id, type: 'character',
|
||||||
|
initiative: init, maxHp: 100, currentHp: 100,
|
||||||
|
...extra,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function enc(ps) {
|
||||||
|
return { name:'t', participants:ps, isStarted:false, isPaused:false,
|
||||||
|
round:0, currentTurnParticipantId:null, turnOrderIds:[] };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('M4: dead participants stay in turn order', () => {
|
||||||
|
test('dead PC not removed from turnOrderIds', () => {
|
||||||
|
const ps = [pc('a', 20), pc('b', 15), pc('c', 10)];
|
||||||
|
let e = enc(ps);
|
||||||
|
e = { ...e, ...startEncounter(e).patch };
|
||||||
|
const orderBefore = e.turnOrderIds.slice();
|
||||||
|
// kill b
|
||||||
|
e = { ...e, ...applyHpChange(e, 'b', 'damage', 100).patch };
|
||||||
|
expect(e.turnOrderIds).toEqual(orderBefore);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dead PC turn still comes up (nextTurn visits them)', () => {
|
||||||
|
const ps = [pc('a', 20), pc('b', 15), pc('c', 10)];
|
||||||
|
let e = enc(ps);
|
||||||
|
e = { ...e, ...startEncounter(e).patch };
|
||||||
|
// kill b
|
||||||
|
e = { ...e, ...applyHpChange(e, 'b', 'damage', 100).patch };
|
||||||
|
// advance: a→b→c. b's turn should come up.
|
||||||
|
e = { ...e, ...nextTurn(e).patch };
|
||||||
|
expect(e.currentTurnParticipantId).toBe('b');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dead PC on their turn can deathSave', () => {
|
||||||
|
const ps = [pc('a', 20), pc('b', 15)];
|
||||||
|
let e = enc(ps);
|
||||||
|
e = { ...e, ...startEncounter(e).patch };
|
||||||
|
// kill b (current = a)
|
||||||
|
e = { ...e, ...applyHpChange(e, 'b', 'damage', 100).patch };
|
||||||
|
// advance to b's turn
|
||||||
|
e = { ...e, ...nextTurn(e).patch };
|
||||||
|
expect(e.currentTurnParticipantId).toBe('b');
|
||||||
|
// b is dead, on their turn: deathSave should not throw
|
||||||
|
const r = deathSave(e, 'b', 1);
|
||||||
|
expect(r.patch).toBeTruthy();
|
||||||
|
const b = r.patch.participants.find(x => x.id === 'b');
|
||||||
|
expect(b.deathSaves).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dead PC not auto-set isActive=false by applyHpChange', () => {
|
||||||
|
const ps = [pc('a', 20), pc('b', 15)];
|
||||||
|
let e = enc(ps);
|
||||||
|
e = { ...e, ...startEncounter(e).patch };
|
||||||
|
e = { ...e, ...applyHpChange(e, 'b', 'damage', 100).patch };
|
||||||
|
const b = e.participants.find(x => x.id === 'b');
|
||||||
|
expect(b.isActive).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user