Enhance scene document validation with project-level checks and add mouse invert setting

This commit is contained in:
2026-04-27 17:23:43 +02:00
parent 129966962f
commit 10bee5ef4c
3 changed files with 134 additions and 3 deletions

View File

@@ -198,6 +198,7 @@ import {
PLAYER_START_GAMEPAD_CAMERA_LOOK_SCENE_DOCUMENT_VERSION,
PLAYER_START_INTERACT_BINDINGS_SCENE_DOCUMENT_VERSION,
PLAYER_START_INPUT_BINDINGS_SCENE_DOCUMENT_VERSION,
PLAYER_START_MOUSE_INVERT_SCENE_DOCUMENT_VERSION,
PLAYER_START_TARGETING_SETTINGS_SCENE_DOCUMENT_VERSION,
PLAYER_START_NAVIGATION_MODE_SCENE_DOCUMENT_VERSION,
PLAYER_START_PAUSE_BINDINGS_SCENE_DOCUMENT_VERSION,

View File

@@ -16,8 +16,17 @@ import {
createSoundEmitterControlTargetRef
} from "../../src/controls/control-surface";
import { createScenePath } from "../../src/document/paths";
import { createEmptySceneDocument } from "../../src/document/scene-document";
import { validateSceneDocument } from "../../src/document/scene-document-validation";
import {
createEmptyProjectDocument,
createEmptyProjectScene,
createEmptySceneDocument,
createSceneDocumentFromProject
} from "../../src/document/scene-document";
import {
validateProjectDocument,
validateSceneDocument,
validateSceneDocumentLocalBuildContent
} from "../../src/document/scene-document-validation";
import {
createCameraRigActorTargetRef,
createCameraRigEntity,
@@ -69,6 +78,124 @@ describe("validateSceneDocument", () => {
expect(validation.warnings).toEqual([]);
});
it("validates project-global actor schedule targets against all project scenes", () => {
const sceneA = createEmptyProjectScene({
id: "scene-a",
name: "Scene A"
});
const sceneB = createEmptyProjectScene({
id: "scene-b",
name: "Scene B"
});
const ana = createNpcEntity({
id: "entity-npc-ana-nanto",
actorId: "Ana Nanto"
});
const actorTarget = createActorControlTargetRef(ana.actorId);
const project = createEmptyProjectDocument({
sceneId: sceneA.id,
sceneName: sceneA.name
});
project.scenes = {
[sceneA.id]: {
...sceneA,
entities: {
[ana.id]: ana
}
},
[sceneB.id]: sceneB
};
project.sequences.sequences["sequence-ana-presence"] =
createProjectSequence({
id: "sequence-ana-presence",
title: "Ana Presence",
effects: [
{
stepClass: "held",
type: "controlEffect",
effect: createSetActorPresenceControlEffect({
target: actorTarget,
active: true
})
}
]
});
project.scheduler.routines["routine-ana-presence"] =
createProjectScheduleRoutine({
id: "routine-ana-presence",
title: "Ana Presence",
target: actorTarget,
sequenceId: "sequence-ana-presence",
effects: []
});
const projectValidation = validateProjectDocument(project);
const sceneBValidation = validateSceneDocumentLocalBuildContent(
createSceneDocumentFromProject(project, sceneB.id)
);
expect(projectValidation.errors).not.toEqual(
expect.arrayContaining([
expect.objectContaining({
code: "missing-control-actor-target"
})
])
);
expect(sceneBValidation.errors).not.toEqual(
expect.arrayContaining([
expect.objectContaining({
code: "missing-control-actor-target"
})
])
);
});
it("keeps project validation strict for truly missing project-global actor targets", () => {
const project = createEmptyProjectDocument();
const missingActorTarget = createActorControlTargetRef("actor-missing");
project.sequences.sequences["sequence-missing-actor"] =
createProjectSequence({
id: "sequence-missing-actor",
title: "Missing Actor",
effects: [
{
stepClass: "held",
type: "controlEffect",
effect: createSetActorPresenceControlEffect({
target: missingActorTarget,
active: true
})
}
]
});
project.scheduler.routines["routine-missing-actor"] =
createProjectScheduleRoutine({
id: "routine-missing-actor",
title: "Missing Actor",
target: missingActorTarget,
sequenceId: "sequence-missing-actor",
effects: []
});
const validation = validateProjectDocument(project);
expect(validation.errors).toEqual(
expect.arrayContaining([
expect.objectContaining({
code: "missing-control-actor-target",
path:
"sequences.sequences.sequence-missing-actor.effects.0.effect.target.actorId"
}),
expect.objectContaining({
code: "missing-control-actor-target",
path: "scheduler.routines.routine-missing-actor.target.actorId"
})
])
);
});
it("detects duplicate authored ids across collections", () => {
const brush = createBoxBrush({
id: "shared-room-id"

View File

@@ -3795,11 +3795,13 @@ describe("RuntimeHost", () => {
thirdPersonController: {
id: "thirdPerson";
activate: ReturnType<typeof vi.fn>;
deactivate: ReturnType<typeof vi.fn>;
};
activateDesiredNavigationController(): void;
};
const deactivate = vi.fn();
const activate = vi.fn();
const nextControllerDeactivate = vi.fn();
const domElement = (
host as unknown as {
domElement: HTMLCanvasElement;
@@ -3815,7 +3817,8 @@ describe("RuntimeHost", () => {
hostInternals.desiredNavigationMode = "thirdPerson";
hostInternals.thirdPersonController = {
id: "thirdPerson",
activate
activate,
deactivate: nextControllerDeactivate
};
Object.defineProperty(document, "pointerLockElement", {
configurable: true,