import { describe, expect, it } from "vitest";

import {
  createActivateCameraRigOverrideControlEffect,
  createActorControlTargetRef,
  createCameraRigControlTargetRef,
  createClearCameraRigOverrideControlEffect,
  createFollowActorPathControlEffect,
  createPlayActorAnimationControlEffect,
  createSetActorPresenceControlEffect
} from "../../src/controls/control-surface";
import {
  createBoxBrush,
  deriveBoxBrushSizeFromGeometry
} from "../../src/document/brushes";
import { createScenePath } from "../../src/document/paths";
import { createDefaultProjectTimeSettings } from "../../src/document/project-time-settings";
import { createTerrain } from "../../src/document/terrains";
import {
  AUTHORED_TERRAIN_PAINT_SCENE_DOCUMENT_VERSION,
  AUTHORED_TERRAIN_FOUNDATION_SCENE_DOCUMENT_VERSION,
  CAMERA_RIG_MAPPED_RAIL_SCENE_DOCUMENT_VERSION,
  CAMERA_RIG_CONTROL_SURFACE_SCENE_DOCUMENT_VERSION,
  CAMERA_RIG_ENTITY_SCENE_DOCUMENT_VERSION,
  CELESTIAL_BODY_OVERLAY_SCENE_DOCUMENT_VERSION,
  DAWN_DUSK_BACKGROUND_IMAGE_SCENE_DOCUMENT_VERSION,
  FOLLOW_ACTOR_PATH_SMOOTH_SCENE_DOCUMENT_VERSION,
  ANIMATION_PLAYBACK_SCENE_DOCUMENT_VERSION,
  ENTITY_NAMES_SCENE_DOCUMENT_VERSION,
  ENTITY_SYSTEM_FOUNDATION_SCENE_DOCUMENT_VERSION,
  FIRST_ROOM_POLISH_SCENE_DOCUMENT_VERSION,
  IMPORTED_MODEL_COLLIDERS_SCENE_DOCUMENT_VERSION,
  LOCAL_LIGHTS_AND_SKYBOX_SCENE_DOCUMENT_VERSION,
  MODEL_ASSET_PIPELINE_SCENE_DOCUMENT_VERSION,
  NPC_ENTITY_FOUNDATION_SCENE_DOCUMENT_VERSION,
  NPC_DIALOGUE_REFERENCE_SCENE_DOCUMENT_VERSION,
  PATH_FOUNDATION_SCENE_DOCUMENT_VERSION,
  PLAYER_START_AIR_CONTROL_SCENE_DOCUMENT_VERSION,
  PLAYER_START_AIR_DIRECTION_CONTROL_SCENE_DOCUMENT_VERSION,
  PLAYER_START_COLLIDER_SETTINGS_SCENE_DOCUMENT_VERSION,
  PLAYER_START_INTERACTION_ANGLE_SCENE_DOCUMENT_VERSION,
  PLAYER_START_INPUT_BINDINGS_SCENE_DOCUMENT_VERSION,
  PLAYER_START_MOVEMENT_TEMPLATE_SCENE_DOCUMENT_VERSION,
  PLAYER_START_NAVIGATION_MODE_SCENE_DOCUMENT_VERSION,
  SCENE_EDITOR_PREFERENCES_SCENE_DOCUMENT_VERSION,
  SCENE_TRANSITION_ENTITIES_SCENE_DOCUMENT_VERSION,
  SCENE_TRANSITION_SEQUENCE_EFFECTS_SCENE_DOCUMENT_VERSION,
  SCENE_DOCUMENT_VERSION,
  SHADER_SKY_HORIZON_HEIGHT_SCENE_DOCUMENT_VERSION,
  SHADER_SKY_SCENE_DOCUMENT_VERSION,
  SPATIAL_AUDIO_SCENE_DOCUMENT_VERSION,
  STATIC_SIMPLE_MODEL_COLLIDERS_SCENE_DOCUMENT_VERSION,
  TRIGGER_ACTION_TARGET_FOUNDATION_SCENE_DOCUMENT_VERSION,
  WHITEBOX_BEVEL_SCENE_DOCUMENT_VERSION,
  WHITEBOX_GEOMETRY_SCENE_DOCUMENT_VERSION,
  WORLD_TIME_ENVIRONMENT_SCENE_DOCUMENT_VERSION,
  WORLD_ENVIRONMENT_SCENE_DOCUMENT_VERSION,
  createEmptySceneDocument
} from "../../src/document/scene-document";
import { migrateSceneDocument } from "../../src/document/migrate-scene-document";
import {
  createCameraRigEntity,
  createNpcAlwaysPresence,
  createCameraRigWorldPointTargetRef,
  createNpcEntity,
  createNpcTimeWindowPresence,
  createPointLightEntity,
  createInteractableEntity,
  createPlayerStartEntity,
  createSoundEmitterEntity,
  createSpotLightEntity,
  createTeleportTargetEntity,
  createTriggerVolumeEntity
} from "../../src/entities/entity-instances";
import { createProjectScheduleRoutine } from "../../src/scheduler/project-scheduler";
import { createProjectSequence } from "../../src/sequencer/project-sequences";
import {
  createControlInteractionLink,
  createPlayAnimationInteractionLink,
  createPlaySoundInteractionLink,
  createStopAnimationInteractionLink,
  createStopSoundInteractionLink,
  createTeleportPlayerInteractionLink,
  createToggleVisibilityInteractionLink
} from "../../src/interactions/interaction-links";
import { STARTER_MATERIAL_LIBRARY } from "../../src/materials/starter-material-library";
import { createModelInstance } from "../../src/assets/model-instances";
import {
  createProjectAssetStorageKey,
  type AudioAssetRecord,
  type ImageAssetRecord,
  type ModelAssetRecord
} from "../../src/assets/project-assets";
import {
  parseSceneDocumentJson,
  serializeSceneDocument
} from "../../src/serialization/scene-document-json";

describe("scene document JSON", () => {
  it("round-trips the current empty schema", () => {
    const document = createEmptySceneDocument({ name: "Bootstrap Scene" });
    const serializedDocument = serializeSceneDocument(document);

    expect(parseSceneDocumentJson(serializedDocument)).toEqual(document);
  });

  it("round-trips authored terrain foundations in the current schema", () => {
    const terrain = createTerrain({
      id: "terrain-roundtrip-main",
      name: "Plateau",
      collisionEnabled: false,
      position: {
        x: -6,
        y: 1,
        z: -4
      },
      sampleCountX: 3,
      sampleCountZ: 4,
      cellSize: 2,
      heights: [0, 0, 1, 1, 2, 3, 0, 1, 2, -1, 0, 0]
    });
    const document = createEmptySceneDocument({ name: "Terrain Scene" });
    document.terrains[terrain.id] = terrain;

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("round-trips camera rig control effects in interaction links, sequences, and scheduler routines", () => {
    const cameraRig = createCameraRigEntity({
      id: "entity-camera-rig-main",
      target: createCameraRigWorldPointTargetRef({
        x: 0,
        y: 1.5,
        z: 0
      })
    });
    const triggerVolume = createTriggerVolumeEntity({
      id: "entity-trigger-camera"
    });
    const document = createEmptySceneDocument({ name: "Camera Control Scene" });
    document.entities[cameraRig.id] = cameraRig;
    document.entities[triggerVolume.id] = triggerVolume;
    document.interactionLinks["link-camera-activate"] =
      createControlInteractionLink({
        id: "link-camera-activate",
        sourceEntityId: triggerVolume.id,
        effect: createActivateCameraRigOverrideControlEffect({
          target: createCameraRigControlTargetRef(cameraRig.id)
        })
      });
    document.sequences.sequences["sequence-camera-clear"] =
      createProjectSequence({
        id: "sequence-camera-clear",
        title: "Camera Clear",
        steps: [
          {
            stepClass: "held",
            type: "controlEffect",
            effect: createClearCameraRigOverrideControlEffect({
              target: createCameraRigControlTargetRef(cameraRig.id)
            })
          }
        ]
      });
    document.scheduler.routines["routine-camera-activate"] =
      createProjectScheduleRoutine({
        id: "routine-camera-activate",
        title: "Camera Activate",
        target: createCameraRigControlTargetRef(cameraRig.id),
        sequenceId: "sequence-camera-clear",
        effect: createActivateCameraRigOverrideControlEffect({
          target: createCameraRigControlTargetRef(cameraRig.id)
        })
      });

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("migrates v70 scene documents by defaulting shader sky settings from the authored day background", () => {
    const document = createEmptySceneDocument({
      name: "Legacy Shader Sky Scene"
    });
    document.world.background = {
      mode: "verticalGradient",
      topColorHex: "#335577",
      bottomColorHex: "#aaccee"
    };

    const migratedDocument = parseSceneDocumentJson(
      JSON.stringify({
        ...document,
        version: CELESTIAL_BODY_OVERLAY_SCENE_DOCUMENT_VERSION,
        world: {
          ...document.world,
          shaderSky: undefined
        }
      })
    );

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.world.background).toEqual(
      document.world.background
    );
    expect(migratedDocument.world.shaderSky.dayTopColorHex).toBe("#335577");
    expect(migratedDocument.world.shaderSky.dayBottomColorHex).toBe("#aaccee");
  });

  it("migrates v71 scene documents by defaulting the shader sky horizon height", () => {
    const document = createEmptySceneDocument({
      name: "Legacy Shader Horizon Scene"
    });
    document.world.background = {
      mode: "shader"
    };
    document.world.shaderSky.dayTopColorHex = "#335577";
    document.world.shaderSky.dayBottomColorHex = "#aaccee";

    const legacyDocument = JSON.parse(
      serializeSceneDocument(document)
    ) as Record<string, unknown>;
    legacyDocument.version = SHADER_SKY_SCENE_DOCUMENT_VERSION;
    delete (
      (
        legacyDocument.world as {
          shaderSky: Record<string, unknown>;
        }
      ).shaderSky as Record<string, unknown>
    ).horizonHeight;

    const migratedDocument = parseSceneDocumentJson(
      JSON.stringify(legacyDocument)
    );

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.world.shaderSky.dayTopColorHex).toBe("#335577");
    expect(migratedDocument.world.shaderSky.dayBottomColorHex).toBe("#aaccee");
    expect(migratedDocument.world.shaderSky.horizonHeight).toBe(0);
  });

  it("migrates v72 scene documents by defaulting the shader sky star horizon offset", () => {
    const document = createEmptySceneDocument({
      name: "Legacy Shader Star Horizon Scene"
    });
    document.world.background = {
      mode: "shader"
    };
    document.world.shaderSky.stars.density = 0.72;

    const legacyDocument = JSON.parse(
      serializeSceneDocument(document)
    ) as Record<string, unknown>;
    legacyDocument.version = SHADER_SKY_HORIZON_HEIGHT_SCENE_DOCUMENT_VERSION;
    delete (
      (
        legacyDocument.world as {
          shaderSky: {
            stars: Record<string, unknown>;
          };
        }
      ).shaderSky.stars as Record<string, unknown>
    ).horizonFadeOffset;

    const migratedDocument = parseSceneDocumentJson(
      JSON.stringify(legacyDocument)
    );

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.world.shaderSky.stars.density).toBe(0.72);
    expect(migratedDocument.world.shaderSky.stars.horizonFadeOffset).toBe(0);
  });

  it("migrates v78 scene documents by defaulting shader sky aurora settings", () => {
    const document = createEmptySceneDocument({
      name: "Legacy Shader Aurora Scene"
    });
    document.world.background = {
      mode: "shader"
    };
    document.world.shaderSky.dayTopColorHex = "#335577";
    document.world.shaderSky.dayBottomColorHex = "#aaccee";

    const legacyDocument = JSON.parse(
      serializeSceneDocument(document)
    ) as Record<string, unknown>;
    legacyDocument.version = CAMERA_RIG_MAPPED_RAIL_SCENE_DOCUMENT_VERSION;
    delete (
      (
        legacyDocument.world as {
          shaderSky: Record<string, unknown>;
        }
      ).shaderSky as Record<string, unknown>
    ).aurora;

    const migratedDocument = parseSceneDocumentJson(
      JSON.stringify(legacyDocument)
    );

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.world.shaderSky.aurora.enabled).toBe(false);
    expect(migratedDocument.world.shaderSky.aurora.primaryColorHex).toBe(
      "#6df7d0"
    );
    expect(migratedDocument.world.shaderSky.aurora.secondaryColorHex).toBe(
      "#6e8dff"
    );
  });

  it("migrates v74 scene documents by defaulting celestial orbit settings from the legacy sun direction", () => {
    const document = createEmptySceneDocument({
      name: "Legacy Celestial Orbit Scene"
    });
    document.world.sunLight.direction = {
      x: -0.4,
      y: 1,
      z: 0.2
    };

    const legacyDocument = JSON.parse(
      serializeSceneDocument(document)
    ) as Record<string, unknown>;
    legacyDocument.version = CAMERA_RIG_ENTITY_SCENE_DOCUMENT_VERSION;
    delete (legacyDocument.world as Record<string, unknown>).celestialOrbits;

    const migratedDocument = parseSceneDocumentJson(
      JSON.stringify(legacyDocument)
    );

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(
      migratedDocument.world.celestialOrbits.sun.azimuthDegrees
    ).toBeCloseTo(153.4349, 3);
    expect(
      migratedDocument.world.celestialOrbits.sun.peakAltitudeDegrees
    ).toBeGreaterThan(60);
    expect(migratedDocument.world.celestialOrbits.moon).toEqual(
      migratedDocument.world.celestialOrbits.sun
    );
  });

  it("migrates pre-paint terrain documents by defaulting terrain layer data", () => {
    const legacyTerrainDocument = {
      ...createEmptySceneDocument({
        name: "Legacy Terrain Paint Scene"
      }),
      version: AUTHORED_TERRAIN_FOUNDATION_SCENE_DOCUMENT_VERSION
    };
    const terrain = createTerrain({
      id: "terrain-legacy-paint",
      sampleCountX: 3,
      sampleCountZ: 3
    });
    legacyTerrainDocument.terrains[terrain.id] = {
      id: terrain.id,
      kind: terrain.kind,
      name: terrain.name,
      visible: terrain.visible,
      enabled: terrain.enabled,
      position: terrain.position,
      sampleCountX: terrain.sampleCountX,
      sampleCountZ: terrain.sampleCountZ,
      cellSize: terrain.cellSize,
      heights: terrain.heights
    } as typeof terrain;

    const migratedDocument = parseSceneDocumentJson(
      JSON.stringify(legacyTerrainDocument)
    );
    const migratedTerrain = migratedDocument.terrains[terrain.id];

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedTerrain.layers).toHaveLength(4);
    expect(migratedTerrain.paintWeights).toHaveLength(27);
    expect(migratedTerrain.paintWeights.every((weight) => weight === 0)).toBe(
      true
    );
  });

  it("migrates pre-collision terrain documents by defaulting terrain collision to enabled", () => {
    const legacyTerrainDocument = {
      ...createEmptySceneDocument({
        name: "Legacy Terrain Collision Scene"
      }),
      version: AUTHORED_TERRAIN_PAINT_SCENE_DOCUMENT_VERSION
    };
    const terrain = createTerrain({
      id: "terrain-legacy-collision",
      sampleCountX: 3,
      sampleCountZ: 3
    });
    legacyTerrainDocument.terrains[terrain.id] = {
      id: terrain.id,
      kind: terrain.kind,
      name: terrain.name,
      visible: terrain.visible,
      enabled: terrain.enabled,
      position: terrain.position,
      sampleCountX: terrain.sampleCountX,
      sampleCountZ: terrain.sampleCountZ,
      cellSize: terrain.cellSize,
      heights: terrain.heights,
      layers: terrain.layers,
      paintWeights: terrain.paintWeights
    } as typeof terrain;

    const migratedDocument = parseSceneDocumentJson(
      JSON.stringify(legacyTerrainDocument)
    );

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.terrains[terrain.id]?.collisionEnabled).toBe(true);
  });

  it("migrates pre-terrain scene documents by defaulting the terrain registry to empty", () => {
    const legacyDocument = {
      ...createEmptySceneDocument({ name: "Legacy Terrainless Scene" }),
      version: FOLLOW_ACTOR_PATH_SMOOTH_SCENE_DOCUMENT_VERSION
    };
    delete (legacyDocument as Partial<typeof legacyDocument>).terrains;

    const migratedDocument = parseSceneDocumentJson(
      JSON.stringify(legacyDocument)
    );

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.terrains).toEqual({});
  });

  it("round-trips authored scheduler routines in the scene document schema", () => {
    const npc = createNpcEntity({
      id: "entity-npc-clocksmith",
      actorId: "actor-clocksmith"
    });
    const document = createEmptySceneDocument({ name: "Schedule Scene" });
    document.entities[npc.id] = npc;
    document.scheduler.routines["routine-clocksmith-open"] =
      createProjectScheduleRoutine({
        id: "routine-clocksmith-open",
        title: "Clocksmith Open",
        target: createActorControlTargetRef(npc.actorId),
        startHour: 10,
        endHour: 18,
        effect: createSetActorPresenceControlEffect({
          target: createActorControlTargetRef(npc.actorId),
          active: true
        })
      });

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("round-trips actor scheduler animation and follow-path routines in the scene document schema", () => {
    const npcModelAsset = {
      id: "asset-model-patroller",
      kind: "model" as const,
      sourceName: "patroller.glb",
      mimeType: "model/gltf-binary",
      storageKey: "asset:model:patroller",
      byteLength: 1024,
      metadata: {
        kind: "model" as const,
        format: "glb" as const,
        sceneName: null,
        nodeCount: 1,
        meshCount: 1,
        materialNames: [],
        textureNames: [],
        animationNames: ["Walk"],
        boundingBox: null,
        warnings: []
      }
    };
    const actorTarget = createActorControlTargetRef("actor-patroller");
    const npc = createNpcEntity({
      id: "entity-npc-patroller",
      actorId: actorTarget.actorId,
      modelAssetId: npcModelAsset.id
    });
    const path = createScenePath({
      id: "path-patrol"
    });
    const document = createEmptySceneDocument({ name: "Schedule Scene" });
    document.assets[npcModelAsset.id] = npcModelAsset;
    document.entities[npc.id] = npc;
    document.paths[path.id] = path;
    document.scheduler.routines["routine-patrol"] =
      createProjectScheduleRoutine({
        id: "routine-patrol",
        title: "Patrolling",
        target: actorTarget,
        startHour: 9,
        endHour: 13,
        effects: [
          createSetActorPresenceControlEffect({
            target: actorTarget,
            active: true
          }),
          createPlayActorAnimationControlEffect({
            target: actorTarget,
            clipName: "Walk",
            loop: true
          }),
          createFollowActorPathControlEffect({
            target: actorTarget,
            pathId: path.id,
            speed: 2,
            loop: false,
            progressMode: "deriveFromTime"
          })
        ]
      });

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("round-trips a document containing a canonical box brush", () => {
    const brush = createBoxBrush({
      id: "brush-box-room",
      name: "Entry Room",
      center: {
        x: 0,
        y: 1,
        z: 0
      },
      size: {
        x: 4,
        y: 2,
        z: 6
      }
    });

    const document = {
      ...createEmptySceneDocument({ name: "Brush Scene" }),
      brushes: {
        [brush.id]: brush
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("round-trips floating-point whitebox box transforms without accidental snapping", () => {
    const brush = createBoxBrush({
      id: "brush-float-transform",
      center: {
        x: 1.25,
        y: 1.5,
        z: -0.875
      },
      rotationDegrees: {
        x: 12.5,
        y: 37.5,
        z: -8.25
      },
      size: {
        x: 2.5,
        y: 3.25,
        z: 4.75
      }
    });

    const document = {
      ...createEmptySceneDocument({ name: "Float Transform Scene" }),
      brushes: {
        [brush.id]: brush
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("round-trips per-face material and UV state", () => {
    const brush = createBoxBrush({
      id: "brush-face-room",
      center: {
        x: 2,
        y: 2,
        z: -1
      },
      size: {
        x: 6,
        y: 4,
        z: 8
      }
    });

    brush.faces.posX.materialId = "starter-amber-grid";
    brush.faces.posX.uv = {
      offset: {
        x: 0.5,
        y: -0.25
      },
      scale: {
        x: 0.25,
        y: 0.5
      },
      rotationQuarterTurns: 3,
      flipU: true,
      flipV: true
    };

    const document = {
      ...createEmptySceneDocument({ name: "Face UV Scene" }),
      brushes: {
        [brush.id]: brush
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("round-trips whitebox box volume settings", () => {
    const waterBrush = createBoxBrush({
      id: "brush-water-volume",
      volume: {
        mode: "water",
        water: {
          colorHex: "#2f79c4",
          surfaceOpacity: 0.65,
          waveStrength: 0.35,
          foamContactLimit: 9,
          surfaceDisplacementEnabled: true
        }
      }
    });
    const fogBrush = createBoxBrush({
      id: "brush-fog-volume",
      volume: {
        mode: "fog",
        fog: {
          colorHex: "#98a6bf",
          density: 0.45,
          padding: 0.2
        }
      }
    });
    const lightBrush = createBoxBrush({
      id: "brush-light-volume",
      volume: {
        mode: "light",
        light: {
          colorHex: "#ffe0b6",
          intensity: 2,
          padding: 0.4,
          falloff: "linear"
        }
      }
    });
    const document = {
      ...createEmptySceneDocument({ name: "Volume Round Trip Scene" }),
      brushes: {
        [waterBrush.id]: waterBrush,
        [fogBrush.id]: fogBrush,
        [lightBrush.id]: lightBrush
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("migrates pre-light-volume documents to the current schema version", () => {
    const legacyDocument = {
      ...createEmptySceneDocument({ name: "Legacy Box Volume Scene" }),
      version: DAWN_DUSK_BACKGROUND_IMAGE_SCENE_DOCUMENT_VERSION
    };
    legacyDocument.brushes["brush-water-legacy"] = createBoxBrush({
      id: "brush-water-legacy",
      volume: {
        mode: "water",
        water: {
          colorHex: "#4da6d9",
          surfaceOpacity: 0.55,
          waveStrength: 0.35,
          foamContactLimit: 6,
          surfaceDisplacementEnabled: false
        }
      }
    });

    const migratedDocument = parseSceneDocumentJson(
      JSON.stringify(legacyDocument)
    );

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.brushes["brush-water-legacy"]?.volume).toEqual({
      mode: "water",
      water: {
        colorHex: "#4da6d9",
        surfaceOpacity: 0.55,
        waveStrength: 0.35,
        foamContactLimit: 6,
        surfaceDisplacementEnabled: false
      }
    });
  });

  it("round-trips authored whitebox geometry vertices", () => {
    const brush = createBoxBrush({
      id: "brush-authored-geometry"
    });
    brush.geometry.vertices.posX_posY_posZ = {
      x: 1.5,
      y: 1,
      z: 1.25
    };
    brush.geometry.vertices.negX_negY_negZ = {
      x: -1,
      y: -1.25,
      z: -1.5
    };
    brush.size = deriveBoxBrushSizeFromGeometry(brush.geometry);

    const document = {
      ...createEmptySceneDocument({ name: "Authored Geometry Scene" }),
      brushes: {
        [brush.id]: brush
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("round-trips authored world environment settings", () => {
    const document = createEmptySceneDocument({
      name: "World Environment Scene"
    });
    document.world.background = {
      mode: "verticalGradient",
      topColorHex: "#6a87ab",
      bottomColorHex: "#151b23"
    };
    document.world.ambientLight = {
      colorHex: "#d4e2ff",
      intensity: 0.45
    };
    document.world.sunLight = {
      colorHex: "#ffd8a6",
      intensity: 2.25,
      direction: {
        x: -1,
        y: 0.8,
        z: 0.2
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("round-trips authored advanced rendering settings", () => {
    const document = createEmptySceneDocument({
      name: "Advanced Rendering Scene"
    });
    document.world.advancedRendering = {
      enabled: true,
      shadows: {
        enabled: true,
        mapSize: 4096,
        type: "pcf",
        bias: -0.001
      },
      ambientOcclusion: {
        enabled: true,
        intensity: 1.4,
        radius: 0.75,
        samples: 16
      },
      bloom: {
        enabled: true,
        intensity: 1.2,
        threshold: 0.9,
        radius: 0.4
      },
      toneMapping: {
        mode: "acesFilmic",
        exposure: 1.25
      },
      whiteboxBevel: {
        enabled: true,
        edgeWidth: 0.18,
        normalStrength: 0.9
      },
      fogPath: "quality",
      waterPath: "performance",
      waterReflectionMode: "world",
      depthOfField: {
        enabled: true,
        focusDistance: 12,
        focalLength: 0.045,
        bokehScale: 1.8
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("migrates v32 scene documents without whitebox bevel settings to defaults", () => {
    const emptyScene = createEmptySceneDocument({
      name: "Legacy Whitebox Bevel Scene"
    });
    const { whiteboxBevel: _whiteboxBevel, ...legacyAdvancedRendering } =
      emptyScene.world.advancedRendering;

    const migratedDocument = migrateSceneDocument({
      version: WHITEBOX_BEVEL_SCENE_DOCUMENT_VERSION,
      name: emptyScene.name,
      world: {
        ...emptyScene.world,
        advancedRendering: legacyAdvancedRendering
      },
      materials: emptyScene.materials,
      textures: emptyScene.textures,
      assets: emptyScene.assets,
      brushes: emptyScene.brushes,
      modelInstances: emptyScene.modelInstances,
      entities: emptyScene.entities,
      interactionLinks: emptyScene.interactionLinks
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.world.advancedRendering.whiteboxBevel).toEqual(
      emptyScene.world.advancedRendering.whiteboxBevel
    );
  });

  it("defaults missing water reflection mode and clamps legacy foam limits during migration", () => {
    const migratedDocument = migrateSceneDocument({
      version: SCENE_DOCUMENT_VERSION,
      name: "Legacy Water Settings",
      time: createDefaultProjectTimeSettings(),
      scheduler: createEmptySceneDocument().scheduler,
      world: {
        ...createEmptySceneDocument().world,
        advancedRendering: {
          ...createEmptySceneDocument().world.advancedRendering,
          enabled: true,
          waterPath: "quality"
        }
      },
      materials: STARTER_MATERIAL_LIBRARY.reduce<
        Record<string, (typeof STARTER_MATERIAL_LIBRARY)[number]>
      >((registry, material) => {
        registry[material.id] = material;
        return registry;
      }, {}),
      textures: {},
      assets: {},
      brushes: {
        "brush-water-legacy": {
          ...createBoxBrush({ id: "brush-water-legacy" }),
          volume: {
            mode: "water",
            water: {
              colorHex: "#2f79c4",
              surfaceOpacity: 0.65,
              waveStrength: 0.35,
              foamContactLimit: 999
            }
          }
        }
      },
      modelInstances: {},
      entities: {},
      interactionLinks: {}
    } as unknown);

    expect(migratedDocument.world.advancedRendering.waterReflectionMode).toBe(
      "none"
    );
    expect(migratedDocument.brushes["brush-water-legacy"]?.volume).toEqual({
      mode: "water",
      water: expect.objectContaining({
        foamContactLimit: 24,
        surfaceDisplacementEnabled: false
      })
    });
  });

  it("migrates legacy documents without advanced rendering settings to defaults", () => {
    const emptyScene = createEmptySceneDocument({
      name: "Legacy Advanced Rendering Scene"
    });
    const { advancedRendering: _advancedRendering, ...legacyWorld } =
      emptyScene.world;

    const migratedDocument = migrateSceneDocument({
      version: SPATIAL_AUDIO_SCENE_DOCUMENT_VERSION,
      name: emptyScene.name,
      world: legacyWorld,
      materials: emptyScene.materials,
      textures: emptyScene.textures,
      assets: emptyScene.assets,
      brushes: emptyScene.brushes,
      modelInstances: emptyScene.modelInstances,
      entities: emptyScene.entities,
      interactionLinks: emptyScene.interactionLinks
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.world.advancedRendering).toEqual(
      emptyScene.world.advancedRendering
    );
  });

  it("round-trips authored local lights and an image background asset", () => {
    const imageAsset = {
      id: "asset-background-panorama",
      kind: "image",
      sourceName: "skybox-panorama.svg",
      mimeType: "image/svg+xml",
      storageKey: createProjectAssetStorageKey("asset-background-panorama"),
      byteLength: 2048,
      metadata: {
        kind: "image" as const,
        width: 512,
        height: 256,
        hasAlpha: false,
        warnings: []
      }
    } satisfies ImageAssetRecord;
    const pointLight = createPointLightEntity({
      id: "entity-point-light-main",
      position: {
        x: 2,
        y: 3,
        z: 1
      },
      colorHex: "#ffddaa",
      intensity: 1.5,
      distance: 10
    });
    const spotLight = createSpotLightEntity({
      id: "entity-spot-light-main",
      position: {
        x: -2,
        y: 4,
        z: 0
      },
      direction: {
        x: 0.25,
        y: -1,
        z: 0.15
      },
      colorHex: "#d6e6ff",
      intensity: 2,
      distance: 14,
      angleDegrees: 42
    });
    const document = {
      ...createEmptySceneDocument({ name: "Local Light and Background Scene" }),
      assets: {
        [imageAsset.id]: imageAsset
      },
      entities: {
        [pointLight.id]: pointLight,
        [spotLight.id]: spotLight
      }
    };
    document.world.background = {
      mode: "image",
      assetId: imageAsset.id,
      environmentIntensity: 0.75
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("round-trips a document containing an authored PlayerStart entity", () => {
    const playerStart = createPlayerStartEntity({
      id: "entity-player-start-main",
      name: "Main Spawn",
      position: {
        x: 4,
        y: 0,
        z: -2
      },
      yawDegrees: 135,
      movementTemplate: {
        kind: "custom",
        moveSpeed: 5.25,
        capabilities: {
          jump: true,
          sprint: false,
          crouch: true
        },
        jump: {
          speed: 8.1,
          bufferMs: 120,
          coyoteTimeMs: 90,
          variableHeight: true,
          maxHoldMs: 220,
          moveWhileJumping: false,
          moveWhileFalling: false,
          directionOnly: true
        },
        sprint: {
          speedMultiplier: 1.8
        },
        crouch: {
          speedMultiplier: 0.55
        }
      },
      collider: {
        mode: "box",
        eyeHeight: 1.4,
        capsuleRadius: 0.3,
        capsuleHeight: 1.8,
        boxSize: {
          x: 0.8,
          y: 1.6,
          z: 0.7
        }
      },
      inputBindings: {
        keyboard: {
          moveForward: "KeyQ",
          moveBackward: "BracketLeft",
          moveLeft: "Comma",
          moveRight: "Period",
          jump: "Space",
          sprint: "ShiftRight",
          crouch: "KeyC",
          interact: "MouseLeft",
          pauseTime: "KeyP"
        },
        gamepad: {
          moveForward: "dpadUp",
          moveBackward: "dpadDown",
          moveLeft: "dpadLeft",
          moveRight: "dpadRight",
          jump: "buttonNorth",
          sprint: "rightShoulder",
          crouch: "buttonEast",
          interact: "buttonWest",
          pauseTime: "buttonMenu",
          cameraLook: "rightStick"
        }
      }
    });
    const document = {
      ...createEmptySceneDocument({ name: "Player Start Scene" }),
      entities: {
        [playerStart.id]: playerStart
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  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("round-trips a document containing an authored rail Camera Rig entity", () => {
    const path = createScenePath({
      id: "path-camera-rail-roundtrip"
    });
    const interactable = createInteractableEntity({
      id: "entity-camera-rail-anchor",
      position: {
        x: 3,
        y: 1,
        z: -2
      }
    });
    const cameraRig = createCameraRigEntity({
      id: "entity-camera-rig-rail-main",
      name: "Catwalk Rail",
      rigType: "rail",
      pathId: path.id,
      priority: 8,
      defaultActive: true,
      target: {
        kind: "entity",
        entityId: interactable.id
      },
      targetOffset: {
        x: 0,
        y: 1.5,
        z: 0
      },
      transitionMode: "cut",
      lookAround: {
        enabled: true,
        yawLimitDegrees: 18,
        pitchLimitDegrees: 10,
        recenterSpeed: 5
      }
    });
    const document = {
      ...createEmptySceneDocument({ name: "Rail Camera Rig Scene" }),
      paths: {
        [path.id]: path
      },
      entities: {
        [interactable.id]: interactable,
        [cameraRig.id]: cameraRig
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("round-trips a document containing an authored mapped rail Camera Rig entity", () => {
    const path = createScenePath({
      id: "path-camera-rail-mapped-roundtrip",
      points: [
        {
          id: "point-a",
          position: {
            x: 0,
            y: 3,
            z: 0
          }
        },
        {
          id: "point-b",
          position: {
            x: 12,
            y: 3,
            z: 0
          }
        }
      ]
    });
    const interactable = createInteractableEntity({
      id: "entity-camera-mapped-rail-anchor",
      position: {
        x: 5,
        y: 1,
        z: 2
      }
    });
    const cameraRig = createCameraRigEntity({
      id: "entity-camera-rig-rail-mapped-main",
      name: "Gallery Rail",
      rigType: "rail",
      pathId: path.id,
      railPlacementMode: "mapTargetBetweenPoints",
      trackStartPoint: {
        x: 0,
        y: 1,
        z: 2
      },
      trackEndPoint: {
        x: 10,
        y: 1,
        z: 2
      },
      railStartProgress: 0.2,
      railEndProgress: 0.8,
      target: {
        kind: "entity",
        entityId: interactable.id
      },
      targetOffset: {
        x: 0,
        y: 1.5,
        z: 0
      },
      transitionMode: "blend",
      transitionDurationSeconds: 0.45
    });
    const document = {
      ...createEmptySceneDocument({ name: "Mapped Rail Camera Rig Scene" }),
      paths: {
        [path.id]: path
      },
      entities: {
        [interactable.id]: interactable,
        [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 77 rail camera rigs to default nearest placement mode", () => {
    const path = createScenePath({
      id: "path-camera-rail-legacy"
    });
    const cameraRig = createCameraRigEntity({
      id: "entity-camera-rig-rail-legacy",
      rigType: "rail",
      pathId: path.id
    });
    const legacyDocument = {
      ...createEmptySceneDocument({ name: "Legacy Rail Camera Rig Scene" }),
      paths: {
        [path.id]: path
      },
      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_CONTROL_SURFACE_SCENE_DOCUMENT_VERSION;
    delete serializedCameraRig.railPlacementMode;
    delete serializedCameraRig.trackStartPoint;
    delete serializedCameraRig.trackEndPoint;
    delete serializedCameraRig.railStartProgress;
    delete serializedCameraRig.railEndProgress;

    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,
        rigType: "rail",
        pathId: path.id,
        target: cameraRig.target
      })
    );
  });

  it("migrates version 14 documents without entity names", () => {
    const pointLight = createPointLightEntity({
      id: "entity-point-light-legacy",
      position: {
        x: 2,
        y: 3,
        z: 1
      },
      colorHex: "#ffeeaa",
      intensity: 1.75,
      distance: 9
    });
    const legacyDocument = {
      ...createEmptySceneDocument({ name: "Legacy Entity Name Scene" }),
      version: 14 as const,
      entities: {
        [pointLight.id]: pointLight
      }
    };

    const migratedDocument = migrateSceneDocument(legacyDocument);

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.entities[pointLight.id]).toEqual(pointLight);
  });

  it("migrates version 15 model instances to include default collider settings", () => {
    const asset = {
      id: "asset-model-legacy-collider",
      kind: "model",
      sourceName: "legacy-collider.glb",
      mimeType: "model/gltf-binary",
      storageKey: createProjectAssetStorageKey("asset-model-legacy-collider"),
      byteLength: 64,
      metadata: {
        kind: "model",
        format: "glb",
        sceneName: "Legacy Collider Scene",
        nodeCount: 1,
        meshCount: 1,
        materialNames: [],
        textureNames: [],
        animationNames: [],
        boundingBox: {
          min: {
            x: -0.5,
            y: 0,
            z: -0.5
          },
          max: {
            x: 0.5,
            y: 1,
            z: 0.5
          },
          size: {
            x: 1,
            y: 1,
            z: 1
          }
        },
        warnings: []
      }
    } satisfies ModelAssetRecord;
    const migratedDocument = migrateSceneDocument({
      ...createEmptySceneDocument({ name: "Legacy Model Collider Scene" }),
      version: ENTITY_NAMES_SCENE_DOCUMENT_VERSION,
      assets: {
        [asset.id]: asset
      },
      modelInstances: {
        "model-instance-legacy-collider": {
          id: "model-instance-legacy-collider",
          kind: "modelInstance",
          assetId: asset.id,
          position: {
            x: 1,
            y: 0,
            z: -2
          },
          rotationDegrees: {
            x: 0,
            y: 45,
            z: 0
          },
          scale: {
            x: 1,
            y: 1,
            z: 1
          }
        }
      }
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(
      migratedDocument.modelInstances["model-instance-legacy-collider"]
        .collision
    ).toEqual({
      mode: "none",
      visible: false
    });
  });

  it("migrates version 17 box brushes to include default whitebox rotation", () => {
    const migratedDocument = migrateSceneDocument({
      ...createEmptySceneDocument({ name: "Legacy Whitebox Transform Scene" }),
      version: PLAYER_START_COLLIDER_SETTINGS_SCENE_DOCUMENT_VERSION,
      brushes: {
        "brush-legacy-room": {
          id: "brush-legacy-room",
          kind: "box",
          center: {
            x: 1.25,
            y: 1.5,
            z: -0.75
          },
          size: {
            x: 2.5,
            y: 3.25,
            z: 4.75
          },
          faces: {
            posX: { materialId: null, uv: createBoxBrush().faces.posX.uv },
            negX: { materialId: null, uv: createBoxBrush().faces.negX.uv },
            posY: { materialId: null, uv: createBoxBrush().faces.posY.uv },
            negY: { materialId: null, uv: createBoxBrush().faces.negY.uv },
            posZ: { materialId: null, uv: createBoxBrush().faces.posZ.uv },
            negZ: { materialId: null, uv: createBoxBrush().faces.negZ.uv }
          }
        }
      }
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.brushes["brush-legacy-room"]).toMatchObject({
      center: {
        x: 1.25,
        y: 1.5,
        z: -0.75
      },
      size: {
        x: 2.5,
        y: 3.25,
        z: 4.75
      },
      rotationDegrees: {
        x: 0,
        y: 0,
        z: 0
      }
    });
  });

  it("migrates version 16 Player Start entities to include default collider settings", () => {
    const playerStart = {
      id: "entity-player-start-legacy-collider",
      kind: "playerStart" as const,
      position: {
        x: 1,
        y: 0,
        z: -3
      },
      yawDegrees: 45
    };
    const legacyDocument = {
      ...createEmptySceneDocument({ name: "Legacy Player Collider Scene" }),
      version: IMPORTED_MODEL_COLLIDERS_SCENE_DOCUMENT_VERSION,
      entities: {
        [playerStart.id]: playerStart
      }
    };

    const migratedDocument = migrateSceneDocument(legacyDocument);

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.entities[playerStart.id]).toEqual(
      createPlayerStartEntity({
        id: playerStart.id,
        position: playerStart.position,
        yawDegrees: playerStart.yawDegrees
      })
    );
  });

  it("migrates version 24 Player Start entities to default to first-person navigation", () => {
    const playerStart = {
      id: "entity-player-start-legacy-navigation",
      kind: "playerStart" as const,
      position: {
        x: 0,
        y: 0,
        z: 1
      },
      yawDegrees: 180,
      collider: {
        mode: "capsule" as const,
        eyeHeight: 1.6,
        capsuleRadius: 0.3,
        capsuleHeight: 1.8,
        boxSize: {
          x: 0.6,
          y: 1.8,
          z: 0.6
        }
      }
    };
    const legacyDocument = {
      ...createEmptySceneDocument({ name: "Legacy Player Navigation Scene" }),
      version: SCENE_TRANSITION_ENTITIES_SCENE_DOCUMENT_VERSION,
      entities: {
        [playerStart.id]: playerStart
      }
    };

    const migratedDocument = migrateSceneDocument(legacyDocument);

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.entities[playerStart.id]).toEqual(
      createPlayerStartEntity({
        ...playerStart,
        navigationMode: "firstPerson"
      })
    );
  });

  it("migrates version 25 Player Start entities to include default input bindings", () => {
    const playerStart = {
      id: "entity-player-start-legacy-bindings",
      kind: "playerStart" as const,
      position: {
        x: 1,
        y: 0,
        z: -2
      },
      yawDegrees: 90,
      navigationMode: "thirdPerson" as const,
      collider: {
        mode: "capsule" as const,
        eyeHeight: 1.6,
        capsuleRadius: 0.3,
        capsuleHeight: 1.8,
        boxSize: {
          x: 0.6,
          y: 1.8,
          z: 0.6
        }
      }
    };
    const legacyDocument = {
      ...createEmptySceneDocument({ name: "Legacy Player Binding Scene" }),
      version: PLAYER_START_NAVIGATION_MODE_SCENE_DOCUMENT_VERSION,
      entities: {
        [playerStart.id]: playerStart
      }
    };

    const migratedDocument = migrateSceneDocument(legacyDocument);

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.entities[playerStart.id]).toEqual(
      createPlayerStartEntity({
        ...playerStart
      })
    );
  });

  it("migrates version 26 Player Start input bindings to include default gamepad camera look", () => {
    const playerStart = createPlayerStartEntity({
      id: "entity-player-start-legacy-camera-look",
      position: {
        x: 2,
        y: 0,
        z: 0
      }
    });
    const legacyDocument = {
      ...createEmptySceneDocument({ name: "Legacy Player Camera Look Scene" }),
      version: PLAYER_START_INPUT_BINDINGS_SCENE_DOCUMENT_VERSION,
      entities: {
        [playerStart.id]: {
          ...playerStart,
          inputBindings: {
            ...playerStart.inputBindings,
            gamepad: {
              moveForward: playerStart.inputBindings.gamepad.moveForward,
              moveBackward: playerStart.inputBindings.gamepad.moveBackward,
              moveLeft: playerStart.inputBindings.gamepad.moveLeft,
              moveRight: playerStart.inputBindings.gamepad.moveRight
            }
          }
        }
      }
    };

    const migratedDocument = migrateSceneDocument(legacyDocument);

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.entities[playerStart.id]).toEqual(playerStart);
  });

  it("migrates version 51 Player Start input bindings to include default pause bindings", () => {
    const playerStart = createPlayerStartEntity({
      id: "entity-player-start-legacy-pause-binding"
    });
    const legacyDocument = {
      ...createEmptySceneDocument({
        name: "Legacy Player Pause Binding Scene"
      }),
      version: NPC_DIALOGUE_REFERENCE_SCENE_DOCUMENT_VERSION,
      entities: {
        [playerStart.id]: {
          ...playerStart,
          inputBindings: {
            keyboard: {
              moveForward: playerStart.inputBindings.keyboard.moveForward,
              moveBackward: playerStart.inputBindings.keyboard.moveBackward,
              moveLeft: playerStart.inputBindings.keyboard.moveLeft,
              moveRight: playerStart.inputBindings.keyboard.moveRight,
              jump: playerStart.inputBindings.keyboard.jump,
              sprint: playerStart.inputBindings.keyboard.sprint,
              crouch: playerStart.inputBindings.keyboard.crouch
            },
            gamepad: {
              moveForward: playerStart.inputBindings.gamepad.moveForward,
              moveBackward: playerStart.inputBindings.gamepad.moveBackward,
              moveLeft: playerStart.inputBindings.gamepad.moveLeft,
              moveRight: playerStart.inputBindings.gamepad.moveRight,
              jump: playerStart.inputBindings.gamepad.jump,
              sprint: playerStart.inputBindings.gamepad.sprint,
              crouch: playerStart.inputBindings.gamepad.crouch,
              cameraLook: playerStart.inputBindings.gamepad.cameraLook
            }
          }
        }
      }
    };

    const migratedDocument = migrateSceneDocument(legacyDocument);

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.entities[playerStart.id]).toEqual(playerStart);
  });

  it("migrates version 81 Player Start input bindings to include default interact bindings", () => {
    const playerStart = createPlayerStartEntity({
      id: "entity-player-start-legacy-interact-binding"
    });
    const legacyDocument = {
      ...createEmptySceneDocument({
        name: "Legacy Player Interact Binding Scene"
      }),
      version: PLAYER_START_INTERACTION_ANGLE_SCENE_DOCUMENT_VERSION,
      entities: {
        [playerStart.id]: {
          ...playerStart,
          inputBindings: {
            keyboard: {
              moveForward: playerStart.inputBindings.keyboard.moveForward,
              moveBackward: playerStart.inputBindings.keyboard.moveBackward,
              moveLeft: playerStart.inputBindings.keyboard.moveLeft,
              moveRight: playerStart.inputBindings.keyboard.moveRight,
              jump: playerStart.inputBindings.keyboard.jump,
              sprint: playerStart.inputBindings.keyboard.sprint,
              crouch: playerStart.inputBindings.keyboard.crouch,
              pauseTime: playerStart.inputBindings.keyboard.pauseTime
            },
            gamepad: {
              moveForward: playerStart.inputBindings.gamepad.moveForward,
              moveBackward: playerStart.inputBindings.gamepad.moveBackward,
              moveLeft: playerStart.inputBindings.gamepad.moveLeft,
              moveRight: playerStart.inputBindings.gamepad.moveRight,
              jump: playerStart.inputBindings.gamepad.jump,
              sprint: playerStart.inputBindings.gamepad.sprint,
              crouch: playerStart.inputBindings.gamepad.crouch,
              pauseTime: playerStart.inputBindings.gamepad.pauseTime,
              cameraLook: playerStart.inputBindings.gamepad.cameraLook
            }
          }
        }
      }
    };

    const migratedDocument = migrateSceneDocument(legacyDocument);

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.entities[playerStart.id]).toEqual(playerStart);
  });

  it("migrates version 30 Player Start entities to include the default movement template", () => {
    const playerStart = {
      id: "entity-player-start-legacy-movement-template",
      kind: "playerStart" as const,
      position: {
        x: -1,
        y: 0,
        z: 3
      },
      yawDegrees: 30,
      navigationMode: "thirdPerson" as const,
      inputBindings: {
        keyboard: {
          moveForward: "KeyW",
          moveBackward: "KeyS",
          moveLeft: "KeyA",
          moveRight: "KeyD"
        },
        gamepad: {
          moveForward: "leftStickUp" as const,
          moveBackward: "leftStickDown" as const,
          moveLeft: "leftStickLeft" as const,
          moveRight: "leftStickRight" as const,
          cameraLook: "rightStick" as const
        }
      },
      collider: {
        mode: "capsule" as const,
        eyeHeight: 1.6,
        capsuleRadius: 0.3,
        capsuleHeight: 1.8,
        boxSize: {
          x: 0.6,
          y: 1.8,
          z: 0.6
        }
      }
    };
    const legacyDocument = {
      ...createEmptySceneDocument({
        name: "Legacy Player Movement Template Scene"
      }),
      version: STATIC_SIMPLE_MODEL_COLLIDERS_SCENE_DOCUMENT_VERSION,
      entities: {
        [playerStart.id]: playerStart
      }
    };

    const migratedDocument = migrateSceneDocument(legacyDocument);

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.entities[playerStart.id]).toEqual(
      createPlayerStartEntity({
        ...playerStart
      })
    );
  });

  it("migrates version 31 Player Start movement bindings to include default jump sprint and crouch actions", () => {
    const playerStart = {
      id: "entity-player-start-legacy-locomotion-bindings",
      kind: "playerStart" as const,
      position: {
        x: 1,
        y: 0,
        z: -4
      },
      yawDegrees: 0,
      navigationMode: "firstPerson" as const,
      movementTemplate: {
        kind: "default" as const,
        moveSpeed: 4.5,
        capabilities: {
          jump: true,
          sprint: true,
          crouch: true
        }
      },
      inputBindings: {
        keyboard: {
          moveForward: "KeyW",
          moveBackward: "KeyS",
          moveLeft: "KeyA",
          moveRight: "KeyD"
        },
        gamepad: {
          moveForward: "leftStickUp" as const,
          moveBackward: "leftStickDown" as const,
          moveLeft: "leftStickLeft" as const,
          moveRight: "leftStickRight" as const,
          cameraLook: "rightStick" as const
        }
      },
      collider: {
        mode: "capsule" as const,
        eyeHeight: 1.6,
        capsuleRadius: 0.3,
        capsuleHeight: 1.8,
        boxSize: {
          x: 0.6,
          y: 1.8,
          z: 0.6
        }
      }
    };
    const legacyDocument = {
      ...createEmptySceneDocument({
        name: "Legacy Player Locomotion Binding Scene"
      }),
      version: PLAYER_START_MOVEMENT_TEMPLATE_SCENE_DOCUMENT_VERSION,
      entities: {
        [playerStart.id]: playerStart
      }
    };

    const migratedDocument = migrateSceneDocument(legacyDocument);

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.entities[playerStart.id]).toEqual(
      createPlayerStartEntity({
        ...playerStart
      })
    );
  });

  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,
      directionOnly: _directionOnly,
      ...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("migrates version 34 Player Start jump settings to include default air direction mode", () => {
    const playerStart = createPlayerStartEntity({
      id: "entity-player-start-legacy-air-direction",
      movementTemplate: {
        kind: "responsive"
      }
    });
    const { directionOnly: _directionOnly, ...legacyJump } =
      playerStart.movementTemplate.jump;
    const legacyDocument = {
      ...createEmptySceneDocument({
        name: "Legacy Player Air Direction Scene"
      }),
      version: PLAYER_START_AIR_CONTROL_SCENE_DOCUMENT_VERSION,
      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("migrates version 35 documents to default authored visible and enabled state while preserving interaction enablement", () => {
    const brush = createBoxBrush({
      id: "brush-authored-state-legacy"
    });
    const modelAsset: ModelAssetRecord = {
      id: "asset-model-authored-state-legacy",
      kind: "model",
      sourceName: "legacy-authored-state.glb",
      mimeType: "model/gltf-binary",
      storageKey: createProjectAssetStorageKey(
        "asset-model-authored-state-legacy"
      ),
      byteLength: 2048,
      metadata: {
        kind: "model",
        format: "glb",
        sceneName: null,
        nodeCount: 1,
        meshCount: 1,
        materialNames: [],
        textureNames: [],
        animationNames: [],
        boundingBox: null,
        warnings: []
      }
    };
    const modelInstance = createModelInstance({
      id: "model-instance-authored-state-legacy",
      assetId: modelAsset.id
    });
    const interactable = createInteractableEntity({
      id: "entity-interactable-authored-state-legacy",
      interactionEnabled: false
    });
    const {
      visible: _legacyBrushVisible,
      enabled: _legacyBrushEnabled,
      ...legacyBrush
    } = brush;
    const {
      visible: _legacyModelVisible,
      enabled: _legacyModelEnabled,
      ...legacyModelInstance
    } = modelInstance;
    const {
      visible: _legacyInteractableVisible,
      enabled: _legacyInteractableEnabled,
      interactionEnabled: legacyInteractableInteractionEnabled,
      ...legacyInteractable
    } = interactable;

    const migratedDocument = migrateSceneDocument({
      ...createEmptySceneDocument({ name: "Legacy Authored State Scene" }),
      version: PLAYER_START_AIR_DIRECTION_CONTROL_SCENE_DOCUMENT_VERSION,
      assets: {
        [modelAsset.id]: modelAsset
      },
      brushes: {
        [brush.id]: legacyBrush
      },
      modelInstances: {
        [modelInstance.id]: legacyModelInstance
      },
      entities: {
        [interactable.id]: {
          ...legacyInteractable,
          enabled: legacyInteractableInteractionEnabled
        }
      }
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.brushes[brush.id]).toMatchObject({
      visible: true,
      enabled: true
    });
    expect(migratedDocument.modelInstances[modelInstance.id]).toMatchObject({
      visible: true,
      enabled: true
    });
    expect(migratedDocument.entities[interactable.id]).toMatchObject({
      visible: true,
      enabled: true,
      interactionEnabled: false
    });
  });

  it("round-trips authored third-person Player Start navigation", () => {
    const playerStart = createPlayerStartEntity({
      id: "entity-player-start-third-person",
      navigationMode: "thirdPerson"
    });
    const document = {
      ...createEmptySceneDocument({ name: "Third Person Player Start Scene" }),
      entities: {
        [playerStart.id]: playerStart
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("round-trips the initial typed entity registry without mixing entities into model instances", () => {
    const playerStart = createPlayerStartEntity({
      id: "entity-player-start-main"
    });
    const audioAsset = {
      id: "asset-audio-main",
      kind: "audio" as const,
      sourceName: "lobby-loop.ogg",
      mimeType: "audio/ogg",
      storageKey: createProjectAssetStorageKey("asset-audio-main"),
      byteLength: 4096,
      metadata: {
        kind: "audio" as const,
        durationSeconds: 4.5,
        channelCount: 2,
        sampleRateHz: 48000,
        warnings: []
      }
    } satisfies AudioAssetRecord;
    const soundEmitter = createSoundEmitterEntity({
      id: "entity-sound-main",
      position: {
        x: 1,
        y: 2,
        z: 3
      },
      audioAssetId: audioAsset.id,
      volume: 0.6,
      refDistance: 7,
      maxDistance: 18,
      autoplay: true,
      loop: true
    });
    const triggerVolume = createTriggerVolumeEntity({
      id: "entity-trigger-main",
      position: {
        x: 0,
        y: 1,
        z: 0
      },
      size: {
        x: 2,
        y: 3,
        z: 4
      }
    });
    const teleportTarget = createTeleportTargetEntity({
      id: "entity-teleport-main",
      position: {
        x: -3,
        y: 0,
        z: 5
      },
      yawDegrees: 180
    });
    const interactable = createInteractableEntity({
      id: "entity-interactable-main",
      position: {
        x: 2,
        y: 1,
        z: -2
      },
      radius: 1.25,
      prompt: "Open Door",
      enabled: true
    });
    const document = {
      ...createEmptySceneDocument({ name: "Typed Entity Scene" }),
      assets: {
        [audioAsset.id]: audioAsset
      },
      entities: {
        [playerStart.id]: playerStart,
        [soundEmitter.id]: soundEmitter,
        [triggerVolume.id]: triggerVolume,
        [teleportTarget.id]: teleportTarget,
        [interactable.id]: interactable
      }
    };

    const roundTripDocument = parseSceneDocumentJson(
      serializeSceneDocument(document)
    );

    expect(roundTripDocument).toEqual(document);
    expect(roundTripDocument.modelInstances).toEqual({});
  });

  it("round-trips NPC entities with stable actor ids and optional model refs", () => {
    const modelAsset = {
      id: "asset-model-npc",
      kind: "model" as const,
      sourceName: "npc-guide.glb",
      mimeType: "model/gltf-binary",
      storageKey: createProjectAssetStorageKey("asset-model-npc"),
      byteLength: 512,
      metadata: {
        kind: "model" as const,
        format: "glb" as const,
        sceneName: "NPC Guide",
        nodeCount: 1,
        meshCount: 1,
        materialNames: [],
        textureNames: [],
        animationNames: [],
        boundingBox: null,
        warnings: []
      }
    } satisfies ModelAssetRecord;
    const npc = createNpcEntity({
      id: "entity-npc-guide",
      actorId: "actor-town-guide",
      presence: createNpcTimeWindowPresence({
        startHour: 21.5,
        endHour: 2.5
      }),
      position: {
        x: 4,
        y: 0,
        z: -2
      },
      yawDegrees: 225,
      modelAssetId: modelAsset.id,
      collider: {
        mode: "box",
        eyeHeight: 1.4,
        boxSize: {
          x: 0.9,
          y: 1.7,
          z: 0.8
        }
      }
    });
    const document = {
      ...createEmptySceneDocument({ name: "NPC Round Trip Scene" }),
      assets: {
        [modelAsset.id]: modelAsset
      },
      entities: {
        [npc.id]: npc
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("round-trips authored playSound and stopSound interaction links", () => {
    const audioAsset = {
      id: "asset-audio-main",
      kind: "audio" as const,
      sourceName: "lobby-loop.ogg",
      mimeType: "audio/ogg",
      storageKey: createProjectAssetStorageKey("asset-audio-main"),
      byteLength: 4096,
      metadata: {
        kind: "audio" as const,
        durationSeconds: 4.5,
        channelCount: 2,
        sampleRateHz: 48000,
        warnings: []
      }
    } satisfies AudioAssetRecord;
    const triggerVolume = createTriggerVolumeEntity({
      id: "entity-trigger-main"
    });
    const soundEmitter = createSoundEmitterEntity({
      id: "entity-sound-main",
      audioAssetId: audioAsset.id
    });
    const playLink = createPlaySoundInteractionLink({
      id: "link-play-sound",
      sourceEntityId: triggerVolume.id,
      trigger: "enter",
      targetSoundEmitterId: soundEmitter.id
    });
    const stopLink = createStopSoundInteractionLink({
      id: "link-stop-sound",
      sourceEntityId: triggerVolume.id,
      trigger: "exit",
      targetSoundEmitterId: soundEmitter.id
    });
    const document = {
      ...createEmptySceneDocument({ name: "Sound Link Scene" }),
      assets: {
        [audioAsset.id]: audioAsset
      },
      entities: {
        [triggerVolume.id]: triggerVolume,
        [soundEmitter.id]: soundEmitter
      },
      interactionLinks: {
        [playLink.id]: playLink,
        [stopLink.id]: stopLink
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("round-trips imported model assets and placed model instances", () => {
    const asset = {
      id: "asset-model-triangle",
      kind: "model",
      sourceName: "tiny-triangle.gltf",
      mimeType: "model/gltf+json",
      storageKey: createProjectAssetStorageKey("asset-model-triangle"),
      byteLength: 36,
      metadata: {
        kind: "model",
        format: "gltf",
        sceneName: "Fixture Triangle Scene",
        nodeCount: 2,
        meshCount: 1,
        materialNames: ["Fixture Material"],
        textureNames: [],
        animationNames: [],
        boundingBox: {
          min: {
            x: 0,
            y: 0,
            z: 0
          },
          max: {
            x: 1,
            y: 1,
            z: 0
          },
          size: {
            x: 1,
            y: 1,
            z: 0
          }
        },
        warnings: []
      }
    } satisfies ModelAssetRecord;
    const modelInstance = createModelInstance({
      id: "model-instance-triangle",
      assetId: asset.id,
      name: "Fixture Triangle",
      position: {
        x: 4,
        y: 2,
        z: -3
      },
      rotationDegrees: {
        x: 0,
        y: 45,
        z: 0
      },
      scale: {
        x: 1.5,
        y: 2,
        z: 1.5
      }
    });
    const document = {
      ...createEmptySceneDocument({ name: "Imported Asset Scene" }),
      assets: {
        [asset.id]: asset
      },
      modelInstances: {
        [modelInstance.id]: modelInstance
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("round-trips authored model-instance collision settings", () => {
    const asset = {
      id: "asset-model-collider",
      kind: "model",
      sourceName: "collision-test.glb",
      mimeType: "model/gltf-binary",
      storageKey: createProjectAssetStorageKey("asset-model-collider"),
      byteLength: 64,
      metadata: {
        kind: "model",
        format: "glb",
        sceneName: "Collision Test Scene",
        nodeCount: 1,
        meshCount: 1,
        materialNames: [],
        textureNames: [],
        animationNames: [],
        boundingBox: {
          min: {
            x: -1,
            y: 0,
            z: -1
          },
          max: {
            x: 1,
            y: 2,
            z: 1
          },
          size: {
            x: 2,
            y: 2,
            z: 2
          }
        },
        warnings: []
      }
    } satisfies ModelAssetRecord;
    const modelInstance = createModelInstance({
      id: "model-instance-collider",
      assetId: asset.id,
      collision: {
        mode: "dynamic",
        visible: true
      }
    });
    const document = {
      ...createEmptySceneDocument({ name: "Model Collision Scene" }),
      assets: {
        [asset.id]: asset
      },
      modelInstances: {
        [modelInstance.id]: modelInstance
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("round-trips authored static-simple model-instance collision settings", () => {
    const asset = {
      id: "asset-model-static-simple-collider",
      kind: "model",
      sourceName: "collision-static-simple.glb",
      mimeType: "model/gltf-binary",
      storageKey: createProjectAssetStorageKey(
        "asset-model-static-simple-collider"
      ),
      byteLength: 64,
      metadata: {
        kind: "model",
        format: "glb",
        sceneName: "Static Simple Collision Test Scene",
        nodeCount: 1,
        meshCount: 1,
        materialNames: [],
        textureNames: [],
        animationNames: [],
        boundingBox: {
          min: {
            x: -2,
            y: 0,
            z: -0.1
          },
          max: {
            x: 2,
            y: 4,
            z: 0.1
          },
          size: {
            x: 4,
            y: 4,
            z: 0.2
          }
        },
        warnings: []
      }
    } satisfies ModelAssetRecord;
    const modelInstance = createModelInstance({
      id: "model-instance-static-simple-collider",
      assetId: asset.id,
      collision: {
        mode: "static-simple",
        visible: true
      }
    });
    const document = {
      ...createEmptySceneDocument({
        name: "Static Simple Model Collision Scene"
      }),
      assets: {
        [asset.id]: asset
      },
      modelInstances: {
        [modelInstance.id]: modelInstance
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("migrates version 29 scene documents to preserve existing model collision settings", () => {
    const asset = {
      id: "asset-model-v29-collider",
      kind: "model",
      sourceName: "v29-collider.glb",
      mimeType: "model/gltf-binary",
      storageKey: createProjectAssetStorageKey("asset-model-v29-collider"),
      byteLength: 64,
      metadata: {
        kind: "model",
        format: "glb",
        sceneName: "V29 Collider Scene",
        nodeCount: 1,
        meshCount: 1,
        materialNames: [],
        textureNames: [],
        animationNames: [],
        boundingBox: {
          min: {
            x: -1,
            y: 0,
            z: -1
          },
          max: {
            x: 1,
            y: 2,
            z: 1
          },
          size: {
            x: 2,
            y: 2,
            z: 2
          }
        },
        warnings: []
      }
    } satisfies ModelAssetRecord;
    const legacyDocument = {
      ...createEmptySceneDocument({ name: "Legacy V29 Collider Scene" }),
      version: SCENE_EDITOR_PREFERENCES_SCENE_DOCUMENT_VERSION,
      assets: {
        [asset.id]: asset
      },
      modelInstances: {
        "model-instance-v29-collider": {
          ...createModelInstance({
            id: "model-instance-v29-collider",
            assetId: asset.id,
            collision: {
              mode: "dynamic",
              visible: true
            }
          })
        }
      }
    };

    const migratedDocument = migrateSceneDocument(legacyDocument);

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(
      migratedDocument.modelInstances["model-instance-v29-collider"].collision
    ).toEqual({
      mode: "dynamic",
      visible: true
    });
  });

  it("round-trips canonical interaction links", () => {
    const triggerVolume = createTriggerVolumeEntity({
      id: "entity-trigger-main"
    });
    const interactable = createInteractableEntity({
      id: "entity-interactable-main",
      prompt: "Use Console"
    });
    const teleportTarget = createTeleportTargetEntity({
      id: "entity-teleport-main"
    });
    const brush = createBoxBrush({
      id: "brush-door"
    });
    const document = {
      ...createEmptySceneDocument({ name: "Interaction Scene" }),
      brushes: {
        [brush.id]: brush
      },
      entities: {
        [triggerVolume.id]: triggerVolume,
        [interactable.id]: interactable,
        [teleportTarget.id]: teleportTarget
      },
      interactionLinks: {
        "link-teleport": createTeleportPlayerInteractionLink({
          id: "link-teleport",
          sourceEntityId: triggerVolume.id,
          trigger: "enter",
          targetEntityId: teleportTarget.id
        }),
        "link-hide-door": createToggleVisibilityInteractionLink({
          id: "link-hide-door",
          sourceEntityId: triggerVolume.id,
          trigger: "exit",
          targetBrushId: brush.id,
          visible: false
        }),
        "link-click-teleport": createTeleportPlayerInteractionLink({
          id: "link-click-teleport",
          sourceEntityId: interactable.id,
          trigger: "click",
          targetEntityId: teleportTarget.id
        })
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("migrates the foundation schema to the current schema version", () => {
    const migratedDocument = migrateSceneDocument({
      version: 1,
      name: "Foundation Scene",
      world: createEmptySceneDocument().world,
      materials: {},
      textures: {},
      assets: {},
      brushes: {},
      modelInstances: {},
      entities: {},
      interactionLinks: {}
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.brushes).toEqual({});
    expect(migratedDocument.name).toBe("Foundation Scene");
    expect(Object.keys(migratedDocument.materials)).toEqual(
      STARTER_MATERIAL_LIBRARY.map((material) => material.id)
    );
  });

  it("migrates NPC foundation entities to include default collider settings", () => {
    const migratedDocument = migrateSceneDocument({
      version: NPC_ENTITY_FOUNDATION_SCENE_DOCUMENT_VERSION,
      name: "NPC Collider Migration",
      time: createDefaultProjectTimeSettings(),
      world: createEmptySceneDocument().world,
      materials: createEmptySceneDocument().materials,
      textures: {},
      assets: {},
      brushes: {},
      modelInstances: {},
      entities: {
        "entity-npc-guide": {
          id: "entity-npc-guide",
          kind: "npc",
          position: {
            x: 1,
            y: 0,
            z: 2
          },
          visible: true,
          enabled: true,
          actorId: "actor-town-guide",
          yawDegrees: 30,
          modelAssetId: null
        }
      },
      interactionLinks: {}
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.entities["entity-npc-guide"]).toEqual(
      createNpcEntity({
        id: "entity-npc-guide",
        actorId: "actor-town-guide",
        position: {
          x: 1,
          y: 0,
          z: 2
        },
        yawDegrees: 30,
        modelAssetId: null
      })
    );
  });

  it("migrates pre-presence NPC entities to always-authored presence", () => {
    const migratedDocument = migrateSceneDocument({
      version: PATH_FOUNDATION_SCENE_DOCUMENT_VERSION,
      name: "NPC Presence Migration",
      time: createDefaultProjectTimeSettings(),
      world: createEmptySceneDocument().world,
      materials: createEmptySceneDocument().materials,
      textures: {},
      assets: {},
      brushes: {},
      paths: {},
      modelInstances: {},
      entities: {
        "entity-npc-guide": {
          id: "entity-npc-guide",
          kind: "npc",
          position: {
            x: 1,
            y: 0,
            z: 2
          },
          visible: true,
          enabled: true,
          actorId: "actor-town-guide",
          yawDegrees: 30,
          modelAssetId: null,
          collider: createNpcEntity().collider
        }
      },
      interactionLinks: {}
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.entities["entity-npc-guide"]).toEqual(
      createNpcEntity({
        id: "entity-npc-guide",
        actorId: "actor-town-guide",
        presence: createNpcAlwaysPresence(),
        position: {
          x: 1,
          y: 0,
          z: 2
        },
        yawDegrees: 30,
        modelAssetId: null
      })
    );
  });

  it("migrates slice 3.0 documents to the current schema version without changing empty asset collections", () => {
    const migratedDocument = migrateSceneDocument({
      version: MODEL_ASSET_PIPELINE_SCENE_DOCUMENT_VERSION,
      name: "Imported Asset Scene",
      world: createEmptySceneDocument().world,
      materials: createEmptySceneDocument().materials,
      textures: {},
      assets: {},
      brushes: {},
      modelInstances: {},
      entities: {},
      interactionLinks: {}
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.assets).toEqual({});
    expect(migratedDocument.modelInstances).toEqual({});
  });

  it("migrates v40 scene documents to the current schema version without changing authored entities", () => {
    const interactable = createInteractableEntity({
      id: "entity-interactable-v40",
      prompt: "Inspect"
    });

    const migratedDocument = migrateSceneDocument({
      ...createEmptySceneDocument({ name: "v40 Compatibility Scene" }),
      version: WORLD_TIME_ENVIRONMENT_SCENE_DOCUMENT_VERSION,
      entities: {
        [interactable.id]: interactable
      }
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.entities[interactable.id]).toEqual(interactable);
  });

  it("migrates slice 1.1 box brushes to explicit per-face UV state", () => {
    const migratedDocument = migrateSceneDocument({
      version: 2,
      name: "Legacy Brush Scene",
      world: createEmptySceneDocument().world,
      materials: {},
      textures: {},
      assets: {},
      brushes: {
        "brush-legacy": {
          id: "brush-legacy",
          kind: "box",
          center: {
            x: 0,
            y: 1,
            z: 0
          },
          size: {
            x: 4,
            y: 2,
            z: 6
          },
          faces: {
            posX: { materialId: null },
            negX: { materialId: null },
            posY: { materialId: null },
            negY: { materialId: null },
            posZ: { materialId: "starter-amber-grid" },
            negZ: { materialId: null }
          }
        }
      },
      modelInstances: {},
      entities: {},
      interactionLinks: {}
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.brushes["brush-legacy"].faces.posZ.materialId).toBe(
      "starter-amber-grid"
    );
    expect(migratedDocument.brushes["brush-legacy"].faces.posZ.uv).toEqual({
      offset: {
        x: 0,
        y: 0
      },
      scale: {
        x: 1,
        y: 1
      },
      rotationQuarterTurns: 0,
      flipU: false,
      flipV: false
    });
  });

  it("migrates legacy starter material entries to the asset-backed PBR registry", () => {
    const legacyDocument = createEmptySceneDocument({
      name: "Legacy Material Registry Scene"
    });
    const legacyBrush = createBoxBrush({
      id: "brush-legacy-materials",
      size: {
        x: 4,
        y: 2,
        z: 4
      }
    });

    legacyBrush.faces.posZ.materialId = "starter-amber-grid";
    legacyDocument.brushes[legacyBrush.id] = legacyBrush;

    const migratedDocument = migrateSceneDocument({
      ...legacyDocument,
      version: SCENE_TRANSITION_SEQUENCE_EFFECTS_SCENE_DOCUMENT_VERSION,
      materials: {
        ...legacyDocument.materials,
        "starter-amber-grid": {
          id: "starter-amber-grid",
          name: "Amber Grid",
          pattern: "grid",
          baseColorHex: "#d97706",
          accentColorHex: "#fbbf24",
          tags: ["warm", "grid"]
        }
      }
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(
      migratedDocument.brushes["brush-legacy-materials"].faces.posZ.materialId
    ).toBe("starter-amber-grid");
    expect(migratedDocument.materials["starter-amber-grid"]).toEqual(
      expect.objectContaining({
        id: "starter-amber-grid",
        name: "Stacked Beige Terracotta Tile",
        assetFolder: "stacked_beige_terracotta_tile_250x250",
        previewImageName: "preview.webp",
        sizeCm: {
          width: 250,
          height: 250
        }
      })
    );
  });

  it("migrates slice 1.2 face materials to the PlayerStart-capable schema", () => {
    const migratedDocument = migrateSceneDocument({
      version: 3,
      name: "Legacy Face Scene",
      world: createEmptySceneDocument().world,
      materials: createEmptySceneDocument().materials,
      textures: {},
      assets: {},
      brushes: {},
      modelInstances: {},
      entities: {},
      interactionLinks: {}
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.entities).toEqual({});
  });

  it("migrates runner-v1 documents to authored brush names without changing existing content", () => {
    const migratedDocument = migrateSceneDocument({
      version: 4,
      name: "Runner V1 Scene",
      world: createEmptySceneDocument().world,
      materials: createEmptySceneDocument().materials,
      textures: {},
      assets: {},
      brushes: {
        "brush-room-shell": {
          id: "brush-room-shell",
          kind: "box",
          center: {
            x: 0,
            y: 1,
            z: 0
          },
          size: {
            x: 4,
            y: 2,
            z: 6
          },
          faces: {
            posX: { materialId: null, uv: createBoxBrush().faces.posX.uv },
            negX: { materialId: null, uv: createBoxBrush().faces.negX.uv },
            posY: { materialId: null, uv: createBoxBrush().faces.posY.uv },
            negY: { materialId: null, uv: createBoxBrush().faces.negY.uv },
            posZ: { materialId: null, uv: createBoxBrush().faces.posZ.uv },
            negZ: { materialId: null, uv: createBoxBrush().faces.negZ.uv }
          }
        }
      },
      modelInstances: {},
      entities: {
        "entity-player-start-main": {
          id: "entity-player-start-main",
          kind: "playerStart",
          position: {
            x: 2,
            y: 0,
            z: -2
          },
          yawDegrees: 45
        }
      },
      interactionLinks: {}
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.brushes["brush-room-shell"].name).toBeUndefined();
    expect(migratedDocument.entities["entity-player-start-main"]).toMatchObject(
      {
        kind: "playerStart",
        yawDegrees: 45
      }
    );
  });

  it("migrates slice 1.4 documents to the world-environment schema without changing authored solid backgrounds", () => {
    const migratedDocument = migrateSceneDocument({
      version: FIRST_ROOM_POLISH_SCENE_DOCUMENT_VERSION,
      name: "First Room Scene",
      world: createEmptySceneDocument().world,
      materials: createEmptySceneDocument().materials,
      textures: {},
      assets: {},
      brushes: {},
      modelInstances: {},
      entities: {},
      interactionLinks: {}
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.world.background).toEqual({
      mode: "solid",
      colorHex: "#2f3947"
    });
  });

  it("migrates slice 3.2 documents with local lights and skyboxes to the current schema version", () => {
    const imageAsset = {
      id: "asset-background-panorama",
      kind: "image",
      sourceName: "skybox-panorama.svg",
      mimeType: "image/svg+xml",
      storageKey: createProjectAssetStorageKey("asset-background-panorama"),
      byteLength: 2048,
      metadata: {
        kind: "image" as const,
        width: 512,
        height: 256,
        hasAlpha: false,
        warnings: []
      }
    } satisfies ImageAssetRecord;
    const pointLight = createPointLightEntity({
      id: "entity-point-light-main"
    });
    const spotLight = createSpotLightEntity({
      id: "entity-spot-light-main"
    });

    const migratedDocument = migrateSceneDocument({
      version: LOCAL_LIGHTS_AND_SKYBOX_SCENE_DOCUMENT_VERSION,
      name: "Local Light Scene",
      world: {
        ...createEmptySceneDocument().world,
        background: {
          mode: "image",
          assetId: imageAsset.id
        }
      },
      materials: createEmptySceneDocument().materials,
      textures: {},
      assets: {
        [imageAsset.id]: imageAsset
      },
      brushes: {},
      modelInstances: {},
      entities: {
        [pointLight.id]: pointLight,
        [spotLight.id]: spotLight
      },
      interactionLinks: {}
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.world.background).toEqual({
      mode: "image",
      assetId: imageAsset.id,
      environmentIntensity: 0.5
    });
    expect(migratedDocument.entities[pointLight.id]).toEqual(pointLight);
    expect(migratedDocument.entities[spotLight.id]).toEqual(spotLight);
    expect(migratedDocument.assets[imageAsset.id]).toEqual(imageAsset);
  });

  it("migrates slice 1.5 documents to the typed-entity schema without changing supported authored entities", () => {
    const migratedDocument = migrateSceneDocument({
      version: WORLD_ENVIRONMENT_SCENE_DOCUMENT_VERSION,
      name: "World Environment Scene",
      world: createEmptySceneDocument().world,
      materials: createEmptySceneDocument().materials,
      textures: {},
      assets: {},
      brushes: {},
      modelInstances: {},
      entities: {
        "entity-player-start-main": {
          id: "entity-player-start-main",
          kind: "playerStart",
          position: {
            x: 2,
            y: 0,
            z: -1
          },
          yawDegrees: 90
        }
      },
      interactionLinks: {}
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.entities["entity-player-start-main"]).toMatchObject(
      {
        kind: "playerStart",
        yawDegrees: 90
      }
    );
  });

  it("migrates slice 2.1 documents to the interaction-link schema with empty interaction links", () => {
    const migratedDocument = migrateSceneDocument({
      version: ENTITY_SYSTEM_FOUNDATION_SCENE_DOCUMENT_VERSION,
      name: "Entity Foundation Scene",
      world: createEmptySceneDocument().world,
      materials: createEmptySceneDocument().materials,
      textures: {},
      assets: {},
      brushes: {},
      modelInstances: {},
      entities: {
        "entity-trigger-main": {
          id: "entity-trigger-main",
          kind: "triggerVolume",
          position: {
            x: 0,
            y: 0,
            z: 0
          },
          size: {
            x: 2,
            y: 2,
            z: 2
          },
          triggerOnEnter: true,
          triggerOnExit: false
        }
      },
      interactionLinks: {}
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.interactionLinks).toEqual({});
    expect(migratedDocument.entities["entity-trigger-main"]).toMatchObject({
      kind: "triggerVolume"
    });
  });

  it("migrates slice 2.2 documents to the click-capable interaction schema without changing existing links", () => {
    const migratedDocument = migrateSceneDocument({
      version: TRIGGER_ACTION_TARGET_FOUNDATION_SCENE_DOCUMENT_VERSION,
      name: "Trigger Action Target Scene",
      world: createEmptySceneDocument().world,
      materials: createEmptySceneDocument().materials,
      textures: {},
      assets: {},
      brushes: {},
      modelInstances: {},
      entities: {
        "entity-trigger-main": {
          id: "entity-trigger-main",
          kind: "triggerVolume",
          position: {
            x: 0,
            y: 0,
            z: 0
          },
          size: {
            x: 2,
            y: 2,
            z: 2
          },
          triggerOnEnter: true,
          triggerOnExit: false
        },
        "entity-teleport-main": {
          id: "entity-teleport-main",
          kind: "teleportTarget",
          position: {
            x: 4,
            y: 0,
            z: -2
          },
          yawDegrees: 90
        }
      },
      interactionLinks: {
        "link-teleport": {
          id: "link-teleport",
          sourceEntityId: "entity-trigger-main",
          trigger: "enter",
          action: {
            type: "teleportPlayer",
            targetEntityId: "entity-teleport-main"
          }
        }
      }
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.interactionLinks).toEqual({
      "link-teleport": {
        id: "link-teleport",
        sourceEntityId: "entity-trigger-main",
        trigger: "enter",
        action: {
          type: "teleportPlayer",
          targetEntityId: "entity-teleport-main"
        }
      }
    });
  });

  it("migrates v11 documents to v12 with animation fields defaulted to undefined on model instances", () => {
    const asset = {
      id: "asset-model-anim",
      kind: "model",
      sourceName: "animated.glb",
      mimeType: "model/gltf-binary",
      storageKey: createProjectAssetStorageKey("asset-model-anim"),
      byteLength: 1024,
      metadata: {
        kind: "model" as const,
        format: "glb" as const,
        sceneName: null,
        nodeCount: 1,
        meshCount: 1,
        materialNames: [],
        textureNames: [],
        animationNames: ["Walk"],
        boundingBox: null,
        warnings: []
      }
    } satisfies ModelAssetRecord;

    const migratedDocument = migrateSceneDocument({
      version: 11,
      name: "V11 Scene",
      world: createEmptySceneDocument().world,
      materials: createEmptySceneDocument().materials,
      textures: {},
      assets: { [asset.id]: asset },
      brushes: {},
      modelInstances: {
        "mi-1": {
          id: "mi-1",
          kind: "modelInstance",
          assetId: asset.id,
          position: { x: 0, y: 0, z: 0 },
          rotationDegrees: { x: 0, y: 0, z: 0 },
          scale: { x: 1, y: 1, z: 1 }
        }
      },
      entities: {},
      interactionLinks: {}
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(
      migratedDocument.modelInstances["mi-1"].animationClipName
    ).toBeUndefined();
    expect(
      migratedDocument.modelInstances["mi-1"].animationAutoplay
    ).toBeUndefined();
  });

  it("migrates v12 sound emitters to the current schema version", () => {
    const migratedDocument = migrateSceneDocument({
      version: ANIMATION_PLAYBACK_SCENE_DOCUMENT_VERSION,
      name: "Legacy Sound Scene",
      world: createEmptySceneDocument().world,
      materials: createEmptySceneDocument().materials,
      textures: {},
      assets: {},
      brushes: {},
      modelInstances: {},
      entities: {
        "entity-sound-main": {
          id: "entity-sound-main",
          kind: "soundEmitter",
          position: {
            x: 1,
            y: 2,
            z: 3
          },
          radius: 9,
          gain: 0.4,
          autoplay: true,
          loop: false
        }
      },
      interactionLinks: {}
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.entities["entity-sound-main"]).toEqual({
      id: "entity-sound-main",
      kind: "soundEmitter",
      name: undefined,
      position: {
        x: 1,
        y: 2,
        z: 3
      },
      visible: true,
      enabled: true,
      audioAssetId: null,
      volume: 0.4,
      refDistance: 9,
      maxDistance: 9,
      autoplay: true,
      loop: false
    });
  });

  it("migrates v13 documents without the advanced rendering block to the current schema version", () => {
    const emptyScene = createEmptySceneDocument();
    const { advancedRendering: _advancedRendering, ...legacyWorld } =
      emptyScene.world;

    const migratedDocument = migrateSceneDocument({
      version: SPATIAL_AUDIO_SCENE_DOCUMENT_VERSION,
      name: "Legacy Spatial Audio Scene",
      world: legacyWorld,
      materials: emptyScene.materials,
      textures: {},
      assets: {},
      brushes: {},
      modelInstances: {},
      entities: {},
      interactionLinks: {}
    });

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.world.advancedRendering).toEqual(
      emptyScene.world.advancedRendering
    );
  });

  it("migrates v19 whitebox boxes without volume settings to the current schema version", () => {
    const legacyBrush = createBoxBrush({
      id: "brush-legacy"
    });
    const migratedDocument = migrateSceneDocument({
      version: WHITEBOX_GEOMETRY_SCENE_DOCUMENT_VERSION,
      name: "Legacy Whitebox Volume Scene",
      world: {
        ...createEmptySceneDocument().world,
        advancedRendering: {
          ...createEmptySceneDocument().world.advancedRendering,
          fogPath: undefined,
          waterPath: undefined
        }
      },
      materials: createEmptySceneDocument().materials,
      textures: {},
      assets: {},
      brushes: {
        "brush-legacy": {
          id: "brush-legacy",
          kind: "box",
          center: legacyBrush.center,
          rotationDegrees: legacyBrush.rotationDegrees,
          size: legacyBrush.size,
          geometry: legacyBrush.geometry,
          faces: legacyBrush.faces
        }
      },
      modelInstances: {},
      entities: {},
      interactionLinks: {}
    } as any);

    expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
    expect(migratedDocument.brushes["brush-legacy"].volume).toEqual({
      mode: "none"
    });
    expect(migratedDocument.world.advancedRendering.fogPath).toBe(
      "performance"
    );
    expect(migratedDocument.world.advancedRendering.waterPath).toBe(
      "performance"
    );
  });

  it("round-trips authored playAnimation and stopAnimation interaction links", () => {
    const asset = {
      id: "asset-model-anim",
      kind: "model",
      sourceName: "animated.glb",
      mimeType: "model/gltf-binary",
      storageKey: createProjectAssetStorageKey("asset-model-anim"),
      byteLength: 1024,
      metadata: {
        kind: "model" as const,
        format: "glb" as const,
        sceneName: null,
        nodeCount: 1,
        meshCount: 1,
        materialNames: [],
        textureNames: [],
        animationNames: ["Walk", "Run"],
        boundingBox: null,
        warnings: []
      }
    } satisfies ModelAssetRecord;
    const modelInstance = createModelInstance({
      id: "mi-animated",
      assetId: asset.id,
      animationClipName: "Walk",
      animationAutoplay: true
    });
    const triggerVolume = createTriggerVolumeEntity({
      id: "entity-trigger-main"
    });
    const playLink = createPlayAnimationInteractionLink({
      id: "link-play",
      sourceEntityId: triggerVolume.id,
      trigger: "enter",
      targetModelInstanceId: modelInstance.id,
      clipName: "Walk"
    });
    const stopLink = createStopAnimationInteractionLink({
      id: "link-stop",
      sourceEntityId: triggerVolume.id,
      trigger: "exit",
      targetModelInstanceId: modelInstance.id
    });
    const document = {
      ...createEmptySceneDocument({ name: "Animation Scene" }),
      assets: { [asset.id]: asset },
      modelInstances: { [modelInstance.id]: modelInstance },
      entities: { [triggerVolume.id]: triggerVolume },
      interactionLinks: {
        [playLink.id]: playLink,
        [stopLink.id]: stopLink
      }
    };

    expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
      document
    );
  });

  it("rejects a v12 document where a playAnimation action has an empty clipName", () => {
    const asset = {
      id: "asset-model-anim",
      kind: "model",
      sourceName: "animated.glb",
      mimeType: "model/gltf-binary",
      storageKey: createProjectAssetStorageKey("asset-model-anim"),
      byteLength: 1024,
      metadata: {
        kind: "model" as const,
        format: "glb" as const,
        sceneName: null,
        nodeCount: 1,
        meshCount: 1,
        materialNames: [],
        textureNames: [],
        animationNames: [],
        boundingBox: null,
        warnings: []
      }
    };

    expect(() =>
      migrateSceneDocument({
        version: SCENE_DOCUMENT_VERSION,
        name: "Bad Animation Scene",
        time: createDefaultProjectTimeSettings(),
        scheduler: createEmptySceneDocument().scheduler,
        world: createEmptySceneDocument().world,
        materials: createEmptySceneDocument().materials,
        textures: {},
        assets: { [asset.id]: asset },
        brushes: {},
        modelInstances: {},
        entities: {
          "entity-trigger-main": {
            id: "entity-trigger-main",
            kind: "triggerVolume",
            position: { x: 0, y: 0, z: 0 },
            size: { x: 2, y: 2, z: 2 },
            triggerOnEnter: true,
            triggerOnExit: false
          }
        },
        interactionLinks: {
          "link-bad-play": {
            id: "link-bad-play",
            sourceEntityId: "entity-trigger-main",
            trigger: "enter",
            action: {
              type: "playAnimation",
              targetModelInstanceId: "mi-animated",
              clipName: ""
            }
          }
        }
      })
    ).toThrow();
  });

  it("rejects unsupported versions", () => {
    expect(() =>
      migrateSceneDocument({
        version: 99,
        name: "Legacy",
        world: {},
        textures: {},
        assets: {},
        brushes: {},
        modelInstances: {},
        entities: {},
        interactionLinks: {}
      })
    ).toThrow("Unsupported scene document version");
  });

  it("rejects duplicate authored ids after migration and validation", () => {
    expect(() =>
      parseSceneDocumentJson(
        JSON.stringify({
          version: SCENE_DOCUMENT_VERSION,
          name: "Duplicate Id Scene",
          scheduler: createEmptySceneDocument().scheduler,
          world: createEmptySceneDocument().world,
          materials: createEmptySceneDocument().materials,
          textures: {},
          assets: {},
          brushes: {
            "brush-room-shell": {
              id: "shared-id",
              kind: "box",
              center: {
                x: 0,
                y: 1,
                z: 0
              },
              size: {
                x: 2,
                y: 2,
                z: 2
              },
              faces: {
                posX: { materialId: null, uv: createBoxBrush().faces.posX.uv },
                negX: { materialId: null, uv: createBoxBrush().faces.negX.uv },
                posY: { materialId: null, uv: createBoxBrush().faces.posY.uv },
                negY: { materialId: null, uv: createBoxBrush().faces.negY.uv },
                posZ: { materialId: null, uv: createBoxBrush().faces.posZ.uv },
                negZ: { materialId: null, uv: createBoxBrush().faces.negZ.uv }
              }
            }
          },
          time: createDefaultProjectTimeSettings(),
          modelInstances: {},
          entities: {
            "shared-id": {
              id: "shared-id",
              kind: "playerStart",
              position: {
                x: 0,
                y: 0,
                z: 0
              },
              yawDegrees: 0
            }
          },
          interactionLinks: {}
        })
      )
    ).toThrow("Duplicate authored id shared-id");
  });
});
