/** * `bitmagic levels` — the level registry in `worldProfileData.levels`, without the web Creator. * * `forge` can CREATE levels but never could manage them: only a game's first forge becomes the * start level (or `forge --make-start` at creation), so fixing a wrong start, renaming, or deleting * a level meant opening the Creator. * * ── The invariant this file exists to respect ──────────────────────────────────────────────── * * `worldProfileData` keeps three LEGACY MIRRORS — `voxelUrl`, `spawnPoints` and * `playerSpawnPosition` — describing the START level, because frozen genre and work-template code * boots from those rather than from the registry (see `WorldLevel` in game/src/types/game.ts). * * Nothing self-heals on the normal boot path. `applyBootLevelOverride` does recompute them, but * only when the main screen's level chooser passes a `bootLevelId`. So a `startLevelId` moved * without its mirrors boots the NEW level's objects, lighting and navmesh over the OLD level's * terrain, and nothing throws. That is the failure this module exists to prevent. * * The mirrors come from `buildStartLevelMirrors` in `@bitmagic/world-forger`, deliberately rather * than reimplemented — the same function the forge uses, carrying a subtlety worth not * rediscovering: spawn mirrors are written ONLY when the level owns spawn points, because a level * without its own inherits the globals, which are then already correct. * * Pure: it computes modifications and never applies them. */ import { type LevelEntry, type WorldJsonModification } from '@bitmagic/world-forger/pipeline/index.js'; import { type JsonObject } from '../project/world-json.js'; export interface LevelRow { id: string; name: string; vwldAssetId: string; /** The `.vwld` asset is absent from `assets[]`, so this level has nothing to load. */ missingAsset: boolean; start: boolean; spawnPoints: number; /** Placed objects tagged to this level. Untagged ones are global and belong to none. */ placed: number; } export declare function listLevels(world: JsonObject): LevelRow[]; export interface LevelPlan { /** * The level the plan is about, as the registry holds it BEFORE the write — so a rename's plan * still carries the old name. For `add` it is the entry the plan will create. */ level: LevelEntry; modifications: WorldJsonModification[]; /** Said after the write. Never a reason to refuse. */ notes: string[]; } /** * Make `levelId` the level the game boots into — registry AND mirrors, together. * * Refused when the level's `.vwld` asset is missing or has no url: the mirrors are what actually * boot the game, so writing `startLevelId` alone would leave `voxelUrl` describing the previous * level, and the registry and the thing that loads would disagree with no error anywhere. */ export declare function planSetStart(world: JsonObject, levelId: string): LevelPlan; export declare function planRename(world: JsonObject, levelId: string, name: string): LevelPlan; export interface AddPlan extends LevelPlan { /** True when this made the game multi-level for the first time. */ converted: boolean; } /** * Register an existing `.vwld` asset as a level. * * Start-level policy matches `forge`'s: a game's FIRST level becomes the start, later ones do not, * and `--make-start` overrides. Stealing the start from a working game because someone registered * a second level would be the surprising outcome. */ export declare function planAdd(world: JsonObject, vwldAssetId: string, options: { name?: string; makeStart: boolean; }): AddPlan; export interface RemovePlan extends LevelPlan { /** Placed instances tagged to this level, which go with it. */ placedRemoved: number; /** Doors and key items scoped to it, which also go. */ scopedRemoved: string[]; } /** * Remove a level from the registry. * * Two refusals, matching the agent's `manage-levels delete` so the two lanes cannot disagree: * the last level, and the START level. The second matters most — `startLevelId` naming nothing * falls back to `levels[0]` at boot while the mirrors still describe the deleted level, which is * the silent stale-mirror failure. Move the start first, deliberately. * * The `.vwld` asset is left alone: it may be large, nothing here deletes uploaded bytes, and a * re-add is then free. */ export declare function planRemove(world: JsonObject, levelId: string): RemovePlan;