NPC 판단 절차와 난수 결과의 선택적 감사 관측 기반 추가
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
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 } }]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user