Initial commit

This commit is contained in:
ryanfitzpatrickio
2026-08-03 10:28:11 -05:00
parent 65bfc3dcb4
commit 6c08153e42
63 changed files with 15790 additions and 1 deletions
+78
View File
@@ -0,0 +1,78 @@
import * as THREE from 'three';
import { createPhysicsWorld, initPhysics } from '../src/physics/world.js';
import { createMatch } from '../src/game/match.js';
import { describeHit } from '../src/game/hits.js';
/**
* Fire skaters at each other from various run-ups and angles and print what
* comes out. A tuning aid, not a test: the numbers below are the ones you stare
* at when deciding what should count as a bump, a stagger and a knockdown.
*
* node tools/hitprobe.mjs
*/
const DT = 1 / 60;
await initPhysics();
/**
* @param {'stationary'|'full'} mode is the victim skating into it too
* @param {number} gap metres between them at the start
* @param {number} offsetZ lateral offset — 0 is dead centre
*/
function probe(mode, gap, offsetZ = 0) {
const physics = createPhysicsWorld();
const match = createMatch({ scene: new THREE.Group(), physics, perTeam: 1, teams: 2 });
const [a, b] = match.states;
a.x = -gap / 2; a.z = 0; a.yaw = Math.PI / 2;
b.x = gap / 2; b.z = offsetZ; b.yaw = mode === 'full' ? -Math.PI / 2 : Math.PI / 2;
match.skaters[0].proxy.teleport(a.x, a.z);
match.skaters[1].proxy.teleport(b.x, b.z);
match.setControl(0, { x: 1, y: 0, sprint: true, brake: false, cameraYaw: 0 });
match.setControl(1, mode === 'full'
? { x: -1, y: 0, sprint: true, brake: false, cameraYaw: 0 }
: { x: 0, y: 0, sprint: false, brake: false, cameraYaw: 0 });
const seen = new Set();
const out = [];
for (let n = 0; n < 6 / DT; n++) {
match.update(DT);
for (const h of match.recentHits) {
const id = `${h.at}|${h.attacker}`;
if (!seen.has(id)) {
seen.add(id);
out.push(h);
}
}
}
physics.destroy();
return out;
}
const rows = [
['stationary', 2.5, 0],
['stationary', 5, 0],
['stationary', 10, 0],
['stationary', 22, 0],
['stationary', 22, 0.45],
['stationary', 22, -0.45],
['full', 10, 0],
['full', 24, 0],
['full', 24, 0.5],
];
console.log('mode gap offZ | outcome m/s sev limbs description');
console.log('-'.repeat(96));
for (const [mode, gap, off] of rows) {
const hits = probe(mode, gap, off);
if (!hits.length) {
console.log(`${mode.padEnd(11)} ${String(gap).padStart(4)} ${String(off).padStart(5)} | (no hit)`);
continue;
}
for (const h of hits) {
console.log(
`${mode.padEnd(11)} ${String(gap).padStart(4)} ${String(off).padStart(5)} | `
+ `${h.outcome.padEnd(10)} ${h.speed.toFixed(1).padStart(4)} ${h.severity.toFixed(1).padStart(5)} `
+ `${(h.attackerPart + '→' + h.victimPart).padEnd(24)} ${describeHit(h)}`,
);
}
}