Repository navigation
Expand file tree
/
Copy pathrunner.test.mjs
More file actions
17 lines (13 loc) · 4.14 KB
/
Copy pathrunner.test.mjs
File metadata and controls
17 lines (13 loc) · 4.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import {test} from 'node:test';import assert from 'node:assert/strict';import {createRunner,runnerPose,runnerCharacters} from './runner.mjs';
const ctx={fillRect(){},beginPath(){},moveTo(){},lineTo(){},stroke(){},ellipse(){},fill(){},fillText(){}};
test('robot jumps approaching beat hurdles, recharges after silence and resumes when audio returns',()=>{const r=createRunner(),loud=Array(32).fill(.8),quiet=Array(32).fill(0);let state,maxJump=0;for(let i=0;i<130;i++){state=r.draw(ctx,760,300,i*40,loud);maxJump=Math.max(maxJump,state.jump)}assert.ok(maxJump>20,'runner automatically jumps');assert.equal(state.charging,false);for(let i=130;i<240;i++)state=r.draw(ctx,760,300,i*40,quiet);assert.equal(state.charging,true);assert.ok(state.speed<5);state=r.draw(ctx,760,300,9640,loud);assert.equal(state.charging,false);assert.ok(state.speed>5)});
test('brief musical gaps do not trigger recharge',()=>{const r=createRunner();let state;for(let i=0;i<30;i++)state=r.draw(ctx,760,300,i*40,Array(32).fill(0));assert.equal(state.charging,false)});
test('running speed changes scroll pace while recharge still stops the runner',()=>{function sample(rate){const r=createRunner();let state;for(let i=0;i<90;i++)state=r.draw(ctx,760,300,i*40,Array(32).fill(.2),1.8,rate);return state.speed}assert.ok(sample(2)>sample(.5)*3.9)});
test('run cycle has ten distinct silhouettes and interpolates smoothly across the loop',()=>{const poses=Array.from({length:10},(_,i)=>runnerPose(i/10));assert.equal(new Set(poses.map(p=>JSON.stringify(p))).size,10);assert.deepEqual(runnerPose(0),runnerPose(1));const before=runnerPose(.9999),after=runnerPose(.0001);assert.ok(before.every((v,i)=>Math.abs(v-after[i])<.1));assert.ok(poses.some(p=>p[0]<0)&&poses.some(p=>p[0]>0),'body rises and compresses');});
test('tapping robot cycles all seventeen characters; skyline heights follow EQ bands',()=>{let bars=[];const c={...ctx,fillRect(x,y,w,h){if(['#103c3e','#302043','#42152f'].includes(this.fillStyle))bars.push(h)}};const r=createRunner();let state=r.draw(c,760,300,100,Array(32).fill(0));const quiet=bars.slice();bars=[];r.draw(c,760,300,140,Array(32).fill(.7));assert.ok(Math.max(...bars)>Math.max(...quiet)+100);assert.equal(r.tap(700,5),null);const names=[];for(let i=0;i<runnerCharacters.length;i++){const b=state.bounds;names.push(r.tap((b.left+b.right)/2,(b.top+b.bottom)/2).name)}assert.deepEqual(names,[...runnerCharacters.slice(1),runnerCharacters[0]]);});
test('each character has a distinct animated idle and wakes when audio returns',()=>{const animations=[];for(let character=0;character<17;character++){const r=createRunner();r.setCharacter(character);let state;for(let i=0;i<100;i++)state=r.draw(ctx,760,300,i*40,Array(32).fill(0));animations.push(state.idleAnimation);assert.equal(state.charging,true);state=r.draw(ctx,760,300,4040,Array(32).fill(.8));assert.equal(state.idleAnimation,null);assert.equal(state.charging,false)}assert.deepEqual(animations,['recharge','decks','catnap','float','delivery','rewind','polish','bubbles','boardspin','campfire','fade','egg','conjure','noodles','route','starchart','drums']);});
test('all seventeen character backgrounds draw at 15 percent opacity and restore foreground opacity',async()=>{const {drawRunnerEnvironment,runnerEnvironments}=await import('./runner-environments.mjs');assert.equal(runnerEnvironments.length,17);let opacities=[];const c={...ctx,globalAlpha:1,stroke(){opacities.push(this.globalAlpha)}};for(let i=0;i<17;i++){opacities=[];drawRunnerEnvironment(c,760,300,1000,i);assert.ok(opacities.length>0);assert.ok(opacities.every(a=>a===.15));assert.equal(c.globalAlpha,1)}});
test('skyline sensitivity changes only EQ height, independently of pace and obstacles',()=>{
function measure(gain){let heights=[];const c={...ctx,fillRect(x,y,w,h){if(['#103c3e','#302043','#42152f'].includes(this.fillStyle))heights.push(h)}};const r=createRunner();const state=r.draw(c,760,300,100,Array(32).fill(.6),1,1.7,gain);return{state,height:Math.max(...heights)}}
const low=measure(.2),normal=measure(1);assert.ok(low.height<normal.height*.3);assert.equal(low.state.speed,normal.state.speed);assert.equal(low.state.obstacles,normal.state.obstacles);assert.equal(low.state.jump,normal.state.jump);
});