Enhance scene document validation with project-level checks and add mouse invert setting
This commit is contained in:
@@ -198,6 +198,7 @@ import {
|
|||||||
PLAYER_START_GAMEPAD_CAMERA_LOOK_SCENE_DOCUMENT_VERSION,
|
PLAYER_START_GAMEPAD_CAMERA_LOOK_SCENE_DOCUMENT_VERSION,
|
||||||
PLAYER_START_INTERACT_BINDINGS_SCENE_DOCUMENT_VERSION,
|
PLAYER_START_INTERACT_BINDINGS_SCENE_DOCUMENT_VERSION,
|
||||||
PLAYER_START_INPUT_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_TARGETING_SETTINGS_SCENE_DOCUMENT_VERSION,
|
||||||
PLAYER_START_NAVIGATION_MODE_SCENE_DOCUMENT_VERSION,
|
PLAYER_START_NAVIGATION_MODE_SCENE_DOCUMENT_VERSION,
|
||||||
PLAYER_START_PAUSE_BINDINGS_SCENE_DOCUMENT_VERSION,
|
PLAYER_START_PAUSE_BINDINGS_SCENE_DOCUMENT_VERSION,
|
||||||
|
|||||||
@@ -16,8 +16,17 @@ import {
|
|||||||
createSoundEmitterControlTargetRef
|
createSoundEmitterControlTargetRef
|
||||||
} from "../../src/controls/control-surface";
|
} from "../../src/controls/control-surface";
|
||||||
import { createScenePath } from "../../src/document/paths";
|
import { createScenePath } from "../../src/document/paths";
|
||||||
import { createEmptySceneDocument } from "../../src/document/scene-document";
|
import {
|
||||||
import { validateSceneDocument } from "../../src/document/scene-document-validation";
|
createEmptyProjectDocument,
|
||||||
|
createEmptyProjectScene,
|
||||||
|
createEmptySceneDocument,
|
||||||
|
createSceneDocumentFromProject
|
||||||
|
} from "../../src/document/scene-document";
|
||||||
|
import {
|
||||||
|
validateProjectDocument,
|
||||||
|
validateSceneDocument,
|
||||||
|
validateSceneDocumentLocalBuildContent
|
||||||
|
} from "../../src/document/scene-document-validation";
|
||||||
import {
|
import {
|
||||||
createCameraRigActorTargetRef,
|
createCameraRigActorTargetRef,
|
||||||
createCameraRigEntity,
|
createCameraRigEntity,
|
||||||
@@ -69,6 +78,124 @@ describe("validateSceneDocument", () => {
|
|||||||
expect(validation.warnings).toEqual([]);
|
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", () => {
|
it("detects duplicate authored ids across collections", () => {
|
||||||
const brush = createBoxBrush({
|
const brush = createBoxBrush({
|
||||||
id: "shared-room-id"
|
id: "shared-room-id"
|
||||||
|
|||||||
@@ -3795,11 +3795,13 @@ describe("RuntimeHost", () => {
|
|||||||
thirdPersonController: {
|
thirdPersonController: {
|
||||||
id: "thirdPerson";
|
id: "thirdPerson";
|
||||||
activate: ReturnType<typeof vi.fn>;
|
activate: ReturnType<typeof vi.fn>;
|
||||||
|
deactivate: ReturnType<typeof vi.fn>;
|
||||||
};
|
};
|
||||||
activateDesiredNavigationController(): void;
|
activateDesiredNavigationController(): void;
|
||||||
};
|
};
|
||||||
const deactivate = vi.fn();
|
const deactivate = vi.fn();
|
||||||
const activate = vi.fn();
|
const activate = vi.fn();
|
||||||
|
const nextControllerDeactivate = vi.fn();
|
||||||
const domElement = (
|
const domElement = (
|
||||||
host as unknown as {
|
host as unknown as {
|
||||||
domElement: HTMLCanvasElement;
|
domElement: HTMLCanvasElement;
|
||||||
@@ -3815,7 +3817,8 @@ describe("RuntimeHost", () => {
|
|||||||
hostInternals.desiredNavigationMode = "thirdPerson";
|
hostInternals.desiredNavigationMode = "thirdPerson";
|
||||||
hostInternals.thirdPersonController = {
|
hostInternals.thirdPersonController = {
|
||||||
id: "thirdPerson",
|
id: "thirdPerson",
|
||||||
activate
|
activate,
|
||||||
|
deactivate: nextControllerDeactivate
|
||||||
};
|
};
|
||||||
Object.defineProperty(document, "pointerLockElement", {
|
Object.defineProperty(document, "pointerLockElement", {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user