| 1 | import test from 'node:test'; |
| 2 | import assert from 'node:assert/strict'; |
| 3 | import { readFileSync } from 'node:fs'; |
| 4 | import { PetSim, REST_STATE, digest, CHANNELS } from '../dist/core/pet-sim.js'; |
| 5 | import { compilePetTelemetry, encodePetJSONL, decodePetJSONL } from '../dist/core/pet-telemetry.js'; |
| 6 | import { PetWorld } from '../dist/core/pet-world.js'; |
| 7 | import { renderPetPCM } from '../dist/core/pet-audio.js'; |
| 8 | import { petDemoEvents } from '../dist/core/pet-demo.js'; |
| 9 | import { importTrace } from '../dist/core/ingest.js'; |
| 10 | |
| 11 | const points = readFileSync(new URL('../public/whale-points.tsv', import.meta.url), 'utf8').trim().split('\n').map(row => row.trim().split(/\s+/).map(Number)); |
| 12 | const event = (overrides = {}) => ({ schemaVersion: 1, id: 'e', traceId: 't', startTime: 0, endTime: 400, |
| 13 | agentId: 'parent', category: 'tool', name: 'work', status: 'success', attributes: {}, ...overrides }); |
| 14 | |
| 15 | test('span time beats onset count; long tools remain measured; containers are excluded', () => { |
| 16 | const e = [event({ id: 'root', category: 'orchestration', endTime: 4000 }), |
| 17 | event({ id: 'long', parentId: 'root', category: 'reasoning', endTime: 4000 }), |
| 18 | ...Array.from({ length: 8 }, (_, i) => event({ id: String(i), startTime: i * 10, endTime: i * 10 }))]; |
| 19 | const b = compilePetTelemetry(e); |
| 20 | assert.equal(b[0].channel, 'reasoning'); assert.equal(b[9].channel, 'reasoning'); |
| 21 | assert.equal(b[0].activeMs[9], 0); assert.equal(b[0].activeMs[0], 400); |
| 22 | }); |
| 23 | test('half-open bins retain endpoint onsets and distinguish unknown duration from idle', () => { |
| 24 | const b = compilePetTelemetry([event({ endTime: 800 }), event({ id: 'last', startTime: 800, endTime: 800, category: 'human' })], 1600); |
| 25 | assert.equal(b[2].channel, 'human'); assert.equal(b[2].onsets[11], 1); assert.equal(b[3].observed, 0); assert.equal(b[3].channel, 'other'); |
| 26 | const open = compilePetTelemetry([event({ openEnded: true, endTime: 3000 })], 1200); |
| 27 | assert.equal(open[0].onsets[1], 1); assert.equal(open[0].activeMs[1], 0); assert.equal(open[1].observed, 0); |
| 28 | }); |
| 29 | test('repeat density is causal; independent future events do not rewrite past buckets', () => { |
| 30 | const first = [event({ endTime: 80 })], future = Array.from({ length: 7 }, (_, i) => event({ id: `f${i}`, startTime: 400 + i * 400, endTime: 480 + i * 400 })); |
| 31 | const a = compilePetTelemetry(first), b = compilePetTelemetry([...first, ...future]); |
| 32 | assert.deepEqual(a[0], b[0]); assert.ok(b[4].coherence < b[0].coherence); |
| 33 | }); |
| 34 | test('errors, human spans and concurrent peers select semantic gaits', () => { |
| 35 | assert.equal(compilePetTelemetry([event({ category: 'reasoning', status: 'error' })])[0].channel, 'error'); |
| 36 | const wait = compilePetTelemetry([event({ category: 'human', status: 'pending' })])[0]; |
| 37 | assert.equal(wait.channel, 'human'); assert.equal(wait.waiting, true); |
| 38 | const peers = compilePetTelemetry(['a', 'b', 'c'].map(id => event({ id, agentId: id })))[0]; |
| 39 | assert.equal(peers.channel, 'agent'); assert.deepEqual(peers.agentIds, ['a', 'b', 'c']); |
| 40 | }); |
| 41 | test('updates deduplicate, multiple traces reject, wire rejects corrupt values', () => { |
| 42 | assert.equal(compilePetTelemetry([event(), event({ endTime: 800 })])[0].onsets[1], 1); |
| 43 | assert.throws(() => compilePetTelemetry([event(), event({ traceId: 'another' })]), /one trace/); |
| 44 | const b = compilePetTelemetry([event()]); assert.deepEqual(decodePetJSONL(encodePetJSONL(b)), b); |
| 45 | for (const edit of [{ activity: null }, { observed: 2 }, { sequence: 8 }, { channel: 'fake' }, { onsets: [1] }]) |
| 46 | assert.throws(() => decodePetJSONL(JSON.stringify({ ...b[0], ...edit }))); |
| 47 | assert.throws(() => compilePetTelemetry([event({ endTime: Infinity })])); |
| 48 | }); |
| 49 | test('reduced motion has no settling, jitter, colour fade or tear decay under constant input', () => { |
| 50 | for (const ch of CHANNELS) { |
| 51 | const sim = new PetSim(points), state = { ...REST_STATE, channel: ch.key, activity: .8, coherence: .4 }; |
| 52 | sim.step(1 / 30, state, { motion: false, sensitivity: 1 }); const first = digest(sim); |
| 53 | for (let i = 0; i < 90; i++) sim.step(1 / 30, state, { motion: false, sensitivity: 1 }); |
| 54 | assert.equal(digest(sim), first, ch.key); |
| 55 | } |
| 56 | }); |
| 57 | test('world and score are invariant to display cadence and repeat replay exactly', () => { |
| 58 | const tape = compilePetTelemetry(petDemoEvents(), 80_000); |
| 59 | const run = dt => { |
| 60 | const world = new PetWorld(points, tape), voices = [...world.voices]; |
| 61 | for (let i = 0; i < Math.round(36 / dt); i++) { world.step(dt); voices.push(...world.voices); } |
| 62 | return { frame: world.frame, digest: digest(world.sim), voices }; |
| 63 | }; |
| 64 | const a = run(1 / 30); assert.deepEqual(run(1 / 60), a); assert.deepEqual(run(1 / 10), a); |
| 65 | assert.equal(a.frame.behaviour === 'doze', false); |
| 66 | assert.ok(a.voices.some(v => v.id.startsWith('tear:'))); assert.ok(a.voices.some(v => v.id.startsWith('address:'))); |
| 67 | }); |
| 68 | test('sleep remains observed in wild mode; recorded gaps stay hollow; touch wakes without inventing coverage', () => { |
| 69 | const world = new PetWorld(points, [], [{ timeMs: 80_000, kind: 'attention', x: .2, y: 0 }]); |
| 70 | for (let i = 0; i < 79 * 30; i++) world.step(1 / 30); |
| 71 | assert.equal(world.frame.behaviour, 'doze'); assert.equal(world.frame.state.observed, 1); assert.ok(world.frame.state.lit < .3); |
| 72 | for (let i = 0; i < 30; i++) world.step(1 / 30); |
| 73 | assert.equal(world.frame.behaviour, 'wake'); assert.equal(world.frame.state.channel, 'human'); |
| 74 | const unknown = new PetWorld(points, compilePetTelemetry([], 90_000), [{ timeMs: 1000, kind: 'attention', x: 0, y: 0 }]); |
| 75 | unknown.step(1); assert.equal(unknown.frame.state.channel, 'human'); assert.equal(unknown.sim.frame.hollow, true); |
| 76 | }); |
| 77 | test('pod identity survives membership changes; needs escalate on actual pending spans', () => { |
| 78 | const events = ['c', 'a', 'b'].map(agentId => event({ id: agentId, agentId, endTime: 800 })); |
| 79 | events.push(event({ id: 'b2', agentId: 'b', startTime: 1200, endTime: 1600 })); |
| 80 | const world = new PetWorld(points, compilePetTelemetry(events, 2000)); |
| 81 | const slots = new Map(world.frame.pod.map(m => [m.id, m.slot])); world.step(1.3); |
| 82 | assert.equal(world.frame.pod.find(m => m.id === 'b').slot, slots.get('b')); |
| 83 | assert.equal(world.frame.pod.find(m => m.id === 'a').present, false); |
| 84 | const waiting = new PetWorld(points, compilePetTelemetry([event({ category: 'human', status: 'pending', endTime: 30_000 })])); |
| 85 | waiting.step(9); assert.equal(waiting.frame.needs, 'approach'); waiting.step(10); waiting.step(8); assert.equal(waiting.frame.needs, 'call'); |
| 86 | }); |
| 87 | test('PCM absolute samples match split chunks including tear noise and remain bounded', () => { |
| 88 | const voices = [{ id: 'tear', start: .05, duration: .3, frequency: 185, gain: .1, pan: -.2, kind: 'noise' }, |
| 89 | { id: 'tone', start: 0, duration: .44, frequency: 130.81, gain: .08, pan: .3, kind: 'tone' }]; |
| 90 | const whole = renderPetPCM(voices, 0, 24_000), a = renderPetPCM(voices, 0, 9123), b = renderPetPCM(voices, 9123, 14_877); |
| 91 | assert.deepEqual([...a.left, ...b.left], [...whole.left]); assert.deepEqual([...a.right, ...b.right], [...whole.right]); |
| 92 | assert.ok(whole.left.some(n => n !== 0)); assert.ok(whole.left.every(n => Number.isFinite(n) && Math.abs(n) <= 1)); |
| 93 | assert.throws(() => renderPetPCM(voices, -1, 4)); |
| 94 | }); |
| 95 | |
| 96 | test('live accepted packets and interactions replay the same frames and audio including expired gaps', () => { |
| 97 | const live = new PetWorld(points, compilePetTelemetry([])), voices = [...live.voices], frames = []; |
| 98 | const packets = compilePetTelemetry(petDemoEvents(), 80_000); |
| 99 | for (let tick = 0; tick < 180; tick++) { |
| 100 | if ([4, 29, 92].includes(tick)) live.acceptTelemetry(packets[tick]); |
| 101 | if (tick === 73) live.interact('attention', .4, -.2); |
| 102 | live.step(1 / 30); voices.push(...live.voices); frames.push(digest(live.sim)); |
| 103 | } |
| 104 | const replay = new PetWorld(points, live.tape, live.interactions), replayVoices = [...replay.voices]; |
| 105 | for (let tick = 0; tick < 180; tick++) { |
| 106 | replay.step(1 / 30); replayVoices.push(...replay.voices); assert.equal(digest(replay.sim), frames[tick], `tick ${tick}`); |
| 107 | } |
| 108 | assert.deepEqual(replayVoices, voices); assert.equal(live.frame.state.observed, 0); |
| 109 | }); |
| 110 | test('metadata privacy preserves only boolean container markers needed by every live and replay driver', () => { |
| 111 | const events = [event({ id: 'container', category: 'reasoning', attributes: { 'whalesong.container': true, 'codewhale.container': 'private text' } }), event({ id: 'work' })]; |
| 112 | const [trace] = importTrace(JSON.stringify(events), 'fixture', { privacy: 'metadata' }); |
| 113 | assert.deepEqual(trace.events[0].attributes, { 'whalesong.container': true }); |
| 114 | assert.equal(compilePetTelemetry(trace.events)[0].channel, 'tool'); |
| 115 | }); |
| 116 | |
| 117 | test('successive peer groups reuse vacant slots while continuously present identities keep their slot and phase', () => { |
| 118 | const events = ['a', 'b', 'c', 'd', 'e', 'f'].map(id => event({ id, agentId: id, endTime: 800 })); |
| 119 | events.push(...['b', 'g', 'h', 'i', 'j', 'k', 'l'].map(id => event({ id: `next-${id}`, agentId: id, startTime: 800, endTime: 1600 }))); |
| 120 | const world = new PetWorld(points, compilePetTelemetry(events)), before = world.frame.pod.find(p => p.id === 'b'); |
| 121 | world.step(1); |
| 122 | assert.deepEqual(world.frame.pod.find(p => p.id === 'b'), before); |
| 123 | assert.equal(world.frame.pod.filter(p => p.present).length, 6); |
| 124 | assert.ok(world.frame.pod.some(p => p.id === 'k')); assert.ok(!world.frame.pod.some(p => p.id === 'a')); |
| 125 | assert.equal(new Set(world.frame.pod.map(p => p.slot)).size, 6); |
| 126 | }); |
| 127 | test('multiple inputs before one fixed tick are all journalled and replay together', () => { |
| 128 | const world = new PetWorld(points); |
| 129 | world.interact('food', -.4, .3); world.interact('attention', .2, .1); world.step(1 / 30); |
| 130 | assert.equal(world.interactions.length, 2); assert.equal(world.frame.food.x, -.4); |
| 131 | const replay = new PetWorld(points, [], world.interactions); replay.step(1 / 30); |
| 132 | assert.deepEqual(replay.frame, world.frame); assert.equal(digest(replay.sim), digest(world.sim)); |
| 133 | }); |
| 134 |