auto-git:

[change] tests/domain/build-runtime-scene.test.ts
 [change] tests/domain/scene-document-validation.test.ts
 [change] tests/serialization/scene-document-json.test.ts
 [change] tests/unit/entity-instances.test.ts
 [change] tests/unit/viewport-focus.test.ts
This commit is contained in:
2026-04-22 17:21:14 +02:00
parent a72b321027
commit 7932706a44
5 changed files with 327 additions and 1 deletions

View File

@@ -13,6 +13,8 @@ import { createDefaultProjectTimeSettings } from "../../src/document/project-tim
import { createTerrain } from "../../src/document/terrains";
import { createEmptySceneDocument } from "../../src/document/scene-document";
import {
createCameraRigEntity,
createCameraRigEntityTargetRef,
DEFAULT_PLAYER_START_MOVE_SPEED,
DEFAULT_PLAYER_START_MOVEMENT_CAPABILITIES,
createNpcEntity,
@@ -42,6 +44,86 @@ import { createFixtureLoadedModelAssetFromGeometry } from "../helpers/model-coll
const defaultMovementTemplate = createPlayerStartMovementTemplate();
describe("buildRuntimeSceneFromDocument", () => {
it("builds enabled fixed camera rigs into runtime entities", () => {
const interactable = createInteractableEntity({
id: "entity-interactable-camera-anchor",
position: {
x: 4,
y: 1,
z: -2
},
prompt: "Anchor"
});
const cameraRig = createCameraRigEntity({
id: "entity-camera-rig-overlook",
position: {
x: 8,
y: 3,
z: -6
},
priority: 7,
defaultActive: false,
target: createCameraRigEntityTargetRef(interactable.id),
targetOffset: {
x: 0,
y: 1.5,
z: 0
},
transitionMode: "blend",
transitionDurationSeconds: 0.6,
lookAround: {
enabled: true,
yawLimitDegrees: 10,
pitchLimitDegrees: 6,
recenterSpeed: 5
}
});
const disabledCameraRig = createCameraRigEntity({
id: "entity-camera-rig-disabled",
enabled: false
});
const runtimeScene = buildRuntimeSceneFromDocument({
...createEmptySceneDocument({ name: "Camera Rig Runtime Scene" }),
entities: {
[interactable.id]: interactable,
[cameraRig.id]: cameraRig,
[disabledCameraRig.id]: disabledCameraRig
}
});
expect(runtimeScene.entities.cameraRigs).toEqual([
{
entityId: cameraRig.id,
rigType: "fixed",
priority: 7,
defaultActive: false,
position: {
x: 8,
y: 3,
z: -6
},
target: {
kind: "entity",
entityId: interactable.id
},
targetOffset: {
x: 0,
y: 1.5,
z: 0
},
transitionMode: "blend",
transitionDurationSeconds: 0.6,
lookAround: {
enabled: true,
yawLimitDegrees: 10,
pitchLimitDegrees: 6,
recenterSpeed: 5
}
}
]);
});
it("builds runtime brush data, colliders, and an authored player spawn from the document", () => {
const brush = createBoxBrush({
id: "brush-room-floor",

View File

@@ -17,6 +17,9 @@ import { createScenePath } from "../../src/document/paths";
import { createEmptySceneDocument } from "../../src/document/scene-document";
import { validateSceneDocument } from "../../src/document/scene-document-validation";
import {
createCameraRigActorTargetRef,
createCameraRigEntity,
createCameraRigEntityTargetRef,
createPointLightEntity,
createInteractableEntity,
createNpcEntity,
@@ -753,6 +756,81 @@ describe("validateSceneDocument", () => {
);
});
it("validates fixed camera rig targets and authored settings", () => {
const otherCameraRig = createCameraRigEntity({
id: "entity-camera-rig-other"
});
const missingActorRig = createCameraRigEntity({
id: "entity-camera-rig-missing-actor",
target: createCameraRigActorTargetRef("actor-missing")
});
const missingEntityRig = createCameraRigEntity({
id: "entity-camera-rig-missing-entity",
target: createCameraRigEntityTargetRef("entity-missing")
});
const selfTargetRig = createCameraRigEntity({
id: "entity-camera-rig-self"
});
const cameraTargetRig = createCameraRigEntity({
id: "entity-camera-rig-camera-target",
target: createCameraRigEntityTargetRef(otherCameraRig.id)
});
const invalidRig = createCameraRigEntity({
id: "entity-camera-rig-invalid"
});
selfTargetRig.target = createCameraRigEntityTargetRef(selfTargetRig.id);
invalidRig.priority = Number.NaN;
invalidRig.targetOffset = {
x: Number.POSITIVE_INFINITY,
y: 0,
z: 0
};
invalidRig.lookAround.enabled = "yes" as unknown as boolean;
invalidRig.lookAround.pitchLimitDegrees = Number.NaN;
const validation = validateSceneDocument({
...createEmptySceneDocument(),
entities: {
[otherCameraRig.id]: otherCameraRig,
[missingActorRig.id]: missingActorRig,
[missingEntityRig.id]: missingEntityRig,
[selfTargetRig.id]: selfTargetRig,
[cameraTargetRig.id]: cameraTargetRig,
[invalidRig.id]: invalidRig
}
});
expect(validation.errors).toEqual(
expect.arrayContaining([
expect.objectContaining({
code: "missing-camera-rig-target-actor"
}),
expect.objectContaining({
code: "missing-camera-rig-target-entity"
}),
expect.objectContaining({
code: "camera-rig-self-target"
}),
expect.objectContaining({
code: "invalid-camera-rig-target-entity-kind"
}),
expect.objectContaining({
code: "invalid-camera-rig-priority"
}),
expect.objectContaining({
code: "invalid-camera-rig-target-offset"
}),
expect.objectContaining({
code: "invalid-camera-rig-look-around-enabled"
}),
expect.objectContaining({
code: "invalid-camera-rig-look-around-pitch-limit"
})
])
);
});
it("detects missing and invalid audio asset references on Sound Emitters", () => {
const audioAsset = {
id: "asset-audio-main",

View File

@@ -53,7 +53,9 @@ import {
} from "../../src/document/scene-document";
import { migrateSceneDocument } from "../../src/document/migrate-scene-document";
import {
createCameraRigEntity,
createNpcAlwaysPresence,
createCameraRigWorldPointTargetRef,
createNpcEntity,
createNpcTimeWindowPresence,
createPointLightEntity,
@@ -970,6 +972,103 @@ describe("scene document JSON", () => {
);
});
it("round-trips a document containing an authored Camera Rig entity", () => {
const cameraRig = createCameraRigEntity({
id: "entity-camera-rig-main",
name: "Overlook",
position: {
x: 6,
y: 4,
z: -8
},
priority: 5,
defaultActive: false,
target: createCameraRigWorldPointTargetRef({
x: 1,
y: 1.75,
z: -2
}),
targetOffset: {
x: 0,
y: 0.25,
z: 0
},
transitionMode: "blend",
transitionDurationSeconds: 0.8,
lookAround: {
enabled: true,
yawLimitDegrees: 15,
pitchLimitDegrees: 9,
recenterSpeed: 4
}
});
const document = {
...createEmptySceneDocument({ name: "Camera Rig Scene" }),
entities: {
[cameraRig.id]: cameraRig
}
};
expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
document
);
});
it("migrates version 73 camera rigs to include fixed-rig defaults", () => {
const cameraRig = createCameraRigEntity({
id: "entity-camera-rig-legacy",
name: "Legacy Camera",
position: {
x: -4,
y: 3,
z: 9
},
target: createCameraRigWorldPointTargetRef({
x: 2,
y: 1,
z: -3
})
});
const legacyDocument = {
...createEmptySceneDocument({ name: "Legacy Camera Rig Scene" }),
entities: {
[cameraRig.id]: cameraRig
}
};
const serializedLegacyDocument = JSON.parse(
serializeSceneDocument(legacyDocument)
) as {
version: number;
entities: Record<string, Record<string, unknown>>;
};
const serializedCameraRig = serializedLegacyDocument.entities[cameraRig.id];
serializedLegacyDocument.version =
CAMERA_RIG_ENTITY_SCENE_DOCUMENT_VERSION - 1;
delete serializedCameraRig.priority;
delete serializedCameraRig.defaultActive;
delete serializedCameraRig.targetOffset;
delete serializedCameraRig.transitionMode;
delete serializedCameraRig.transitionDurationSeconds;
delete serializedCameraRig.lookAround;
const migratedDocument = parseSceneDocumentJson(
JSON.stringify(serializedLegacyDocument)
);
expect(migratedDocument.entities[cameraRig.id]).toEqual(
createCameraRigEntity({
id: cameraRig.id,
name: cameraRig.name,
visible: cameraRig.visible,
enabled: cameraRig.enabled,
position: cameraRig.position,
rigType: cameraRig.rigType,
target: cameraRig.target
})
);
});
it("migrates version 14 documents without entity names", () => {
const pointLight = createPointLightEntity({
id: "entity-point-light-legacy",

View File

@@ -1,6 +1,15 @@
import { describe, expect, it } from "vitest";
import {
DEFAULT_CAMERA_RIG_DEFAULT_ACTIVE,
DEFAULT_CAMERA_RIG_LOOK_AROUND_ENABLED,
DEFAULT_CAMERA_RIG_LOOK_AROUND_PITCH_LIMIT_DEGREES,
DEFAULT_CAMERA_RIG_LOOK_AROUND_RECENTER_SPEED,
DEFAULT_CAMERA_RIG_LOOK_AROUND_YAW_LIMIT_DEGREES,
DEFAULT_CAMERA_RIG_PRIORITY,
DEFAULT_CAMERA_RIG_TARGET_OFFSET,
DEFAULT_CAMERA_RIG_TRANSITION_DURATION_SECONDS,
DEFAULT_CAMERA_RIG_TRANSITION_MODE,
DEFAULT_POINT_LIGHT_COLOR_HEX,
DEFAULT_POINT_LIGHT_DISTANCE,
DEFAULT_POINT_LIGHT_INTENSITY,
@@ -86,6 +95,26 @@ describe("entity registry defaults", () => {
distance: DEFAULT_POINT_LIGHT_DISTANCE
});
expect(createDefaultEntityInstance("cameraRig")).toMatchObject({
kind: "cameraRig",
position: { x: 0, y: 0, z: 0 },
rigType: "fixed",
priority: DEFAULT_CAMERA_RIG_PRIORITY,
defaultActive: DEFAULT_CAMERA_RIG_DEFAULT_ACTIVE,
target: {
kind: "player"
},
targetOffset: DEFAULT_CAMERA_RIG_TARGET_OFFSET,
transitionMode: DEFAULT_CAMERA_RIG_TRANSITION_MODE,
transitionDurationSeconds: DEFAULT_CAMERA_RIG_TRANSITION_DURATION_SECONDS,
lookAround: {
enabled: DEFAULT_CAMERA_RIG_LOOK_AROUND_ENABLED,
yawLimitDegrees: DEFAULT_CAMERA_RIG_LOOK_AROUND_YAW_LIMIT_DEGREES,
pitchLimitDegrees: DEFAULT_CAMERA_RIG_LOOK_AROUND_PITCH_LIMIT_DEGREES,
recenterSpeed: DEFAULT_CAMERA_RIG_LOOK_AROUND_RECENTER_SPEED
}
});
expect(createDefaultEntityInstance("spotLight")).toMatchObject({
kind: "spotLight",
position: { x: 0, y: 0, z: 0 },

View File

@@ -5,7 +5,13 @@ import { createBoxBrush } from "../../src/document/brushes";
import { createScenePath } from "../../src/document/paths";
import { createEmptySceneDocument } from "../../src/document/scene-document";
import { createTerrain } from "../../src/document/terrains";
import { createPointLightEntity, createPlayerStartEntity, createSpotLightEntity, createTriggerVolumeEntity } from "../../src/entities/entity-instances";
import {
createCameraRigEntity,
createPointLightEntity,
createPlayerStartEntity,
createSpotLightEntity,
createTriggerVolumeEntity
} from "../../src/entities/entity-instances";
import { resolveViewportFocusTarget } from "../../src/viewport-three/viewport-focus";
describe("resolveViewportFocusTarget", () => {
@@ -169,6 +175,38 @@ describe("resolveViewportFocusTarget", () => {
});
});
it("frames the selected Camera Rig helper", () => {
const cameraRig = createCameraRigEntity({
id: "entity-camera-rig-focus",
position: {
x: -3,
y: 2,
z: 5
}
});
const document = {
...createEmptySceneDocument(),
entities: {
[cameraRig.id]: cameraRig
}
};
const focusTarget = resolveViewportFocusTarget(document, {
kind: "entities",
ids: [cameraRig.id]
});
expect(focusTarget).toEqual({
center: {
x: -3,
y: 2.28,
z: 5
},
radius: expect.any(Number)
});
expect(focusTarget?.radius).toBeGreaterThan(0.45);
});
it("frames the selected Path around its authored point bounds", () => {
const path = createScenePath({
id: "path-focus",