| 1 | import test from 'node:test'; |
| 2 | import assert from 'node:assert/strict'; |
| 3 | import { readFileSync } from 'node:fs'; |
| 4 | import { PetNative } from '../dist/core/pet-native.js'; |
| 5 | import { PetWorld } from '../dist/core/pet-world.js'; |
| 6 | import { PetSim, digest } from '../dist/core/pet-sim.js'; |
| 7 | import { compilePetTelemetry, encodePetJSONL } from '../dist/core/pet-telemetry.js'; |
| 8 | import { petDemoEvents } from '../dist/core/pet-demo.js'; |
| 9 | |
| 10 | const points = readFileSync(new URL('../public/whale-points.tsv', import.meta.url), 'utf8').trim().split('\n').map(row => row.trim().split(/\s+/).map(Number)); |
| 11 | const tape = compilePetTelemetry(petDemoEvents(), 80_000); |
| 12 | |
| 13 | test('native hosts replay checkpoint-free exports from the beginning in their recorded expression version', () => { |
| 14 | for (const version of [1, 2]) { |
| 15 | const source = new PetNative(JSON.stringify(points), encodePetJSONL(tape), '[]', false, version); |
| 16 | for (let i = 0; i < 540; i++) source.step(1 / 30, true); |
| 17 | const recording = JSON.parse(source.recording()); |
| 18 | if (version === 1) delete recording.expressionVersion; |
| 19 | const restored = new PetNative(JSON.stringify(points)); |
| 20 | restored.restoreRecording(JSON.stringify(recording)); |
| 21 | assert.equal(JSON.parse(restored.snapshot()).timeMs, 0); |
| 22 | assert.equal(JSON.parse(restored.recording()).expressionVersion, version); |
| 23 | const expected = new PetNative(JSON.stringify(points), encodePetJSONL(tape), '[]', false, version); |
| 24 | for (let i = 0; i < 720; i++) assert.equal(restored.step(1 / 30, true), expected.step(1 / 30, true)); |
| 25 | const before = restored.recording(true); |
| 26 | recording.checkpoint = null; |
| 27 | assert.throws(() => restored.restoreRecording(JSON.stringify(recording))); |
| 28 | assert.equal(restored.recording(true), before); |
| 29 | } |
| 30 | }); |
| 31 | |
| 32 | test('recordings without an expression version retain v1 after checkpoint hydration and subsequent work', () => { |
| 33 | const legacy = new PetNative(JSON.stringify(points), encodePetJSONL(tape), '[]', false, 1); |
| 34 | for (let i = 0; i < 540; i++) legacy.step(1 / 30, true); |
| 35 | const saved = JSON.parse(legacy.recording(true)); |
| 36 | delete saved.expressionVersion; delete saved.checkpoint.sim.expressionVersion; |
| 37 | const restored = new PetNative(JSON.stringify(points)); |
| 38 | restored.restoreRecording(JSON.stringify(saved)); |
| 39 | assert.equal(JSON.parse(restored.recording()).expressionVersion, 1); |
| 40 | for (let i = 0; i < 240; i++) assert.equal(restored.step(1 / 30, i % 40 < 30), legacy.step(1 / 30, i % 40 < 30)); |
| 41 | }); |
| 42 | |
| 43 | test('unknown or mismatched expression versions are rejected before replacing the current habitat', () => { |
| 44 | const pet = new PetNative(JSON.stringify(points)); pet.step(1, true); |
| 45 | const before = pet.recording(true), saved = JSON.parse(before); |
| 46 | for (const edit of [r => r.expressionVersion = 3, r => r.expressionVersion = null, |
| 47 | r => r.checkpoint.sim.expressionVersion = 99, r => r.checkpoint.sim.expressionVersion = 1]) { |
| 48 | const candidate = structuredClone(saved); edit(candidate); |
| 49 | assert.throws(() => pet.restoreRecording(JSON.stringify(candidate)), /version/); |
| 50 | assert.equal(pet.recording(true), before); |
| 51 | } |
| 52 | }); |
| 53 | |
| 54 | test('field expressions preserve all particle identities and the unchanged home form', () => { |
| 55 | const old = new PetSim(points, 0xC0FFEE, 1), field = new PetSim(points); |
| 56 | const state = { activity: .1, coherence: .9, attention: 0, channel: 'reasoning', observed: 1, roamX: 0, roamY: 0, flip: 1, lit: 1 }; |
| 57 | for (let i = 0; i < 60; i++) { old.step(1 / 30, state, { motion: true, sensitivity: 1 }); field.step(1 / 30, state, { motion: true, sensitivity: 1 }); } |
| 58 | assert.equal(digest(old), digest(field)); |
| 59 | state.activity = .8; state.channel = 'code'; |
| 60 | for (let i = 0; i < 120; i++) { old.step(1 / 30, state, { motion: true, sensitivity: 1 }); field.step(1 / 30, state, { motion: true, sensitivity: 1 }); } |
| 61 | assert.notEqual(digest(old), digest(field)); |
| 62 | assert.deepEqual(field.checkpoint().body, old.checkpoint().body); |
| 63 | assert.equal(field.p.length, 980); |
| 64 | }); |
| 65 | |
| 66 | test('new worlds show a human junction without the legacy approach-to-owner steering', () => { |
| 67 | const tape = compilePetTelemetry([{ schemaVersion: 1, id: 'request', traceId: 't', startTime: 0, endTime: 40_000, |
| 68 | agentId: 'parent', category: 'human', name: 'request', status: 'pending', attributes: {} }], 40_000); |
| 69 | const legacy = new PetWorld(points, tape, [], 1), modern = new PetWorld(points, tape); |
| 70 | for (let i = 0; i < 900; i++) { legacy.step(1 / 30); modern.step(1 / 30); } |
| 71 | assert.equal(modern.frame.needs, 'call'); // Recorded request remains visible. |
| 72 | assert.equal(legacy.checkpoint().targetX, 0); |
| 73 | assert.notEqual(modern.checkpoint().targetX, 0); |
| 74 | assert.deepEqual(modern.tape, legacy.tape); |
| 75 | }); |
| 76 |