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)}`, ); } }