Back to Outbreak: Soycoon City
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.

tests/controller.test.mjs
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { testModules } from '../scripts/test-modules.mjs';
const { GameController, mergeControls } = await testModules();
const sample=(index=0,mapping='standard')=>({index,id:'synthetic controller',mapping,connected:true,axes:[0,0,0,0],buttons:Array.from({length:17},()=>({value:0,pressed:false,touched:false}))});
test('controller dead zone, normalized tank movement and single action edges',()=>{
  const pad=sample(3);let active=true;const actions=[];
  const c=new GameController({read:()=>[null,null,null,pad],active:()=>active});const poll=()=>c.poll(true,a=>actions.push(a));
  pad.buttons[7].value=1;poll();assert.deepEqual(actions,[]); // Held at connection is not a shot.
  pad.buttons[7].value=0;pad.axes=[.1,-.1,0,0];poll();assert.equal(c.controls.forward,0);assert.equal(c.controls.turn,0);
  pad.axes=[.6,-.6,0,0];pad.buttons[6].value=1;pad.buttons[7].value=1;poll();poll();
  assert(Math.abs(c.controls.forward-.5)<1e-9);assert(Math.abs(c.controls.turn+.5)<1e-9);assert(c.controls.aim);assert.deepEqual(actions,['shoot']);
  active=false;poll();assert.equal(c.controls.forward,0);assert(!c.controls.aim);
  active=true;poll();assert.deepEqual(actions,['shoot']);
  pad.buttons[7].value=0;poll();pad.buttons[7].value=1;poll();assert.deepEqual(actions,['shoot','shoot']);c.dispose();
});
test('disconnect pauses, sparse replacement slots work, unknown mappings fall back',()=>{
  let pads=[sample(0),sample(1)];const actions=[];const c=new GameController({read:()=>pads,active:()=>true});
  const poll=()=>c.poll(true,a=>actions.push(a));poll();pads[0].axes[1]=-1;poll();assert.equal(c.controls.forward,1);
  pads=[null,pads[1]];poll();assert.deepEqual(actions,['controllerLost']);assert.equal(c.controls.forward,0);
  poll();assert.equal(c.slot,1);pads=[sample(0,'')];poll();poll();assert.match(c.status,/unknown/);assert.equal(c.controls.forward,0);c.dispose();
});
test('disabled controller actions and reset do not replay held buttons; keyboard/touch combine',()=>{
  const pad=sample(),actions=[],c=new GameController({read:()=>[pad],active:()=>true});c.poll(false,a=>actions.push(a));
  pad.buttons[9].value=1;c.poll(false,a=>actions.push(a));c.poll(true,a=>actions.push(a));assert.deepEqual(actions,[]);
  c.reset();c.poll(true,a=>actions.push(a));assert.deepEqual(actions,[]);
  assert.deepEqual(mergeControls({forward:1,turn:0,run:false,aim:true},{forward:.7,turn:-.5,run:true,aim:false}),{forward:1,turn:-.5,run:true,aim:true,aimUp:false});c.dispose();
});

test('upward aim requires LT and right-stick up; release clears intent',()=>{
 const pad=sample(),c=new GameController({read:()=>[pad],active:()=>true});const poll=()=>c.poll(true,()=>{});poll();pad.axes[3]=-1;poll();assert(!c.controls.aimUp);pad.buttons[6].value=1;poll();assert(c.controls.aimUp);pad.axes[3]=0;poll();assert(!c.controls.aimUp);c.dispose();
});