SOURCE / PINNED RELEASE
Made of little things.
Outbreak: Soycoon City
- Release
- db7750b1d009…
- Author-recorded commit
- 3ee3f5fd27a6…
- License
- LICENSE
- Author’s source reference
- nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-b87c04571a9
Archive hash verified: dbebdc772b142a1c…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import ts from 'typescript';
const demo = JSON.parse(await readFile(new URL('../levels/demo.json', import.meta.url), 'utf8'));
import { runInNewContext } from 'node:vm';
async function module(path, dependencies = {}) {
const source = await readFile(new URL(path, import.meta.url), 'utf8');
const js = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText;
const m = { exports: {} }; runInNewContext(js, { module: m, exports: m.exports, require: name => name === '../levels/demo.json' ? { default: demo } : dependencies[name], structuredClone, console }); return m.exports;
}
const world = await module('../src/world.ts');
const { Game } = await module('../src/game.ts', { './world.js': world });
const topology = await module('../src/connections.ts', { './world.js': world });
const idle = { forward: 0, turn: 0, run: false, aim: false };
test('demo map validates and every connection has a working return door', () => {
const stage = world.createStage(); assert.equal(world.parseStage(JSON.stringify(stage)).rooms.length, 6); assert.equal(topology.connections(stage).length, 7);
const g = new Game(stage); g.paused = false;
for (const r of stage.rooms) for (const d of r.doors) { g.room = r; Object.assign(g.player, d); assert.equal(g.interact(), true); assert.equal(g.room.id, d.to); assert.equal(world.blocked(g.room, g.player), false); }
});
test('several doors can share a wall; failed links are atomic and paired removal is complete', () => {
const stage = world.createStage(), a = stage.rooms[3], b = stage.rooms[4];
const count = a.doors.length, targetCount = b.doors.length;
const [first, second] = topology.connectRooms({room:a,side:'south',offset:-2}, {room:b,side:'west',offset:1.8});
const [next] = topology.connectRooms({room:a,side:'south',offset:2}, {room:b,side:'west',offset:-.5});
assert.equal(a.doors.filter(d=>d.side==='south').length,2);
assert.throws(()=>topology.connectRooms({room:a,side:'south',offset:2.1},{room:b,side:'south',offset:0}),/1.4 m/);
assert.equal(a.doors.length,count+2); assert.equal(b.doors.length,targetCount+2);
const original = JSON.stringify(first);
assert.throws(()=>topology.repositionDoor(a,first,{room:a,side:'south',offset:2.2}),/1.4 m/);
assert.equal(JSON.stringify(first),original);
topology.removeConnection(stage,a,first);
assert(!a.doors.includes(first)); assert(!b.doors.includes(second)); assert(a.doors.includes(next));
assert.doesNotThrow(()=>world.parseStage(JSON.stringify(stage)));
});
test('moving room blocks preserves art while door and dressing edits invalidate it', () => {
const stage=world.createStage(), r=stage.rooms[0], c=r.cameras[0], initial=world.signature(r,c);
r.origin.x+=5; assert.equal(world.signature(r,c),initial);
r.dressing[0].x+=.25; assert.notEqual(world.signature(r,c),initial);
assert(stage.rooms.flatMap(r=>r.cameras).some(c=>c.position[1]<.6&&c.target[1]>c.position[1]));
});
test('collision stops movement through props and room bounds while allowing sliding', () => {
const r = world.createStage().rooms[0]; const p = { x: -.5, z: 0 };
world.move(r, p, .2, .3); assert.equal(p.x, -.5); assert.equal(p.z, .3);
const wall = { x: 4.7, z: 2 }; world.move(r, wall, 1, 0); assert.equal(wall.x, 4.7);
});
test('camera zones cut and preserve the player world facing, with edge hysteresis', () => {
const g = new Game(world.createStage()); g.paused = false; g.player.yaw = 2.1;
g.player.z = -2; g.player.x = -1; g.update(.01, idle); assert.equal(g.camera.id, 'cam-2'); assert.equal(g.player.yaw, 2.1);
const r = g.room; assert.equal(world.cameraAt(r, { x: 0, z: -.56 }, 'cam-2').id, 'cam-2'); assert.equal(world.cameraAt(r, { x: 0, z: 1 }, 'cam-2').id, 'cam-1');
});
test('shotgun requires forward cone, respects cover and can kill multiple close targets', () => {
const g = new Game(world.createStage()); g.paused = false; g.player.x = -1.8; g.player.z = 2.2;
g.zombies.forEach(z => z.hp = 0);
g.zombies[0] = { ...g.zombies[0], hp: 2, x: -1.8, z: .5 };
g.zombies[1] = { ...g.zombies[1], hp: 2, x: -1.6, z: .8 };
g.shoot(); assert.equal(g.ammo, 5); assert.equal(g.zombies[0].hp, 0); assert.equal(g.zombies[1].hp, 0); assert.equal(g.won, true);
const b = new Game(world.createStage()); b.paused = false; b.player.x = .9; b.player.z = 2;
b.zombies[0].x = .9; b.zombies[0].z = -1.5; b.shoot(); assert.equal(b.zombies[0].hp, 2);
});
test('reload consumes reserve only at completion; pause stops timers and bites', () => {
const g = new Game(world.createStage()); g.paused = false; g.ammo = 1; g.reload(); assert.equal(g.ammo, 1);
for (let i = 0; i < 31; i++) g.update(.05, idle); assert.equal(g.ammo, 6); assert.equal(g.reserve, 19);
g.zombies[0].x = g.player.x; g.zombies[0].z = g.player.z; g.paused = true; g.update(.05, idle); assert.equal(g.player.hp, 100);
g.paused = false; g.update(.05, idle); assert.equal(g.player.hp, 84);
});
test('invalid imported coordinates, disconnected doors and spawn collisions are rejected', () => {
const s = world.createStage(); s.rooms[0].w = -4; assert.throws(() => world.parseStage(JSON.stringify(s)));
const a = world.createStage(); a.rooms[0].doors[0].targetDoor = 'missing'; assert.throws(() => world.parseStage(JSON.stringify(a)), /matching return/);
const b = world.createStage(); b.rooms[0].spawn = { x: .9, z: 0 }; assert.throws(() => world.parseStage(JSON.stringify(b)), /inside a wall/);
const c = world.createStage(); c.rooms[0].props.push({ id: 'blocked-door', kind: 'crate', x: 3.95, z: 2.9, w: .5, d: .5, h: .5 }); assert.throws(() => world.parseStage(JSON.stringify(c)), /inside the east door/);
});
