This commit is contained in:
Andras Schmelczer 2026-08-22 15:29:33 +01:00
commit b9274a42c7
2 changed files with 480 additions and 457 deletions

View file

@ -1,3 +1,5 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`shared character simulation determinism > matches the pinned reference pose (changes only with intentional physics edits) 1`] = `"{"head":[76.14,123.412],"leftFoot":[83.214,105.036],"rightFoot":[79.974,123.01],"bodyVelocity":[0,0]}"`;
exports[`shared character simulation determinism > matches the pinned reference pose on a spinning planet 1`] = `"{"head":[320.9,-33.986],"leftFoot":[248.603,-50.839],"rightFoot":[281.287,-71.994],"direction":-0.314,"onSurface":true,"secondsSinceOnSurface":0}"`;

View file

@ -13,7 +13,12 @@ const { vec2 } = require('../shared/node_modules/gl-matrix');
const {
stepCharacterMovement,
tickPlanetDetachment,
resolveCircleMovement,
planetDistance,
planetGravity,
PlanetBase,
Random,
headRadius,
feetRadius,
headOffset,
@ -42,6 +47,79 @@ const emptyWorld = {
const stepSeconds = 1 / 200;
// A spinning planet, built exactly the way create-world builds one, wired up as
// the GroundSurface the shared movement collides against and is pulled by. The
// free-space run above deliberately avoids planet geometry, so without this the
// SDF, the gravity falloff and the on-surface branch — the parts most likely to
// be touched by a perf rewrite — have no pin at all.
const makeSpinningPlanetWorld = () => {
Random.seed = 20;
const vertices = PlanetBase.createPlanetVertices(
vec2.fromValues(0, -900),
1700,
1900,
70,
);
const base = new PlanetBase(1, vertices);
let rotation = 0;
const surface = {
canCollide: true,
isGround: true,
center: base.center,
angularVelocity: 0.09,
advance: (dt) => (rotation += surface.angularVelocity * dt),
distance: (target) =>
planetDistance(target, vertices, base.center, Math.cos(rotation), Math.sin(rotation)),
gravityAt: (target) => planetGravity(base.center, base.radius, target),
};
const grounds = [surface];
return {
surface,
world: {
groundsNear: () => grounds,
stepBody: (body, dt) => {
const { hitObject } = resolveCircleMovement(body, dt, grounds);
return hitObject && hitObject.isGround ? hitObject : undefined;
},
},
};
};
// Walk a body across a spinning planet: gravity, the SDF outline, collision
// resolution, surface latching and the rotation carry all participate.
const runPlanetSimulation = () => {
const { surface, world } = makeSpinningPlanetWorld();
const start = vec2.fromValues(0, 250);
const state = {
head: makeBody(vec2.add(vec2.create(), start, headOffset), headRadius),
leftFoot: makeBody(vec2.add(vec2.create(), start, leftFootOffset), feetRadius),
rightFoot: makeBody(vec2.add(vec2.create(), start, rightFootOffset), feetRadius),
direction: 0,
currentPlanet: undefined,
secondsSinceOnSurface: 1,
bodyVelocity: vec2.create(),
};
for (let i = 0; i < 900; i++) {
// Fall in, then walk right, then left.
const input =
i < 200 ? vec2.create() : vec2.fromValues(i < 600 ? 1 : -1, 0);
tickPlanetDetachment(state, stepSeconds);
stepCharacterMovement(state, world, input, stepSeconds);
surface.advance(stepSeconds);
}
const round = (v) => Math.round(v * 1000) / 1000;
return JSON.stringify({
head: [round(state.head.center[0]), round(state.head.center[1])],
leftFoot: [round(state.leftFoot.center[0]), round(state.leftFoot.center[1])],
rightFoot: [round(state.rightFoot.center[0]), round(state.rightFoot.center[1])],
direction: round(state.direction),
onSurface: !!state.currentPlanet,
secondsSinceOnSurface: round(state.secondsSinceOnSurface),
});
};
// Run a fixed, scripted input sequence through the shared sim and return a
// stable string snapshot of the final pose + carried momentum.
const runSimulation = () => {
@ -80,4 +158,14 @@ describe('shared character simulation determinism', () => {
// Regression pin — update deliberately when physics behaviour changes.
expect(runSimulation()).toMatchSnapshot();
});
it('is deterministic on a spinning planet too', () => {
expect(runPlanetSimulation()).toBe(runPlanetSimulation());
});
it('matches the pinned reference pose on a spinning planet', () => {
// Covers the planet SDF, the gravity falloff, collision resolution and the
// rotation carry, none of which the free-space run above touches.
expect(runPlanetSimulation()).toMatchSnapshot();
});
});