Something went wrong. Try again.
A Social Deduction / Deception Game
Something went wrong. Try again.
3.6 kB · 76 lines
TypeScript
at main
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677import { describe, expect, it } from 'vitest'import { attributesFromGenerator, type GeneratorRecord } from './attributes.js'import { buildOverlapMystery, tokensOf, verifyRuleOfThree } from './construct.js'import type { SuspectAttributes } from './types.js'
function mulberry32(seed: number): () => number { let a = seed >>> 0 return () => { a = (a + 0x6d2b79f5) | 0 let t = Math.imul(a ^ (a >>> 15), 1 | a) t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t return ((t ^ (t >>> 14)) >>> 0) / 4294967296 }}
function suspect(did: string, layers: Array<[string, string, string?]>): SuspectAttributes { const rec: GeneratorRecord = { bodyType: 'male', layers: layers.map(([id, title, main]) => ({ id, title, colors: main ? { main } : undefined })), } return attributesFromGenerator(did, did, rec)!}
describe('tokensOf', () => { it('emits title, colour, and combined tokens — colour only for recolored items', () => { const s = suspect('did:plc:a', [ ['tops', 'Basic Shirt', '#c0392b'], // red, recolorable ['ears', 'Elfen Ears'], // fixed: no main colour ['righthand', 'Coffee Mug'], ]) const toks = tokensOf(s) // three granularities for the recolored shirt expect(toks.has('tops#title#Basic Shirt')).toBe(true) expect(toks.has('tops#color#red')).toBe(true) expect(toks.has('tops#both#Basic Shirt#red')).toBe(true) // fixed item → title fragment only, never a colour fragment expect(toks.has('ears#title#Elfen Ears')).toBe(true) expect([...toks].some((t) => t.startsWith('ears#color'))).toBe(false) expect(toks.has('held#title#Coffee Mug')).toBe(true) })})
describe('buildOverlapMystery', () => { // Hand-built pool that admits the overlap-by-2 lattice around culprit C, using three shared traits: // t1 = red tops, t2 = green bottoms, t3 = a Hard Hat // C has all three; w12 has t1+t2; w23 has t2+t3; w1 has t1 only; w3 has t3 only. const pool: SuspectAttributes[] = [ suspect('C', [['tops', 'Shirt', '#c0392b'], ['bottoms', 'Pants', '#2ecc71'], ['headwear', 'Hard Hat', '#888']]), suspect('w12', [['tops', 'Shirt', '#c0392b'], ['bottoms', 'Pants', '#2ecc71'], ['headwear', 'Beret', '#111']]), suspect('w23', [['bottoms', 'Pants', '#2ecc71'], ['headwear', 'Hard Hat', '#888'], ['tops', 'Tee', '#2244cc']]), suspect('w1', [['tops', 'Shirt', '#c0392b'], ['bottoms', 'Pants', '#34495e'], ['headwear', 'Cap', '#111']]), suspect('w3', [['headwear', 'Hard Hat', '#888'], ['tops', 'Tee', '#f1c40f'], ['bottoms', 'Pants', '#34495e']]), ]
it('finds a valid overlap-by-2 structure satisfying the rule of 3', () => { const m = buildOverlapMystery(pool, mulberry32(3)) expect(m).not.toBeNull() expect(verifyRuleOfThree(m!)).toBe(true) // culprit is in all three match sets; every other suspect in at most two. const counts = new Map<string, number>() for (const set of m!.matchSets) for (const d of set) counts.set(d, (counts.get(d) ?? 0) + 1) expect(counts.get(m!.culpritDid)).toBe(3) for (const [did, n] of counts) if (did !== m!.culpritDid) expect(n).toBeLessThanOrEqual(2) })
it('returns null for a pool with no shared traits (all unique attire)', () => { const unique: SuspectAttributes[] = Array.from({ length: 6 }, (_, i) => suspect(`u${i}`, [['tops', `Shirt${i}`, '#000000'], ['headwear', `Hat${i}`]]), ) expect(buildOverlapMystery(unique, mulberry32(1))).toBeNull() })
it('returns null below 5 suspects', () => { expect(buildOverlapMystery(pool.slice(0, 4), mulberry32(1))).toBeNull() })})