import assert from 'node:assert/strict'; import fs from 'node:fs'; import { test } from 'node:test'; import { advanceRunner, createRunner, createRunnerRandom, measuredRunnerFairWindowMs, restartRunner, RUNNER_SEED, runnerProjection, runnerSeedFromSearch, setRunnerInput, startRunner, stepRunner, } from '../src-tauri/resources/agc-skills/agc-browser-playtest/references/runner-physics.mjs'; function jump( settings, holdTicks, functions = { createRunner, startRunner, setRunnerInput, stepRunner, runnerProjection, }, ) { const state = functions.createRunner(RUNNER_SEED, settings); functions.startRunner(state); functions.setRunnerInput(state, 'jump', true); const samples = []; for (let tick = 0; tick < 150; tick += 1) { if (tick === holdTicks) functions.setRunnerInput(state, 'jump', false); functions.stepRunner(state); samples.push(functions.runnerProjection(state)); if (state.onGround && state.jumpCount > 0) break; } return { state, samples, height: Math.max( ...samples.map((sample) => sample.groundY - sample.playerY), ), }; } test('fixed seed, fixed steps and independent decoration produce the same course', () => { assert.equal(runnerSeedFromSearch('?agcPlaytestSeed=20260920'), RUNNER_SEED); assert.throws(() => runnerSeedFromSearch('?agcPlaytestSeed=1&agcPlaytestSeed=2'), ); assert.throws(() => runnerSeedFromSearch('?agcPlaytestSeed=4294967296')); const first = createRunner(); const decoration = createRunnerRandom(71); for (let index = 0; index < 100; index += 1) decoration(); const second = createRunner(); assert.deepEqual(first.course, second.course); assert.notEqual( first.courseFingerprint, createRunner(20260921).courseFingerprint, ); startRunner(first); startRunner(second); for (let index = 0; index < 60; index += 1) advanceRunner(first, 1 / 60); for (let index = 0; index < 120; index += 1) advanceRunner(second, 1 / 120); assert.deepEqual(runnerProjection(first), runnerProjection(second)); const course = structuredClone(first.course); restartRunner(first); assert.equal(first.phase, 'ready'); assert.equal(first.simulationTick, 0); assert.equal(first.jumpCount, 0); assert.deepEqual(first.course, course); }); test('short jump cuts once, long jump rises higher, release restores slide collision size', () => { const short = jump({}, 4); const long = jump({}, 18); assert.equal(short.state.jumpCount, 1); assert.equal(short.state.jumpCutCount, 1); assert.ok(long.height > short.height + 10); assert.ok(short.state.onGround && long.state.onGround); const state = short.state; setRunnerInput(state, 'slide', true); stepRunner(state); assert.ok(state.sliding && state.playerHeight < state.config.playerHeight); setRunnerInput(state, 'slide', false); stepRunner(state); assert.ok(!state.sliding && !state.slideHeld); assert.equal(state.playerHeight, state.config.playerHeight); }); test('baseline has at least 180ms clearance while the original narrow-window parameters fail', () => { const baseline = jump({}, 4); assert.ok( measuredRunnerFairWindowMs( baseline.samples, 49, baseline.state.course[0], ) >= 180, ); const original = jump( { gravity: 2300, jumpVelocity: 800, releaseVelocity: 720, playerWidth: 48.9, }, 1, ); const window = measuredRunnerFairWindowMs( original.samples, 48.9, original.state.course[0], ); assert.ok(window < 180, `original jump window ${window}ms must fail`); }); test('regression mutations expose repeated jump-cut and ignored slide release', async () => { const source = fs.readFileSync( new URL( '../src-tauri/resources/agc-skills/agc-browser-playtest/references/runner-physics.mjs', import.meta.url, ), 'utf8', ); const repeatedCut = source.replace('&& !state.jumpCut)', ')'); assert.notEqual(repeatedCut, source); const broken = await import( `data:text/javascript;base64,${Buffer.from(repeatedCut).toString('base64')}` ); assert.ok(jump({}, 4, broken).state.jumpCutCount > 1); const noRelease = source.replace( 'state.slideHeld = Boolean(held);', 'if (held) state.slideHeld = true;', ); const stuck = await import( `data:text/javascript;base64,${Buffer.from(noRelease).toString('base64')}` ); const state = stuck.createRunner(); stuck.startRunner(state); stuck.setRunnerInput(state, 'slide', true); stuck.stepRunner(state); stuck.setRunnerInput(state, 'slide', false); stuck.stepRunner(state); assert.equal(state.sliding, true); });