Update tests for player movement validation and serialization, add migration for version 33 Player Start jump settings

This commit is contained in:
2026-04-12 02:06:33 +02:00
parent 8aba9a02d7
commit 9459cbd191
3 changed files with 168 additions and 2 deletions

View File

@@ -130,7 +130,9 @@ describe("validateSceneDocument", () => {
bufferMs: -1,
coyoteTimeMs: -1,
variableHeight: "yes",
maxHoldMs: 0
maxHoldMs: 0,
moveWhileJumping: "yes",
moveWhileFalling: 1
},
sprint: {
speedMultiplier: 0
@@ -209,6 +211,12 @@ describe("validateSceneDocument", () => {
expect.objectContaining({
code: "invalid-player-start-variable-jump-max-hold-ms"
}),
expect.objectContaining({
code: "invalid-player-start-move-while-jumping"
}),
expect.objectContaining({
code: "invalid-player-start-move-while-falling"
}),
expect.objectContaining({
code: "invalid-player-start-sprint-speed-multiplier"
}),

View File

@@ -465,7 +465,9 @@ describe("scene document JSON", () => {
bufferMs: 120,
coyoteTimeMs: 90,
variableHeight: true,
maxHoldMs: 220
maxHoldMs: 220,
moveWhileJumping: false,
moveWhileFalling: false
},
sprint: {
speedMultiplier: 1.8
@@ -939,6 +941,40 @@ describe("scene document JSON", () => {
);
});
it("migrates version 33 Player Start jump settings to include default air movement flags", () => {
const playerStart = createPlayerStartEntity({
id: "entity-player-start-legacy-air-move-flags",
movementTemplate: {
kind: "responsive"
}
});
const {
moveWhileJumping: _moveWhileJumping,
moveWhileFalling: _moveWhileFalling,
...legacyJump
} = playerStart.movementTemplate.jump;
const legacyDocument = {
...createEmptySceneDocument({
name: "Legacy Player Air Movement Scene"
}),
version: 33,
entities: {
[playerStart.id]: {
...playerStart,
movementTemplate: {
...playerStart.movementTemplate,
jump: legacyJump
}
}
}
};
const migratedDocument = migrateSceneDocument(legacyDocument);
expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
expect(migratedDocument.entities[playerStart.id]).toEqual(playerStart);
});
it("round-trips authored third-person Player Start navigation", () => {
const playerStart = createPlayerStartEntity({
id: "entity-player-start-third-person",

View File

@@ -382,6 +382,67 @@ describe("player-locomotion", () => {
expect(step?.verticalVelocity).toBeLessThan(0);
});
it("disables jump-phase air movement when move while jumping is off", () => {
const step = stepPlayerLocomotion({
dt: 0.1,
feetPosition: {
x: 0,
y: 1,
z: 0
},
movementYawRadians: 0,
standingShape: FIRST_PERSON_PLAYER_SHAPE,
verticalVelocity: 2,
previousLocomotionState: createIdleRuntimeLocomotionState("airborne"),
previousPlanarDisplacement: {
x: 0,
y: 0,
z: 0
},
jumpBufferRemainingMs: 0,
coyoteTimeRemainingMs: 0,
jumpHoldRemainingMs: 0,
crouched: false,
wasJumpPressed: false,
input: FORWARD_INPUT,
movement: {
...DEFAULT_MOVEMENT,
jump: {
...DEFAULT_MOVEMENT.jump,
moveWhileJumping: false
}
},
resolveMotion: (feetPosition, motion): ResolvedPlayerMotion => ({
feetPosition: {
x: feetPosition.x + motion.x,
y: feetPosition.y + motion.y,
z: feetPosition.z + motion.z
},
grounded: false,
collisionCount: 0,
groundCollisionNormal: null,
collidedAxes: {
x: false,
y: false,
z: false
}
}),
resolveVolumeState: () => createVolumeState(),
probeGround: () => ({
grounded: false,
distance: null,
normal: null,
slopeDegrees: null
}),
canOccupyShape: () => true
});
expect(step).not.toBeNull();
expect(step?.locomotionState.locomotionMode).toBe("airborne");
expect(step?.planarDisplacement.z).toBeCloseTo(0);
expect(step?.locomotionState.planarSpeed).toBeCloseTo(0);
});
it("keeps falling when airborne input pushes into a wall and the pre-move probe flickers grounded", () => {
let probeCount = 0;
@@ -452,6 +513,67 @@ describe("player-locomotion", () => {
expect(step?.verticalVelocity).toBeLessThan(-1.5);
});
it("disables falling air movement when move while falling is off", () => {
const step = stepPlayerLocomotion({
dt: 0.1,
feetPosition: {
x: 0,
y: 1,
z: 0
},
movementYawRadians: 0,
standingShape: FIRST_PERSON_PLAYER_SHAPE,
verticalVelocity: -2,
previousLocomotionState: createIdleRuntimeLocomotionState("airborne"),
previousPlanarDisplacement: {
x: 0,
y: 0,
z: 0
},
jumpBufferRemainingMs: 0,
coyoteTimeRemainingMs: 0,
jumpHoldRemainingMs: 0,
crouched: false,
wasJumpPressed: false,
input: FORWARD_INPUT,
movement: {
...DEFAULT_MOVEMENT,
jump: {
...DEFAULT_MOVEMENT.jump,
moveWhileFalling: false
}
},
resolveMotion: (feetPosition, motion): ResolvedPlayerMotion => ({
feetPosition: {
x: feetPosition.x + motion.x,
y: feetPosition.y + motion.y,
z: feetPosition.z + motion.z
},
grounded: false,
collisionCount: 0,
groundCollisionNormal: null,
collidedAxes: {
x: false,
y: false,
z: false
}
}),
resolveVolumeState: () => createVolumeState(),
probeGround: () => ({
grounded: false,
distance: null,
normal: null,
slopeDegrees: null
}),
canOccupyShape: () => true
});
expect(step).not.toBeNull();
expect(step?.locomotionState.locomotionMode).toBe("airborne");
expect(step?.planarDisplacement.z).toBeCloseTo(0);
expect(step?.locomotionState.planarSpeed).toBeCloseTo(0);
});
it("sinks toward the water surface while keeping the head above water", () => {
const step = stepPlayerLocomotion({
dt: 0.1,