SOURCE / PINNED RELEASE
Made of little things.
Pursuit PIT
- Release
- 565f4981bdc3…
- Author-recorded commit
- 197afdbed44b…
- License
- LICENSE
- Author’s source reference
- nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-951414e7fd7
Archive hash verified: 3b0485589942fd5c…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import { createGamepadInput, type GamepadSample } from '../docs/examples/gamepad.js';
const bindings = {
x: [{ axis: 0 }, { button: 14, scale: -1 }, { button: 15 }],
y: [{ axis: 1 }, { button: 12, scale: -1 }, { button: 13 }],
gas: [{ button: 7 }], brake: [{ button: 6 }],
confirm: [{ button: 0 }], back: [{ button: 1 }],
siren: [{ button: 2 }], radio: [{ button: 3 }],
previousCar: [{ button: 4 }], nextCar: [{ button: 5 }],
mute: [{ button: 8 }], pause: [{ button: 9 }],
joinLeft: [{ button: 10 }], joinRight: [{ button: 11 }],
} as const;
export type ControllerAction = keyof typeof bindings;
export type Direction = 'up' | 'down' | 'left' | 'right';
export type Driving = { gas: number; brake: number; steer: number };
const neutral = (): Driving => ({ gas: 0, brake: 0, steer: 0 });
/** One local driver. Never identifies a player by array position or device name. */
export function createControllerInput(options: {
read?: () => ArrayLike<GamepadSample | null>;
active?: () => boolean;
} = {}) {
const input = createGamepadInput(bindings, options);
let slot: number | null = null;
let driving = neutral();
let needsNeutral = true;
let navigationReady = false;
let direction: Direction | null = null;
let repeatAt = 0;
function reset() {
input.reset();
driving = neutral();
needsNeutral = true;
navigationReady = false;
direction = null;
}
return {
get driving() { return driving; },
reset,
dispose: () => input.dispose(),
poll(now: number) {
const frame = input.poll();
const lost = slot !== null && frame.disconnected.includes(slot);
if (lost) { slot = null; reset(); }
let joined = false;
if (slot === null && !lost && frame.status === 'ready') {
const candidate = frame.players.find((pad) => pad.mapped && Object.values(pad.actions).some((a) => a.pressed));
if (candidate) { slot = candidate.index; joined = true; reset(); }
}
const pad = frame.players.find((p) => p.index === slot);
const actions = pad?.actions;
const pressed = new Set<ControllerAction>();
let navigation: Direction | null = null;
driving = neutral();
if (pad && actions && frame.status === 'ready' && !joined && !lost) {
for (const name of Object.keys(bindings) as ControllerAction[]) {
if (actions[name].pressed) pressed.add(name);
}
const gas = actions.gas.value, brake = actions.brake.value, steer = actions.x.value;
if (gas === 0 && brake === 0 && steer === 0) needsNeutral = false;
if (!needsNeutral) driving = { gas, brake, steer };
const x = actions.x.value, y = actions.y.value;
const next = Math.max(Math.abs(x), Math.abs(y)) < 0.5 ? null
: Math.abs(x) > Math.abs(y) ? (x < 0 ? 'left' : 'right') : (y < 0 ? 'up' : 'down');
if (!next) { navigationReady = true; direction = null; }
else if (navigationReady && (next !== direction || now >= repeatAt)) {
navigation = next;
repeatAt = now + (next !== direction ? 350 : 120);
direction = next;
}
} else {
needsNeutral = true;
navigationReady = false;
direction = null;
}
const status = frame.status === 'ready'
? slot !== null ? 'Controller connected'
: frame.players.some((p) => p.mapped) ? 'Press a controller button to join'
: 'Controller layout unsupported — use keyboard or touch'
: frame.status === 'blocked' ? 'Controller access blocked — use keyboard or touch'
: frame.status === 'unavailable' ? 'Controller input unavailable — use keyboard or touch'
: frame.status === 'inactive' ? 'Focus the game for controller input'
: 'Connect a controller, then press a button';
return { pressed, navigation, lost, joined, status };
},
};
}
/** Each source keeps its own held state; opposite equal steering signals cancel. */
export function mixDriving(held: ReadonlySet<string>, pad: Driving) {
const keyboardSteer = Number(held.has('right')) - Number(held.has('left'));
const steer = keyboardSteer === -pad.steer ? 0
: Math.abs(keyboardSteer) > Math.abs(pad.steer) ? keyboardSteer : pad.steer;
return {
throttle: Math.max(Number(held.has('gas')), pad.gas) - Math.max(Number(held.has('brake')), pad.brake),
steer,
};
}
