2026-07-06 16:39:11 -04:00
|
|
|
'use strict';
|
2026-06-30 13:57:55 -04:00
|
|
|
|
|
|
|
|
const shared = require('@ttrpg/shared');
|
|
|
|
|
const { makeParticipant, startEncounter, nextTurn, applyHpChange, deathSave } = shared;
|
2026-07-06 10:31:46 -04:00
|
|
|
const { mockCtx } = require('./_helpers');
|
2026-06-30 13:57:55 -04:00
|
|
|
|
|
|
|
|
function pc(id, init, extra = {}) {
|
|
|
|
|
return makeParticipant({
|
2026-07-06 16:39:11 -04:00
|
|
|
id,
|
|
|
|
|
name: id,
|
|
|
|
|
type: 'character',
|
|
|
|
|
initiative: init,
|
|
|
|
|
maxHp: 100,
|
|
|
|
|
currentHp: 100,
|
2026-06-30 13:57:55 -04:00
|
|
|
...extra,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
function enc(ps) {
|
|
|
|
|
return { name:'t', participants:ps, isStarted:false, isPaused:false,
|
|
|
|
|
round:0, currentTurnParticipantId:null, turnOrderIds:[] };
|
|
|
|
|
}
|
2026-07-06 16:39:11 -04:00
|
|
|
const byId = (e, id) => e.participants.find(p => p.id === id);
|
2026-06-30 13:57:55 -04:00
|
|
|
|
2026-07-06 16:39:11 -04:00
|
|
|
describe('death state: participant remains in initiative and encounter', () => {
|
|
|
|
|
test('dropping to 0 keeps PC active, in participants, and in turnOrderIds', async () => {
|
2026-07-06 10:31:46 -04:00
|
|
|
const { ctx } = mockCtx();
|
2026-07-06 16:39:11 -04:00
|
|
|
let e = enc([pc('a', 20), pc('b', 15), pc('c', 10)]);
|
2026-07-06 10:31:46 -04:00
|
|
|
e = await startEncounter(e, ctx);
|
2026-06-30 13:57:55 -04:00
|
|
|
const orderBefore = e.turnOrderIds.slice();
|
2026-07-06 16:39:11 -04:00
|
|
|
|
2026-07-06 10:31:46 -04:00
|
|
|
e = await applyHpChange(e, 'b', 'damage', 100, ctx);
|
2026-07-06 16:39:11 -04:00
|
|
|
|
|
|
|
|
expect(byId(e, 'b').status).toBe('dying');
|
|
|
|
|
expect(byId(e, 'b').isActive).not.toBe(false);
|
|
|
|
|
expect(e.participants.map(p => p.id)).toContain('b');
|
2026-06-30 13:57:55 -04:00
|
|
|
expect(e.turnOrderIds).toEqual(orderBefore);
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-06 16:39:11 -04:00
|
|
|
test('dead PC remains in participants and turnOrderIds after three failures', async () => {
|
2026-07-06 10:31:46 -04:00
|
|
|
const { ctx } = mockCtx();
|
2026-07-06 16:39:11 -04:00
|
|
|
let e = enc([pc('a', 20), pc('b', 15), pc('c', 10)]);
|
2026-07-06 10:31:46 -04:00
|
|
|
e = await startEncounter(e, ctx);
|
2026-07-06 16:39:11 -04:00
|
|
|
const orderBefore = e.turnOrderIds.slice();
|
2026-07-06 10:31:46 -04:00
|
|
|
e = await applyHpChange(e, 'b', 'damage', 100, ctx);
|
2026-06-30 13:57:55 -04:00
|
|
|
|
2026-07-06 16:39:11 -04:00
|
|
|
e = (await deathSave(e, 'b', 'fail', ctx)).enc;
|
|
|
|
|
e = (await deathSave(e, 'b', 'fail', ctx)).enc;
|
|
|
|
|
e = (await deathSave(e, 'b', 'fail', ctx)).enc;
|
|
|
|
|
|
|
|
|
|
expect(byId(e, 'b').status).toBe('dead');
|
|
|
|
|
expect(byId(e, 'b').isActive).not.toBe(false);
|
|
|
|
|
expect(e.participants.map(p => p.id)).toContain('b');
|
|
|
|
|
expect(e.turnOrderIds).toEqual(orderBefore);
|
2026-06-30 13:57:55 -04:00
|
|
|
});
|
|
|
|
|
|
2026-07-06 16:39:11 -04:00
|
|
|
test('nextTurn may still land on dead PC because DM/tool events may matter', async () => {
|
2026-07-06 10:31:46 -04:00
|
|
|
const { ctx } = mockCtx();
|
2026-07-06 16:39:11 -04:00
|
|
|
let e = enc([pc('a', 20), pc('b', 15), pc('c', 10)]);
|
2026-07-06 10:31:46 -04:00
|
|
|
e = await startEncounter(e, ctx);
|
|
|
|
|
e = await applyHpChange(e, 'b', 'damage', 100, ctx);
|
2026-07-06 16:39:11 -04:00
|
|
|
e = (await deathSave(e, 'b', 'fail', ctx)).enc;
|
|
|
|
|
e = (await deathSave(e, 'b', 'fail', ctx)).enc;
|
|
|
|
|
e = (await deathSave(e, 'b', 'fail', ctx)).enc;
|
|
|
|
|
|
|
|
|
|
e = await nextTurn(e, ctx);
|
|
|
|
|
|
|
|
|
|
expect(e.currentTurnParticipantId).toBe('b');
|
|
|
|
|
expect(byId(e, 'b').status).toBe('dead');
|
2026-07-03 17:59:41 -04:00
|
|
|
});
|
|
|
|
|
|
2026-07-06 16:39:11 -04:00
|
|
|
test('deathSave rejected/no-op once dead', async () => {
|
2026-07-06 10:31:46 -04:00
|
|
|
const { ctx } = mockCtx();
|
2026-07-06 16:39:11 -04:00
|
|
|
let e = enc([pc('a', 20), pc('b', 15)]);
|
2026-07-06 10:31:46 -04:00
|
|
|
e = await startEncounter(e, ctx);
|
|
|
|
|
e = await applyHpChange(e, 'b', 'damage', 100, ctx);
|
2026-07-06 16:39:11 -04:00
|
|
|
e = (await deathSave(e, 'b', 'fail', ctx)).enc;
|
|
|
|
|
e = (await deathSave(e, 'b', 'fail', ctx)).enc;
|
|
|
|
|
e = (await deathSave(e, 'b', 'fail', ctx)).enc;
|
|
|
|
|
const before = byId(e, 'b');
|
|
|
|
|
|
|
|
|
|
try { e = (await deathSave(e, 'b', 'fail', ctx)).enc; } catch (err) {}
|
|
|
|
|
|
|
|
|
|
expect(byId(e, 'b')).toMatchObject(before);
|
2026-06-30 13:57:55 -04:00
|
|
|
});
|
|
|
|
|
});
|