/** * The parametric-studio seam: a registry that maps an editor object `kind` to a typed parameter * schema (drives the inspector + `meta` parse/validation), an optional pure-data `resolve` (turns a * document object into renderable data), and `+ Add` menu metadata. Registering a kind lets a third * party ship a new authorable "studio" (pole line, water, bookcase) — schema + resolver here, a * matching renderer in `shell` — without editing editor or engine files. Scatter is the proof adopter. */ import type { WorldSolid } from "../world/worldSolids.js"; /** Which document collection a scene kind lives in. Drives placement + which `meta` bag holds params. */ export type SceneKindTarget = "path" | "marker" | "volume"; /** A closed-polygon area vs an open polyline, for path-target kinds. Ignored for marker/volume kinds. */ export type ScenePathShape = "area" | "line"; /** One weighted entry in a `weightedList` param — an item id and its relative spawn weight. */ export interface WeightedParamEntry { item: string; weight: number; } /** A numeric slider row: bounded range with a live readout. */ export interface RangeParamField { type: "range"; key: string; label?: string; /** Optional group id — the inspector renders fields sharing a group under one collapsible section. */ group?: string; min: number; max: number; step?: number; default: number; /** Suffix shown next to the readout, e.g. "m" or "/m²". */ unit?: string; } /** A free numeric input, optionally clamped. */ export interface NumberParamField { type: "number"; key: string; label?: string; /** Optional group id — the inspector renders fields sharing a group under one collapsible section. */ group?: string; step?: number; default: number; min?: number; max?: number; } /** A checkbox. */ export interface BoolParamField { type: "bool"; key: string; label?: string; /** Optional group id — the inspector renders fields sharing a group under one collapsible section. */ group?: string; default: boolean; } /** A dropdown of fixed string options. */ export interface SelectParamField { type: "select"; key: string; label?: string; /** Optional group id — the inspector renders fields sharing a group under one collapsible section. */ group?: string; options: readonly { value: string; label?: string; }[]; default: string; } /** A hex color picker. */ export interface ColorParamField { type: "color"; key: string; label?: string; /** Optional group id — the inspector renders fields sharing a group under one collapsible section. */ group?: string; default: string; } /** A free string input. */ export interface TextParamField { type: "text"; key: string; label?: string; /** Optional group id — the inspector renders fields sharing a group under one collapsible section. */ group?: string; default: string; } /** A seed string with a reroll button — same seed reproduces the same generated result. */ export interface SeedParamField { type: "seed"; key: string; label?: string; /** Optional group id — the inspector renders fields sharing a group under one collapsible section. */ group?: string; default: string; } /** A repeatable list of weighted `{ item, weight }` rows (species palette, book set, …). */ export interface WeightedListParamField { type: "weightedList"; key: string; label?: string; /** Optional group id — the inspector renders fields sharing a group under one collapsible section. */ group?: string; /** Placeholder for the item id input. */ itemLabel?: string; default: readonly WeightedParamEntry[]; } /** * A button row that runs a built-in inspector action on its group — currently `"randomize"` (reroll * the group's numeric/seed/bool/select fields within their bounds) or `"reset"` (restore group * defaults). Carries no persisted value; purely a control. */ export interface ActionParamField { type: "action"; key: string; label?: string; group?: string; action: "randomize" | "reset"; } /** One row in a kind's parameter schema — the union the generic inspector knows how to render. */ export type ParamField = RangeParamField | NumberParamField | BoolParamField | SelectParamField | ColorParamField | TextParamField | SeedParamField | WeightedListParamField | ActionParamField; /** A named, optionally-collapsed section the inspector groups fields under (by `field.group === id`). */ export interface ParamGroup { id: string; label: string; /** Start collapsed. Default expanded. */ collapsed?: boolean; } /** * A named slider/weight bundle for a kind — a saved set of field values the inspector (or the * `apply_preset` verb) writes into an object's `meta` in one patch, ready to tweak afterwards. * Presets are plain data on the schema, so kinds ship archetypes and games can override the * registration with their own. */ export interface ParamPreset { id: string; label?: string; /** Field values the preset writes; fields not listed keep their current values. */ values: Readonly>; } /** A kind's full parameter surface: an ordered list of fields the inspector renders top-to-bottom. */ export interface ParamSchema { fields: readonly ParamField[]; /** Optional named sections; fields reference one by `group`. Ungrouped fields render first, headerless. */ groups?: readonly ParamGroup[]; /** Optional named field-value bundles the inspector offers as one-click applies. */ presets?: readonly ParamPreset[]; } /** Look up a schema preset by id, or undefined — the seam `apply_preset` and games resolve bundles through. */ export declare function findSchemaPreset(schema: ParamSchema, id: string): ParamPreset | undefined; /** Parsed params after `parseParams`: every schema field present with a validated, defaulted value. */ export type ParsedParams = Record; /** * Marks a kind as *scatterable* — it fills an authored area with instances at one density under a * bounded budget — so the editor renders the shared `CoverageSection` (Area → Assets → Density slider * + budget note) that gives grass/scatter/city one authoring gesture. `spec` selects the unit + budget * from `@jgengine/core/world/scatterCoverage`; `densityKey` is the single density slider hoisted into * the coverage header (and hidden from the schema body below); `assetsKey` is the optional weighted * "what fills it" palette summarized in the header. */ export interface SceneKindCoverage { spec: import("../world/scatterCoverage.js").ScatterCoverageKind; densityKey: string; assetsKey?: string; } /** * Parse a raw `meta` bag against a schema into typed params — every field present, invalid/missing * values replaced by the field default, numbers clamped to their range. The single parser every * studio shares instead of hand-writing its own `metaNumber`/`metaBool` ladder. */ export declare function parseParams(schema: ParamSchema, meta: Record | undefined): ParsedParams; /** One validation problem found by {@link validateParams}: which field and why. */ export interface ParamIssue { key: string; message: string; } /** * Check a raw `meta` bag against a schema without mutating it — reports out-of-range numbers, unknown * select values, and wrong types. Empty array means valid. Used by `set_meta` to reject bad agent * patches before they land in the document. * @internal */ export declare function validateParams(schema: ParamSchema, meta: Record | undefined): ParamIssue[]; /** * A `meta` patch that randomizes every field in `groupId` within its bounds — the "randomize" button's * behavior, pure and injectable so it's deterministic in tests. Ranges/numbers land in `[min, max]` * (integer-rounded when `step >= 1`), bools/selects pick uniformly, seeds get a fresh token, colors a * random mid-tone. Text and weighted lists are left untouched. * @internal */ export declare function randomizeGroupParams(schema: ParamSchema, groupId: string | undefined, random: () => number): Record; /** A `meta` patch restoring every field in `groupId` to its schema default — the "reset" button. @internal */ export declare function resetGroupParams(schema: ParamSchema, groupId: string | undefined): Record; /** The `meta` patch that fills a fresh object of a kind with its schema defaults. @internal */ export declare function defaultParamMeta(schema: ParamSchema): Record; /** * A registered scene kind — everything the editor/engine need to author and render a studio without * bespoke code. `schema` drives the inspector + parse; `resolve` (optional) turns one document object * into pure renderable data a matching `shell` renderer consumes; `add*` fields build the `+ Add` menu. * @typeParam TResolved — the kind's renderable payload (scatter instances, pole+cable set, …). */ export interface SceneKindDefinition { kind: string; target: SceneKindTarget; /** Human label for the inspector header and `+ Add` entry. */ label: string; schema: ParamSchema; /** For path kinds: closed area (default) or open line. Ignored for markers/volumes. */ pathShape?: ScenePathShape; /** `+ Add` menu section this kind lists under (e.g. "Studios"). Omit to hide from the menu. */ addCategory?: string; /** Accent color for the inspector panel + `+ Add` entry, as a hex string. */ accent?: string; /** * Turn one document object (its geometry + parsed params) into pure renderable data. Pure — no * three.js/DOM. Omitted for kinds whose renderer reads the object directly. `object` is the raw * marker/volume/path; `params` is `parseParams(schema, object.meta)`. */ resolve?: (object: SceneKindObject, params: ParsedParams, context: SceneKindResolveContext) => TResolved; /** * World-space collision for a resolved object. Omitted, the kind is render-only. `syncAuthoredSolids` * (`world/authoredSolids`) writes these into `ctx.world.solids`, so movement, NPCs, physics * backends and nav grids all collide with what the studio draws. */ solids?: (resolved: TResolved, object: SceneKindObject, params: ParsedParams, context: SceneKindResolveContext) => readonly WorldSolid[]; /** Optional one-line readout under the inspector fields (estimate, dimensions) — owned by the studio. */ note?: (object: SceneKindObject, params: ParsedParams) => string; /** * Optional scatterable-coverage descriptor. When set, the editor leads this kind's inspector with * the shared Area / Assets / Density `CoverageSection` and surfaces the budget clamp-and-warn note, * giving every scatterable kind (grass/scatter/city) one authoring pattern. */ coverage?: SceneKindCoverage; } /** The raw document object a resolver receives — shape shared by markers, volumes, and paths. */ export interface SceneKindObject { id: string; kind: string; meta?: Record; /** Present for path targets: the polyline/polygon points (XZ used). */ points?: readonly { x: number; y: number; z: number; }[]; /** Present for marker targets: the placed position. */ position?: { x: number; y: number; z: number; }; /** Present for volume targets: the region center. */ center?: { x: number; y: number; z: number; }; /** Present for volume targets: box half-extents. */ halfExtents?: { x: number; y: number; z: number; }; /** Present for volume targets: sphere/cylinder radius. */ radius?: number; rotationY?: number; } /** Ground sampler + options a resolver may read (terrain height/normal snap). */ export interface SceneKindResolveContext { sampleHeight?: (x: number, z: number) => number; sampleNormal?: (x: number, z: number) => readonly [number, number, number]; /** Every studio object in the same document, for kinds that read siblings (a `city` reads its `cityzone` volumes). */ objects?: readonly SceneKindObject[]; } /** * Register a scene kind — the plug-in point for a new parametric studio. Idempotent per `kind` (last * registration wins), so a game's registration overrides a default. Call at module load; the editor * inspector, `+ Add` menu, and `AuthoredScene` renderer lookup all read this registry. */ export declare function registerSceneKind(definition: SceneKindDefinition): void; /** * Declare a game's own **click-to-place** marker kind in one call — the light path for logical * markers a game reads off the document by `kind` (a stash, checkpoint, spawn point, bounty…), with * none of a full studio's schema/resolver boilerplate. Registering it makes the kind a click-to-place * tool in the editor's `+ Add` menu (grouped under `category`) and gives its markers an inspector for * any `fields` you pass. Call at module load from a module the editor loads (a game's `editorLayers` * import graph). Rendering is unaffected — the game still reads `kind` off `editorLayers.markers`. */ export declare function definePlaceableMarkerKind(options: { kind: string; label: string; /** `+ Add` menu section header this kind groups under. Defaults to `"Custom"`. */ category?: string; /** Accent hex for the menu entry + inspector header. */ accent?: string; /** Optional inspector fields written into each marker's `meta` (e.g. a payout value). */ fields?: readonly ParamField[]; }): string; /** The registered definition for a kind, or undefined if the kind is a plain (non-studio) kind. @internal */ export declare function getSceneKind(kind: string): SceneKindDefinition | undefined; /** True when a kind has a registered studio definition (schema/inspector/renderer). @internal */ export declare function isSceneKind(kind: string): boolean; /** Every registered kind, optionally filtered by target, in registration order. @internal */ export declare function listSceneKinds(target?: SceneKindTarget): SceneKindDefinition[]; /** Parse a document object's `meta` against its registered kind schema, or `null` if unregistered. @internal */ export declare function parseSceneKindParams(object: SceneKindObject): ParsedParams | null;