Back to Big Rig Rush
SOURCE / PINNED RELEASE

Made of little things.

Big Rig Rush

Release
ec9b6549f752…
Author-recorded commit
74b729258b1a…
License
LICENSE
Author’s source reference
nostr://npub1sj2vketepqw9v6fmge5fdzuj7atj9sv6qv2jss05n2z6rl64lcfqumdsnt/wss%3A%2F%2Fgit.napplet.soy%2F/n-0a99f0cae40

Archive hash verified: 900e22810340ccb1…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

tests/traffic-routing.test.mjs
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import ts from 'typescript';
const source = readFileSync(new URL('../src/game/traffic-routing.ts', import.meta.url), 'utf8');
const js = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ES2022 } }).outputText;
const { TrafficRouting } = await import(`data:text/javascript;base64,${Buffer.from(js).toString('base64')}`);
function lane(id, y = 0, next = [], extras = {}) {
  return { id, pts: [{x:0,y}, {x:400,y}], cum:[0,400], len:400, x0:0,x1:400,y0:y,y1:y,
    layer0:0,layer1:0,breakS:0,next,kind:'street',speed:50,...extras };
}
const fire = {x:200,y:0,r:20,layer:0,hard:true};
test('intersects segment interiors and separates ground, deck and ramp intervals', () => {
  const routing = new TrafficRouting([lane(0),lane(1,0,[],{layer0:1,layer1:1}),lane(2,0,[],{layer1:1,breakS:200})]);
  routing.refresh([fire],0);
  assert.equal(routing.stopAt(0,0,0),180);
  assert.equal(routing.stopAt(1,0,0),Infinity);
  assert.equal(routing.stopAt(2,0,0),180);
  assert.equal(routing.stopAt(2,210,0),Infinity);
  assert.equal(routing.safeSpawn(0,170,0),false);
  assert.equal(routing.safeSpawn(1,170,0),true);
});
test('awareness is delayed at distance, immediate nearby, and preserves committed good choices', () => {
  const lanes=[lane(0,300,[1,2]),lane(1,200,[3],{kind:'connector'}),lane(2,300,[4],{kind:'connector'}),lane(3),lane(4,300)];
  const routing=new TrafficRouting(lanes); routing.refresh([fire],0);
  assert.equal(routing.choose(lanes[0],0,8,0,500,1,()=>0),1);
  assert.equal(routing.choose(lanes[0],0,8,200,80,1,()=>0),2);
  for (let t=0.5;t<=8;t+=0.5) routing.refresh([fire],t);
  assert.equal(routing.choose(lanes[0],8,8,0,500,1,()=>0),2);
  assert.equal(routing.choose(lanes[0],8,8,0,500,2,()=>0),2);
});
test('cleanup removes physical stop, then fades route caution and reopens spawning', () => {
  const routing=new TrafficRouting([lane(0)]); routing.refresh([fire],0);
  routing.refresh([],1);
  assert.equal(routing.stopAt(0,0,1),Infinity);
  assert.ok(routing.cost(0,1,0,0,0)>0);
  assert.equal(routing.safeSpawn(0,170,1),true);
  routing.refresh([],13);
  assert.equal(routing.cost(0,13,0,0,0),0);
  routing.clear(); assert.equal(routing.stopAt(0,0,13),Infinity);
});
test('radius and layer changes update geometry, while a car inside danger can escape', () => {
  const routing=new TrafficRouting([lane(0)]); routing.refresh([fire],0);
  routing.refresh([{...fire,r:40}],0.5);
  assert.equal(routing.stopAt(0,0,0.5),160);
  assert.equal(routing.stopAt(0,200,0.5),Infinity);
  routing.refresh([{...fire,layer:1}],1);
  assert.equal(routing.stopAt(0,0,1),Infinity);
});
test('cycles and branching searches remain finite', () => {
  const lanes=Array.from({length:30},(_,id)=>lane(id,id*10,Array.from({length:8},(_,n)=>(id+n+1)%30)));
  const routing=new TrafficRouting(lanes); routing.refresh([fire],0);
  assert.ok(lanes[0].next.includes(routing.choose(lanes[0],0,0,200,0,-1,()=>0)));
});

test('real traffic waits outside fire beyond jam timeout, then moves after cleanup', async () => {
  const { mkdtempSync, readdirSync, writeFileSync, rmSync } = await import('node:fs');
  const { tmpdir } = await import('node:os');
  const { join } = await import('node:path');
  const { pathToFileURL } = await import('node:url');
  const dir=mkdtempSync(join(tmpdir(),'traffic-test-'));
  const originalRandom=Math.random;
  try {
    writeFileSync(join(dir,'package.json'),'{"type":"module"}');
    for (const name of readdirSync(new URL('../src/game/',import.meta.url)).filter((n)=>n.endsWith('.ts'))) {
      const contents=readFileSync(new URL(`../src/game/${name}`,import.meta.url),'utf8');
      writeFileSync(join(dir,name.replace(/\.ts$/,'.js')),ts.transpileModule(contents,{compilerOptions:{target:ts.ScriptTarget.ES2022,module:ts.ModuleKind.ES2022}}).outputText);
    }
    const { Traffic, Car }=await import(pathToFileURL(join(dir,'traffic.js')));
    const { makeLook }=await import(pathToFileURL(join(dir,'vehicles.js')));
    Math.random=()=>0.5;
    let fires=[{x:200,y:0,r:20}];
    const road=lane(0,0,[0]);
    const city={lanes:[road],laneSignal:new Map()};
    const traffic=new Traffic(city,{roadFires:()=>fires});
    const car=new Car(makeLook('sedan','#888','#aaa',false));
    car.temper=0.9; car.speed=40; car.next=0;
    traffic.cars.push(car);
    const truck={x:0,y:200,layer:0,circles:()=>[]};
    const view={x:-100,y:-100,w:1000,h:1000};
    for(let i=0;i<900;i++) traffic.update(1/30,truck,view,0);
    assert.ok(car.s<180-car.body.halfL,`stopped at ${car.s}`);
    assert.ok(car.speed<0.1);
    assert.ok(car.ghost<=0,'jam timeout must not bypass fire');
    const stopped=car.s;
    fires=[];
    for(let i=0;i<60;i++) traffic.update(1/30,truck,view,0);
    assert.ok(car.s>stopped+20,'traffic resumes after physical danger clears');
    traffic.clear(); assert.equal(traffic.cars.length,0);
  } finally { Math.random=originalRandom; rmSync(dir,{recursive:true,force:true}); }
});