877fbaa8e7
Persist show state to local saves; author map darkness in scene editor with bake-to-scene and help updates. Co-authored-by: Cursor <cursoragent@cursor.com>
109 lines
3.4 KiB
TypeScript
109 lines
3.4 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
|
|
import { EffectsStore } from './effectsStore';
|
|
import { GameSavesStore } from '../gameSave/gameSavesStore';
|
|
import { canonicalGameSave, emptyGameSaveBody, freezeTokenPathPlayback } from '../../shared/gameSave/buildSnapshot';
|
|
import type { EffectInstance } from '../../shared/types';
|
|
import type { TokenPath } from '../../shared/types/tokenPath';
|
|
import type { TokenPathPlaybackEntry } from '../../shared/types/tokenPathSession';
|
|
|
|
function fog(): EffectInstance {
|
|
return {
|
|
id: 'fog1',
|
|
type: 'fog',
|
|
seed: 1,
|
|
createdAtMs: 1,
|
|
points: [{ x: 0.2, y: 0.2, tMs: 1 }],
|
|
radiusN: 0.1,
|
|
opacity: 0.4,
|
|
lifetimeMs: null,
|
|
};
|
|
}
|
|
|
|
function bolt(): EffectInstance {
|
|
return {
|
|
id: 'lt1',
|
|
type: 'lightning',
|
|
seed: 2,
|
|
createdAtMs: 1,
|
|
start: { x: 0, y: 0 },
|
|
end: { x: 1, y: 1 },
|
|
widthN: 0.02,
|
|
intensity: 1,
|
|
lifetimeMs: 800,
|
|
};
|
|
}
|
|
|
|
void test('effects stay on the card and action effects do not', () => {
|
|
const store = new EffectsStore();
|
|
store.switchScene('card-a');
|
|
store.dispatch({ kind: 'instance.add', instance: fog() });
|
|
store.dispatch({ kind: 'instance.add', instance: bolt() });
|
|
store.switchScene('card-b');
|
|
assert.equal(store.getState().instances.length, 0);
|
|
store.switchScene('card-a');
|
|
assert.deepEqual(
|
|
store.getState().instances.map((i) => i.type),
|
|
['fog'],
|
|
);
|
|
store.dispatch({ kind: 'instances.clear' });
|
|
store.switchScene('card-b');
|
|
store.switchScene('card-a');
|
|
assert.equal(store.getState().instances.length, 0);
|
|
});
|
|
|
|
void test('freezeTokenPathPlayback parks a moving token', () => {
|
|
const pathLine: TokenPath = {
|
|
points: [
|
|
{ nx: 0, ny: 0.5 },
|
|
{ nx: 1, ny: 0.5 },
|
|
],
|
|
closed: false,
|
|
loopMode: 'once',
|
|
durationSec: 2,
|
|
startMode: 'onEnter',
|
|
delaySec: 0,
|
|
facingMode: 'tangentSmooth',
|
|
fixedRotationDeg: 0,
|
|
};
|
|
const entry: TokenPathPlaybackEntry = {
|
|
kind: 'token',
|
|
placementId: 't1',
|
|
phase: 'moving',
|
|
segmentStartedAtMs: 0,
|
|
baseDist: 0,
|
|
direction: 1,
|
|
rejoinDist: null,
|
|
durationSec: 2,
|
|
pathLength: 1,
|
|
};
|
|
const frozen = freezeTokenPathPlayback({ 'token:t1': entry }, () => pathLine, 1000);
|
|
const next = frozen['token:t1'];
|
|
assert.ok(next);
|
|
assert.equal(next.phase, 'stopped');
|
|
assert.ok(next.baseDist > 0.4 && next.baseDist < 0.6);
|
|
});
|
|
|
|
void test('game save file keeps id on overwrite and lists one campaign', async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'dnd-saves-'));
|
|
const store = new GameSavesStore(root);
|
|
const body = emptyGameSaveBody('p_one');
|
|
const first = await store.write('p_one', body, { id: null, name: 'Вечер' });
|
|
const again = await store.write('p_one', body, { id: first.id, name: 'Вечер 2' });
|
|
assert.equal(again.id, first.id);
|
|
assert.equal(again.name, 'Вечер 2');
|
|
const list = await store.list('p_one');
|
|
assert.equal(list.length, 1);
|
|
assert.equal(list[0]?.name, 'Вечер 2');
|
|
const other = emptyGameSaveBody('p_two');
|
|
await store.write('p_two', other, { id: null, name: 'Другая' });
|
|
assert.equal((await store.list('p_one')).length, 1);
|
|
const loaded = await store.read('p_one', first.id);
|
|
const { id: _id, name: _name, savedAt: _savedAt, ...rest } = loaded;
|
|
assert.equal(canonicalGameSave(rest), canonicalGameSave(body));
|
|
});
|