Files
core2026/app/game-engine/test/playAuditAiRng.test.ts
T

66 lines
3.1 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { LiteHashDRBG, RandUtil } from '@sammo-ts/common';
import { observeAiRng, type AiTraceStep } from '../src/turn/ai/generalAi/trace.js';
describe('AI RNG observation', () => {
it.each(['audit-a', 'audit-b', 'audit-c'])(
'preserves results, identity and following random state for %s',
(seed) => {
const baseline = new RandUtil(LiteHashDRBG.build(seed));
const events: AiTraceStep[] = [];
const observed = observeAiRng(new RandUtil(LiteHashDRBG.build(seed)), (event) => events.push(event));
const candidates = [
{ id: 1, secret: 'hidden' },
{ id: 2, secret: 'hidden' },
];
const draw = (rng: RandUtil) => [
rng.nextFloat1(),
rng.nextBool(0),
rng.nextBool(1),
rng.nextBool(0.5),
rng.nextBool(0.3),
rng.nextRange(-10, 100),
rng.nextRangeInt(1, 30),
rng.nextInt(1, 2),
rng.nextIntInclusive(0),
rng.choice([42]),
rng.choice(candidates),
rng.choice(new Set(candidates)),
rng.choice({ first: candidates[0]!, second: candidates[1]! }),
rng.choiceUsingWeight({ a: 0, b: 2, c: 3 }),
rng.choiceUsingWeightPair([
[candidates[0]!, 2],
[candidates[1]!, 3],
]),
rng.shuffle(candidates),
];
const expected = draw(baseline);
const actual = draw(observed);
expect(actual).toEqual(expected);
expect(actual[10]).toBe(expected[10]);
expect(events).toHaveLength(16); // helper 내부 호출을 별도 판단으로 중복 기록하지 않는다.
expect(JSON.stringify(events)).not.toContain('hidden');
expect(JSON.stringify(events)).not.toContain(seed);
expect(events[10]).toMatchObject({
kind: 'RNG',
method: 'choice',
result: { entityId: candidates.indexOf(actual[10] as (typeof candidates)[number]) + 1 },
});
expect(observed.nextFloat1()).toBe(baseline.nextFloat1());
}
);
it('does not fabricate successful observations for failed choices', () => {
const events: AiTraceStep[] = [];
const rng = observeAiRng(new RandUtil(LiteHashDRBG.build('error')), (step) => events.push(step));
expect(() => rng.choice([])).toThrow('Empty items');
expect(events).toEqual([]);
});
it('marks unsupported result shapes instead of copying arbitrary debug data', () => {
const events: AiTraceStep[] = [];
const rng = observeAiRng(new RandUtil(LiteHashDRBG.build('projection')), (step) => events.push(step));
const value = { secret: 'not a candidate id' };
expect(rng.choice([value])).toBe(value);
expect(events).toEqual([{ kind: 'RNG', method: 'choice', parameters: null, result: { unprojected: true } }]);
});
});