Something went wrong. Try again.
A Social Deduction / Deception Game
Something went wrong. Try again.
2.7 kB · 57 lines
TypeScript
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758import { describe, expect, it } from 'vitest'import { emptyNotebook, question, recordAnswer, selectCulprit } from './notebook.js'import type { EvidencePiece } from './evidence.js'
// The §4.1 worked example: three incriminating fakes (all sourced to the culprit C, so every matcher// denies), arranged so C is the only suspect inside all three match sets.// P1→{A,B,C} P2→{B,C,D} P3→{C,D,E} (C = culprit)function fake(id: string, matchSet: string[]): EvidencePiece { return { id, fragment: `f-${id}`, resource: 'trash', verb: 'collect', stationId: `st-${id}`, depth: 1, matchSet, trueSource: 'C', incriminating: true, condition: 'sabotaged' }}const P1 = fake('P1', ['A', 'B', 'C'])const P2 = fake('P2', ['B', 'C', 'D'])const P3 = fake('P3', ['C', 'D', 'E'])const CLUES = ['P1', 'P2', 'P3']
/** Ask every suspect in each fake's match set about it (disciplined: only matchers). */function disciplined() { let nb = emptyNotebook() for (const p of [P1, P2, P3]) for (const did of p.matchSet) nb = question(nb, p, did) return nb}
describe('notebook', () => { it('records every answer, including a non-matcher denial (the noise is real, §4.1)', () => { let nb = emptyNotebook() nb = question(nb, P3, 'B') // B does not match P3, but is asked anyway → a stray Denied is written expect(nb['B']?.['P3']).toBe('denied') })
it('records the source of a non-incriminating piece as Admitted, the rest Denied', () => { const real: EvidencePiece = { id: 'r1', verb: 'collect', fragment: 'f', resource: 'ore', stationId: 'st-r1', depth: 0, matchSet: ['A', 'B'], trueSource: 'A', incriminating: false, condition: 'broken' } let nb = emptyNotebook() nb = question(nb, real, 'A') nb = question(nb, real, 'B') expect(nb['A']?.['r1']).toBe('admitted') expect(nb['B']?.['r1']).toBe('denied') })
it('disciplined questioning → the intersection is uniquely the culprit', () => { expect(selectCulprit(disciplined(), CLUES)).toEqual(['C']) })
it('sloppy questioning (ask a non-matcher about a fake) → ambiguous intersection', () => { // B matches P1 and P2 but NOT P3. Asking B about P3 anyway writes a third denial → B joins C. const polluted = question(disciplined(), P3, 'B') expect(selectCulprit(polluted, CLUES).sort()).toEqual(['B', 'C']) })
it('a suspect never asked about a selected clue is not "denied in all"', () => { const nb = recordAnswer(recordAnswer(emptyNotebook(), 'Z', 'P1', 'denied'), 'Z', 'P2', 'denied') expect(selectCulprit(nb, CLUES)).toEqual([]) // Z blank on P3 })
it('empty selection selects no one', () => { expect(selectCulprit(disciplined(), [])).toEqual([]) })})