auto-git:

[change] src/app/app.css
 [change] src/commands/set-scene-loading-screen-command.ts
 [change] src/document/migrate-scene-document.ts
 [change] src/document/scene-document-validation.ts
 [change] src/document/scene-document.ts
 [change] src/runner-web/RunnerCanvas.tsx
 [change] src/runtime-three/first-person-navigation-controller.ts
 [change] src/runtime-three/navigation-controller.ts
 [change] src/runtime-three/orbit-visitor-navigation-controller.ts
 [change] src/runtime-three/runtime-host.ts
 [change] tests/domain/editor-store.test.ts
 [change] tests/serialization/local-draft-storage.test.ts
 [change] tests/serialization/project-document-json.test.ts
 [change] tests/serialization/project-package.test.ts
 [change] tests/unit/runner-canvas.test.tsx
 [change] tests/unit/runtime-host.test.ts
This commit is contained in:
2026-04-11 04:19:50 +02:00
parent 75986da19d
commit e205cea50c
16 changed files with 2232 additions and 563 deletions

View File

@@ -6,9 +6,15 @@ import type { LoadedImageAsset } from "../assets/image-assets";
import type { ProjectAssetRecord } from "../assets/project-assets";
import type { SceneLoadingScreenSettings } from "../document/scene-document";
import type { FirstPersonTelemetry } from "../runtime-three/navigation-controller";
import { RuntimeHost, type RuntimeSceneLoadState } from "../runtime-three/runtime-host";
import {
RuntimeHost,
type RuntimeSceneLoadState
} from "../runtime-three/runtime-host";
import type { RuntimeInteractionPrompt } from "../runtime-three/runtime-interaction-system";
import type { RuntimeNavigationMode, RuntimeSceneDefinition } from "../runtime-three/runtime-scene-build";
import type {
RuntimeNavigationMode,
RuntimeSceneDefinition
} from "../runtime-three/runtime-scene-build";
import { createWorldBackgroundStyle } from "../shared-ui/world-background-style";
interface RunnerCanvasProps {
@@ -45,10 +51,13 @@ export function RunnerCanvas({
status: "loading",
message: null
});
const [interactionPrompt, setInteractionPrompt] = useState<RuntimeInteractionPrompt | null>(null);
const [firstPersonTelemetry, setFirstPersonTelemetry] = useState<FirstPersonTelemetry | null>(null);
const [interactionPrompt, setInteractionPrompt] =
useState<RuntimeInteractionPrompt | null>(null);
const [firstPersonTelemetry, setFirstPersonTelemetry] =
useState<FirstPersonTelemetry | null>(null);
const overlayMessage = runnerMessage ?? sceneLoadState.message;
const overlayStatus = overlayMessage !== null ? "error" : sceneLoadState.status;
const overlayStatus =
overlayMessage !== null ? "error" : sceneLoadState.status;
const runnerReady = overlayStatus === "ready";
useEffect(() => {
@@ -86,7 +95,9 @@ export function RunnerCanvas({
};
} catch (error) {
const message =
error instanceof Error ? error.message : "Runner initialization failed.";
error instanceof Error
? error.message
: "Runner initialization failed.";
const failureMessage = `Runner initialization failed: ${message}`;
setRunnerMessage(failureMessage);
setSceneLoadState({
@@ -98,10 +109,19 @@ export function RunnerCanvas({
onFirstPersonTelemetryChange(null);
return;
}
}, [onFirstPersonTelemetryChange, onInteractionPromptChange, onRuntimeMessageChange]);
}, [
onFirstPersonTelemetryChange,
onInteractionPromptChange,
onRuntimeMessageChange
]);
useEffect(() => {
hostRef.current?.updateAssets(projectAssets, loadedModelAssets, loadedImageAssets, loadedAudioAssets);
hostRef.current?.updateAssets(
projectAssets,
loadedModelAssets,
loadedImageAssets,
loadedAudioAssets
);
}, [projectAssets, loadedModelAssets, loadedImageAssets, loadedAudioAssets]);
useEffect(() => {
@@ -116,7 +136,12 @@ export function RunnerCanvas({
onFirstPersonTelemetryChange(null);
onRuntimeMessageChange(null);
hostRef.current?.loadScene(runtimeScene);
}, [onFirstPersonTelemetryChange, onInteractionPromptChange, onRuntimeMessageChange, runtimeScene]);
}, [
onFirstPersonTelemetryChange,
onInteractionPromptChange,
onRuntimeMessageChange,
runtimeScene
]);
useEffect(() => {
hostRef.current?.setNavigationMode(navigationMode);
@@ -131,7 +156,10 @@ export function RunnerCanvas({
aria-busy={!runnerReady}
style={createWorldBackgroundStyle(
runtimeScene.world.background,
runtimeScene.world.background.mode === "image" ? loadedImageAssets[runtimeScene.world.background.assetId]?.sourceUrl ?? null : null
runtimeScene.world.background.mode === "image"
? (loadedImageAssets[runtimeScene.world.background.assetId]
?.sourceUrl ?? null)
: null
)}
>
<div
@@ -191,19 +219,33 @@ export function RunnerCanvas({
{runnerReady &&
navigationMode === "firstPerson" &&
interactionPrompt !== null ? (
<div className="runner-canvas__prompt" data-testid="runner-interaction-prompt" role="status" aria-live="polite">
<div
className="runner-canvas__prompt"
data-testid="runner-interaction-prompt"
role="status"
aria-live="polite"
>
<div className="runner-canvas__prompt-badge">Click</div>
<div className="runner-canvas__prompt-text" data-testid="runner-interaction-prompt-text">
<div
className="runner-canvas__prompt-text"
data-testid="runner-interaction-prompt-text"
>
{interactionPrompt.prompt}
</div>
<div className="runner-canvas__prompt-meta" data-testid="runner-interaction-prompt-meta">
{interactionPrompt.distance.toFixed(1)}m away · {interactionPrompt.range.toFixed(1)}m range
<div
className="runner-canvas__prompt-meta"
data-testid="runner-interaction-prompt-meta"
>
{interactionPrompt.distance.toFixed(1)}m away ·{" "}
{interactionPrompt.range.toFixed(1)}m range
</div>
</div>
) : null}
{runnerMessage === null ? null : (
<div className="runner-canvas__fallback" role="status">
<div className="runner-canvas__fallback-title">Runner Unavailable</div>
<div className="runner-canvas__fallback-title">
Runner Unavailable
</div>
<div>{runnerMessage}</div>
</div>
)}