import type { ModelAnimationConfig } from "../game/playableGame.js"; import type { InventoryState } from "../inventory/inventoryModel.js"; import type { EntityPosition } from "./entityStore.js"; export interface ObjectVisual { scale?: number | readonly [number, number, number]; color?: string; opacity?: number; } export interface SceneObject { instanceId: string; catalogId: string; position: EntityPosition; rotationY: number; parentSpace?: string; visual?: ObjectVisual; /** * Per-placement rig animation override applied on top of catalog resolution — the same shape as * `ModelConfig.animation` (role→clip states, `"auto"`/`"none"`, speeds, fade, one-shot bindings). * Authored in the editor as `marker.meta.animation` (#1274) and threaded here by * `resolveAuthoredObjects` → `placeAuthoredObjects`; `undefined` keeps the catalog-resolved default. */ animation?: ModelAnimationConfig | "auto" | "none"; /** * Opaque per-instance state for this placement — a chest's locked flag, a machine's fill level, the * owner of a placed turret. Replicated and saved with the object through the `objects` snapshot * module and round-trips as `RuntimeObjectRow.flags`, so a command may own it without the game * hand-rolling a parallel store keyed by `instanceId`. */ state?: Record; /** * Container contents when this object's catalog entry declares `slotInventory`. Mutate through * `ctx.scene.object.slots` rather than writing it directly — that path validates against the * declared `accepts` and stack limits. */ slots?: InventoryState; } export interface PlaceOptions { instanceId?: string; parentSpace?: string; rotation?: number; visual?: ObjectVisual; /** Initial {@link SceneObject.state} for this placement. */ state?: Record; /** Initial {@link SceneObject.slots} contents; omit to start from the catalog `slotInventory` layout's empty slots. */ slots?: InventoryState; /** Per-placement rig animation override stored on the placed {@link SceneObject}; same shape as `ModelConfig.animation`. Omit to keep the catalog-resolved default. */ animation?: ModelAnimationConfig | "auto" | "none"; /** When `instanceId` is already placed: `"throw"` (default), `"replace"` (fresh placement over it — remount-safe world setup, #284.10), or `"keep"` (leave it untouched and return the id). */ onExisting?: "throw" | "replace" | "keep"; } export interface ObjectListFilter { parentSpace?: string; } export interface ObjectStore { place(catalogId: string, x: number, y: number, z: number, options?: PlaceOptions): string; remove(instanceId: string): boolean; move(instanceId: string, x: number, y: number, z: number): boolean; rotate(instanceId: string, rotationY: number): boolean; setVisual(instanceId: string, visual: ObjectVisual | undefined): boolean; /** Replace {@link SceneObject.state} wholesale; `undefined` drops the field. Returns `false` when the instance is not placed. */ setState(instanceId: string, state: Record | undefined): boolean; /** Shallow-merge `patch` into {@link SceneObject.state}; a key set to `undefined` is removed. Returns `false` when the instance is not placed. */ patchState(instanceId: string, patch: Record): boolean; /** Replace {@link SceneObject.slots} wholesale; prefer `ctx.scene.object.slots` which validates against the catalog layout. */ setSlots(instanceId: string, slots: InventoryState | undefined): boolean; get(instanceId: string): SceneObject | null; list(filter?: ObjectListFilter): readonly SceneObject[]; /** Stable-identity list of all placed object ids; identity changes only on place/remove, not on move/rotate/setVisual. Pair with {@link subscribeMembership} for membership-only React subscriptions (#625). */ ids(): readonly string[]; at(x: number, y: number, z: number, tolerance?: number): SceneObject[]; inBox(min: EntityPosition, max: EntityPosition): readonly SceneObject[]; clear(): void; subscribe(listener: () => void): () => void; /** Notified only when the object set changes (place/remove), never on move/rotate/setVisual — markers read live poses imperatively. */ subscribeMembership(listener: () => void): () => void; snapshot(): readonly SceneObject[]; /** Replace the placed set with `objects` — the client half of `objects` replication; instances absent from the payload are removed. */ hydrate(objects: readonly SceneObject[]): void; } export declare function createObjectStore(): ObjectStore; export declare function objectVisualScale(visual: ObjectVisual | undefined): readonly [number, number, number];