import type { AnimGraph } from "../anim/animGraph.js"; import type { AudioBusDef, SoundDef } from "../audio/audioFalloff.js"; import type { MusicTheme } from "../audio/music.js"; import type { UiCueOverrides } from "../audio/uiCues.js"; import type { FeedbackCueOverrides } from "../audio/feedbackCues.js"; import type { NoticeFeedConfig } from "./notices.js"; import type { MaterialAsset, MaterialAssignment, MaterialSurfaceParameters, MaterialTextureRole } from "../material/materialAsset.js"; import type { EditorCatalogDefinition, EditorDocument } from "../editor/types.js"; import type { PostProcessingConfig } from "../render/postProcessing.js"; import type { EnvironmentSource } from "../render/environment.js"; import type { SpriteAtlas } from "../assets/spriteAtlas.js"; import type { LookPreset } from "../render/lookPreset.js"; import type { GamepadFeelConfig } from "../input/gamepadModel.js"; import type { LocalPlayersConfig } from "../runtime/localPlayers.js"; import type { TouchControlsConfig } from "../input/touchScheme.js"; import type { GameSettingsConfig } from "../settings/settingsModel.js"; import type { GraphicsProfile } from "../settings/graphicsProfile.js"; import type { GameOrientation } from "../ui/orientation.js"; import type { HudPlatform, HudViewportConfig } from "../ui/hudScale.js"; import type { PositionedPrompt } from "../interaction/proximityPrompt.js"; import type { ChaseCameraTuning } from "../runtime/cameraDirector.js"; import type { CatalogEntityRole, GameContext, GameContextContent } from "../runtime/gameContext.js"; import type { FreeFlightBindings } from "../movement/freeFlight.js"; import type { SceneEntity } from "../scene/entityStore.js"; import type { ModelDims } from "../scene/assetCatalog.js"; import type { PartMotionParams, PartRole } from "./partAnimation.js"; import type { CollisionMeshData } from "../scene/collisionMesh.js"; import type { SkyEnvironmentConfig } from "../world/features.js"; import type { VisibilityConfig } from "../visibility/config.js"; import type { GameDefinition, GameLoop } from "./defineGame.js"; import type { GameCameraConfig } from "./cameraConfig.js"; import type { ViewportsConfig } from "./viewports.js"; import type { LootFilterRule } from "./lootFilter.js"; import type { RarityStyle } from "./worldItem.js"; /** How the shell presents `ctx.game.notices`; every field is optional. */ export interface NoticePresentationConfig extends NoticeFeedConfig { /** Screen band for the feed. "top" sits below top-edge HUD bars, "center" over the play area, "bottom" above action bars. Default "top". */ placement?: "top" | "center" | "bottom"; /** Distance from the band's screen edge (px or a CSS length), added to the device safe-area inset. Default `clamp(64px, 14%, 150px)` for top, `clamp(120px, 22%, 230px)` for bottom, 34% for center. */ offset?: number | string; /** Move the top/bottom stack clear of HUD panels and touch controls registered in its column. Default true. */ avoidHud?: boolean; /** Stacking order. Default 85, above `ModalHost` (80) and `PanelHost` (40), so notices stay readable over pause screens. */ zIndex?: number; /** Keep the feed and cues but render nothing (the game draws `ctx.game.notices` itself). Default false. */ hidden?: boolean; /** Show refused commands' `message` as deny notices. Default true. */ refusals?: boolean; /** Play a UI cue per new notice (deny → deny, success → confirm, else notify); a push with `silent: true` skips its own. Independent of the HUD control sounds (`audio.hudCues`). Default true. */ cues?: boolean; } export type { CameraFollowState, CameraKeyframe, CameraProjection, CameraRigKind, CameraWeaponView, CameraShakeConfig, ChaseCameraConfig, ChaseView, CinematicCameraConfig, FirstPersonCameraConfig, FirstPersonCollisionConfig, GameCameraConfig, InspectionCameraConfig, InspectionZoomAnchor, LockOnCameraConfig, ObserverCameraConfig, PlayerFovConfig, RtsCameraConfig, ShoulderCameraConfig, SideScrollCameraConfig, TopDownCameraConfig, TurntableCameraConfig, } from "./cameraConfig.js"; export { CAMERA_FRUSTUM_DEFAULTS } from "./cameraConfig.js"; /** * Mouse/touch pointer bindings for `defineGame({ pointer })`. * * @capability pointer-commands click an entity or the ground to run a command (click-to-target, click-to-move, grab loot, marquee select) */ export interface PointerConfig { /** * Left-click runs this command with `{ point, entity, object }`; `entity` is the clicked entity id or * null, so the same command covers click-to-move, ground-target and click-to-target * (`ctx.scene.entity.setTarget`). Suppresses the default left-click hotbar fire. */ moveCommand?: string; /** Enable left-drag marquee + single-click box-select of entities (RTS unit command). */ select?: boolean; /** Only entities matching this pass are selectable/orderable; default all non-local entities. */ selectFilter?: (entityId: string) => boolean; /** Right-click on ground runs this command with `{ selection, point }` — order the selection. */ orderCommand?: string; /** Right-click on an entity/object opens the target's catalog verb menu (#31). */ contextMenu?: boolean; /** Route the primary ability's aim to the cursor world point instead of camera yaw/pitch (#22). */ aim?: boolean; /** Left-click a `worldItem` within pickup radius grants it to the local player and despawns it (#32). */ grabWorldItems?: boolean; /** Press the bound `ping` action → `worldHit()` → run this command with `{ point, entity, object, normal }` (contextual ping, #94). */ pingCommand?: string; /** Right-click runs this command with `{ point, entity, object, aim }` when neither `orderCommand` nor `contextMenu` claims the click (#164.4). */ secondaryCommand?: string; } /** Props handed to a `WorldOverlay` component (#542): explicit `ctx` access so canvas-layer VFX read live engine state directly, without an extra hook or a module-global workaround. */ export interface WorldOverlayProps { ctx: GameContext; } export interface EntitySpriteConfig { url: string; width: number; height: number; y: number; /** Optional atlas-backed animation. The atlas image supersedes `url` when present. */ clip?: { atlas: SpriteAtlas; animation: string; }; } export interface WorldItemRenderConfig { /** Baseline rarity→color/beam/label render binding (#32); the game's rarity palette. */ rarityStyle?: Record; /** Loot-filter rules layered over the rarity baseline (#33) — data the game supplies, first match wins. */ filter?: readonly LootFilterRule[]; /** World units within which a ground item is grabbable/highlighted. Default 2. */ pickupRadius?: number; /** Beam height above the item's ground position. Default 2.5. */ beamHeight?: number; /** Walk-over collection: the shell grants the nearest dropped item within this radius of the local player each frame (Minecraft-style pickup). `true` uses `pickupRadius`. Omitted/false leaves pickup to `pointer.grabWorldItems` clicks. */ autoPickup?: boolean | { radius?: number; }; } /** * Player-vs-world collision for the first-person controller. Without this the * shell keeps the player on flat ground at y=0. With `voxel: true` the shell * resolves the player as a box against placed scene objects (each treated as a * solid unit cell), so they stand on blocks, fall into holes, and are stopped by * walls — the controller a block-building/mining game needs. */ export interface VoxelCollisionConfig { voxel: true; /** Half the player box width on each horizontal axis. Default 0.3. */ halfWidth?: number; /** Player box height from the feet. Default 1.8. */ height?: number; /** Tallest ledge walked up without jumping. Default 0.6. */ stepHeight?: number; } /** Movement-state clip set for `ModelAnimationConfig.states`: the shell reads the entity's live speed each frame and crossfades between these clips, so a walking mob animates without any game-side driver. */ export interface ModelAnimationStates { /** Clip name while the entity is stationary. */ idle: string; /** Clip name while the entity is moving. */ walk: string; /** Clip name above `runSpeed`; omit to keep `walk` at any speed. */ run?: string; /** Clip while moving to the character's left; with `strafeRight` adds the lateral strafe blend, driven by the `direction` parameter. */ strafeLeft?: string; /** Clip while moving to the character's right; with `strafeLeft` adds the lateral strafe blend. */ strafeRight?: string; /** Clip while moving backwards; adds the backward blend, driven by the `facing` parameter. */ backward?: string; /** Clip while turning on the spot; omit to pivot on `walk`, driven by the `turnRate` parameter. */ turn?: string; /** Play every locomotion state as root motion: the rig holds in place and the blended travel comes out as `rootDelta` for the movement owner to consume. */ rootMotion?: boolean; /** Speed (world units/sec) above which the entity counts as moving. Default 0.5. */ walkSpeed?: number; /** Speed above which `run` plays when provided. Default 6. */ runSpeed?: number; /** Crossfade duration in seconds when the state changes. Default 0.2. */ fadeSec?: number; } /** Rig playback for a `ModelConfig`'s GLTF animation clips — looping idles, one-shots, and held poses. */ export interface ModelAnimationConfig { /** Infer defaults from the loaded rig's actual clip roles, then replace fields with explicit overrides. Requires an identifiable idle clip. */ auto?: true; /** Playback clock: real seconds (default), or entity-bound locally accepted clock progress after speed/timescale and before calendar scale. Requires an advancing accepted-progress clock; unbound previews use real seconds. */ clock?: "real" | "game"; /** Clip name to play; defaults to the GLB's first clip. */ clip?: string; /** Loop the clip. Default true. */ loop?: boolean; /** Playback rate multiplier. Default 1. */ timeScale?: number; /** Hold the rig on a fixed frame instead of advancing it each tick. */ paused?: boolean; /** Seek the clip to this time in seconds; combine with `paused: true` to hold a specific pose ("pose library" usage). */ time?: number; /** Speed-driven idle/walk/run clip switching, crossfaded by the shell from the entity's live movement; overrides `clip` while set. */ states?: ModelAnimationStates; /** * One-shot clips keyed by event name, each played once over the locomotion state then released back to it. * Reserved keys `hit` and `death` auto-fire on this entity's `combat.hitReaction` / `entity.died`; any other * key fires when the game emits `entity.animation` (`ctx.game.playEntityAnimation(instanceId, event)`). A * `string[]` picks a random variant per trigger. `death` clamps on its final frame instead of returning. */ oneShots?: Record; /** * A full animation graph (`@jgengine/core/anim/animGraph`) — layered state machine with blend trees, data * transitions, triggers, and clip events. Takes over from `states`/`oneShots` when set: the shell feeds the * entity's smoothed ground speed as the `speed` parameter plus anything under the entity blackboard key * `ANIM_PARAMS_KEY`, arms triggers from `entity.animation` (`hit` and `death` from their combat events), and * emits `animation.event` for clip events. `locomotionGraph()` builds the default idle/walk/run graph. */ graph?: AnimGraph; } /** * Real PBR map URLs (e.g. `buildMaterialCatalog(...).resolve(id)!.maps` from `@jgengine/assets`) * layered onto a model's material — the seam for texturing an otherwise-flat/untextured GLB. Any * supported MaterialTextureRole may be omitted to keep the model's own map. Physical roles use the * renderer's physical material; map URLs and response parameters remain caller-owned. */ export interface ModelMaterialMaps extends Partial> { } /** Per-entity PBR material override (#151.3) applied to every standard or physical material in the model's cloned scene graph. */ export interface ModelMaterialOverride extends MaterialSurfaceParameters { /** Real PBR texture maps applied over the model's material — see {@link ModelMaterialMaps}. */ maps?: ModelMaterialMaps; /** * Fresnel rim light: a bright edge where the surface turns away from the viewer. This is what * separates an actor from ground of the same value — a body lit only by the scene's own key sits * in the terrain's tonal band at range and reads as a smudge, however well its albedo is chosen. * Omit for no rim. */ rim?: ModelRimLight; } /** Fresnel rim-light term added on top of a model's lighting — see {@link ModelMaterialOverride.rim}. */ export interface ModelRimLight { /** Rim colour. Default "#ffffff". */ color?: string; /** Rim brightness. 0 disables it; ~0.3 is a subtle read, ~1.5 is a hard sci-fi edge. Default 0.6. */ strength?: number; /** Falloff exponent: higher confines the rim to the extreme edge. Default 2.5. */ power?: number; } /** Parents a prop/weapon model to a named bone or node on the host model's rig — a sword on `handslot.r`, a spellbook offhand — following the bone's animated transform each frame. */ export interface ModelAttachment { /** Bone or node name in the rig to parent to (e.g. `"handslot.r"`). */ slot: string; /** The attached model — a catalog asset id or an inline `ModelConfig`. */ model: string | ModelConfig; /** Local position offset at the bone, in the attached model's own space. */ position?: [number, number, number]; /** Local Euler rotation (radians) applied at the bone. */ rotation?: [number, number, number]; /** Uniform scale of the attached model at the bone. Default 1. */ scale?: number; } /** Static child model stacked at a fixed local offset under its parent's transform — no bone/rig resolution, unlike `ModelAttachment`. Assembles a compound entity (e.g. a modular castle wall + tower + roof) from several single-mesh kit pieces. */ export interface ModelPart { /** The child model — a catalog asset id or an inline `ModelConfig`; resolves through the catalog exactly like the top-level model and gets its own `dims`/anchor centering. */ model: string | ModelConfig; /** Local position offset under the parent's transform. */ position?: [number, number, number]; /** Local Euler rotation (radians) under the parent's transform. */ rotation?: [number, number, number]; /** Uniform scale of the part under the parent's transform. Default 1. */ scale?: number; /** * Semantic motion role for a rig-less part-composed character (see `game/partAnimation`). * Tagging any part enables the shell's procedural driver on an entity model: legs and arms swing * counter-phase with movement speed, the head counter-sways, tails wag, wings flap, and the whole * composition gains walk bob, idle breathe, hit flinch, and a death topple. Untagged parts stay * static kit pieces. */ role?: PartRole; } export interface ModelConfig { url: string; scale?: number; /** Normalize the rendered model to this world-unit height: the shell measures the loaded scene's bounding box, scales it so its height matches, and grounds its lowest point at the placement Y. Composes with `scale` as a multiplier. */ targetHeight?: number; y?: number; /** How the model registers on its placement point. `"center"` (default) horizontally centers the measured footprint on the point and ground-snaps its lowest vertex to the point's Y — the correct behavior for corner-pivot modular kits. `"origin"` renders at the raw GLB origin (legacy). */ anchor?: "center" | "origin"; /** Measured footprint/center/minY; supplied automatically when the model resolves through an `@jgengine/assets` catalog. Required for `anchor: "center"` to take effect. */ dims?: ModelDims; /** Opt-in compact collision mesh from the asset index; when present, collider auto-fit raycasts the actual triangles, so shots pass through holes in concave models (torus, archway) instead of hitting the fitted box. Supplied automatically for opted-in catalog assets. */ collisionMesh?: CollisionMeshData; /** Per-entity PBR tint/finish override (#151.3); cloned onto each `MeshStandardMaterial` in the model so shared GLTF caches stay untouched. */ material?: ModelMaterialOverride; /** `false` keeps this model's exported material params and skips the engine's default painted surface detail. Default true. */ surfaceDetail?: boolean; /** * Per-model wind sway for foliage: trunk and standing geometry stay planted while the upper * canopy drifts in the shared gust field. `strength` is metres of tip travel, `speed` the gust * rate, `height` the world height over which sway ramps in (defaults to the model's own height). * `false` or omitted keeps the model rigid. Compiles into each cloned material. */ wind?: ModelWindConfig | false; /** Reusable authored surfaces available to this model's named slot assignments. */ materialAssets?: readonly MaterialAsset[]; /** Sparse per-slot assignments; unselected imported materials keep their original appearance. */ materialAssignments?: readonly MaterialAssignment[]; /** Hide named visual subtrees on this instance before placement measurement. Exact runtime names or unique original imported names resolve without removing bones or changing the cached model; missing or ambiguous names warn and leave existing visibility unchanged. Malformed lists reject before model or texture allocation. Omit or pass [] to retain imported visibility. */ hiddenNodes?: readonly string[]; /** Shadow participation of every mesh in the model. Default `"both"` (casts and receives); `"none"` for foliage cards, decals, or viewmodels that must never shadow the world. */ shadows?: "cast" | "receive" | "both" | "none"; /** * Plays a GLTF animation clip on the model when the source has any (skinned or not). * A model resolved from a catalog id whose index carries `clips` animates automatically — * semantic clip roles build the idle/walk/run states and hit/death/attack one-shots (see * `defaultAnimationForClips`). `"auto"` forces that derivation, `"none"` opts out (bind pose), * and an explicit config always wins. Omit on an inline (non-catalog) config to render the * rig's bind pose. */ animation?: ModelAnimationConfig | "auto" | "none"; /** * Runtime inverse kinematics for a rigged model, applied after the animation mixer each frame. * Feet follow slopes and steps while keeping each clip's swing lift, soles never sink below the * ground, the pelvis drops so the lower leg can reach, and the correction fades out while airborne. * `"auto"` finds the legs by bone name (`UpLeg`, `UpperLeg`, `Thigh`); bone names resolve on the * loaded scene. */ ik?: "auto" | ModelIkConfig; /** Props/weapons parented to named bones on this model's rig; each follows its bone through animation. */ attachments?: readonly ModelAttachment[]; /** Static kit-of-parts pieces stacked at fixed local offsets — no bone/rig involved. Use this for a compound entity assembled from several modular meshes (a castle keep from base + mid + roof pieces); use `attachments` for props parented to an animated rig's bones. Tag parts with a `role` to procedurally animate a rig-less character composition. */ parts?: readonly ModelPart[]; /** Tuning for the procedural part-motion driver when any part carries a `role`; omit for defaults. */ partMotion?: PartMotionParams; /** * Distance tiers for a rigged character: how far away it still poses its skeleton every frame, and * from where a far one renders through the GPU crowd path instead. `false` keeps this model at full * rate everywhere, which is what a player avatar or a character in a cutscene wants. Omit for no * distance tiers (full rate) — see {@link ModelLodConfig}. */ lod?: ModelLodConfig | false; } /** * Distance tiers for one character model. Distances are metres from the camera; every field is data * so a game can set its own thresholds per model and retune them without touching the engine. */ export interface ModelLodConfig { /** Within this distance the rig poses every frame. Default 15. */ fullRateDistance?: number; /** Within this distance the rig poses every other frame, carrying the accumulated time. Default 40. */ halfRateDistance?: number; /** Beyond this distance a crowd-eligible rig renders through the GPU crowd path and updates no bones. Default 25. */ crowdDistance?: number; /** Metres a character must come back inside `crowdDistance` before it returns to its own rig, so the swap cannot flicker at the boundary. Default 2. */ crowdHysteresis?: number; } /** Wind sway for a foliage model; see {@link ModelConfig.wind}. */ export interface ModelWindConfig { /** Tip travel in metres. Default 0.35. */ strength?: number; /** Gust rate multiplier. Default 1. */ speed?: number; /** World height over which sway ramps from the base to full tip travel, in metres. Defaults to the model's measured height. */ height?: number; /** Wind heading as a two-axis direction; defaults to the engine's shared gust direction. */ direction?: readonly [number, number]; } /** Bone names and limits for {@link ModelConfig.ik}. */ export interface ModelIkConfig { /** Thigh → shin → foot chains; omit to find them by bone name. `[]` turns foot IK off. */ feet?: readonly { root: string; mid: string; tip: string; }[]; /** Bone shifted down when a foot has to reach lower ground; defaults to the first thigh's parent. */ pelvis?: string; /** Largest foot or pelvis correction, as a fraction of leg length. Default `0.4`. */ maxAdjust?: number; /** Tilt planted feet to the ground normal, `0..1`. Default `1`. */ alignToGround?: number; /** Turns this bone toward a target. */ lookAt?: ModelLookAtConfig; } /** {@link ModelIkConfig.lookAt}: the bone that turns, and what it turns toward. */ export interface ModelLookAtConfig { /** Bone turned toward the target; the head on a humanoid rig. */ bone: string; /** * What the bone turns toward: the camera (default), another entity's position, or a fixed world * point. An entity target that is not spawned this frame keeps the last known direction. */ target?: "camera" | { entity: string; } | { point: readonly [number, number, number]; }; /** Height above an entity target's origin to aim at; the entity's eye level. Default 1.5. */ entityEyeHeight?: number; /** Yaw limit in radians; the head stops turning past it. Omitted means unconstrained. */ maxYaw?: number; /** Pitch limit in radians. Omitted means unconstrained. */ maxPitch?: number; /** How much of the solved turn reaches the bone, `0..1`. Default `1`. */ weight?: number; } export interface ObjectStyle { color?: string; opacity?: number; hidden?: boolean; } export interface AmbientLightingConfig { color?: string; intensity?: number; } export interface DirectionalLightingConfig { color?: string; intensity?: number; position: readonly [number, number, number]; castShadow?: boolean; /** Shadow map resolution in px (square) when `castShadow`. Higher = crisper, costlier. Default 1024. */ shadowMapSize?: number; /** Half-extent of the orthographic shadow camera in world units — sized to the shadowed area. Default 40. */ shadowCameraSize?: number; /** * Cascade count for cascaded shadow maps (CSM). When `> 1` and `castShadow`, the shell mounts * three.js CSM so outdoor scenes keep shadows past a single ortho frustum. Default `1` (legacy * single map). Typical outdoor value: `3` or `4`. */ cascades?: number; /** Max far plane for CSM cascades in world units. Default 200. */ shadowMaxFar?: number; /** Shadow depth bias to fight acne. Default -0.0004. */ shadowBias?: number; /** Shadow normal bias to fight peter-panning. Default 0.02. */ shadowNormalBias?: number; /** PCF penumbra width in shadow-map texels; 0 is a hard edge. Default 1.25. */ shadowRadius?: number; } export interface HemisphereLightingConfig { skyColor?: string; groundColor?: string; intensity?: number; } /** A bounded dynamic point light in world space. */ export interface PointLightingConfig { color?: string; intensity?: number; position: readonly [number, number, number]; distance?: number; decay?: number; castShadow?: boolean; } /** A bounded dynamic spot light in world space. */ export interface SpotLightingConfig extends PointLightingConfig { target?: readonly [number, number, number]; angle?: number; penumbra?: number; } /** Declarative lighting replacing the shell's hardcoded ambient/directional default (#207.5); mounts regardless of world kind, only when supplied. */ export interface LightingConfig { ambient?: AmbientLightingConfig; directional?: readonly DirectionalLightingConfig[]; point?: readonly PointLightingConfig[]; spot?: readonly SpotLightingConfig[]; /** Maximum dynamic point + spot lights mounted by the shell. Default 8. */ maxDynamicLights?: number; hemisphere?: HemisphereLightingConfig; /** * Multiplier on `ambient` intensity while image-based light is on (every look but `"flat"`), so the * shaped sky light carries the fill instead of a flat wash. Default 0.5; 1 keeps the authored value. */ iblAmbientScale?: number; /** * With no `directional` entry, the shell adds a soft shadowless key light so forms still get a lit * and a shaded side. Default true; `false` keeps the scene lit by ambient/hemisphere/IBL alone. */ fallbackKey?: boolean; } export interface BackdropFogConfig { color?: string; near?: number; far?: number; /** Exponential (`FogExp2`) falloff instead of linear near/far; when set, `near`/`far` are ignored. */ density?: number; } /** Generic sky/background/fog for ANY world kind, including a custom `environment` component (#207.6). */ export interface BackdropConfig { background?: string; sky?: SkyEnvironmentConfig; fog?: BackdropFogConfig; } /** Free-flight families moved into the walk controller so one seam covers ground + air — creative/spectator/noclip/hover are weightless, aircraft, rotorcraft and rockets use `physics/aircraftDynamics`. */ export interface FlightConfig { /** Which free-flight family to use when flight is active. Default "creative". */ mode?: "creative" | "spectator" | "noclip" | "hover"; /** Horizontal base speed (units/s). Default 8. */ speed?: number; /** Vertical base speed for ascend/descend. Default = `speed`. */ verticalSpeed?: number; /** Sprint multiplier while shift is held. Default 1.8. */ sprintMultiplier?: number; /** Lerp rate toward target velocity. Default 20. */ acceleration?: number; /** Downward acceleration for hover mode. Default 0 (weightless). */ gravity?: number; /** Upward thrust while ascending in hover. Default 32. */ thrust?: number; /** When true, W moves along the look pitch (6DOF spectator). Default false. */ alignWithLook?: boolean; /** Whether flight slides against world solids. Default true for creative/hover, false for spectator/noclip. */ collide?: boolean; /** Axis bindings for this flight — which actions drive each axis. Unlisted axes keep the defaults above. */ bindings?: FreeFlightBindings; /** Camera POV while this flight is active — chase tuning overlay applied on mount, cleared on dismount. */ camera?: ChaseCameraTuning | null; /** Gate flight behind live game state — creative toggle, stamina check, etc. `false` stays walking. Default true when `flight` is present. */ canFly?: (ctx: GameContext) => boolean; } /** Feel knobs for the built-in walk controller; see {@link PlayerMovementConfig.feel}. */ export interface MovementFeelConfig { /** Ground velocity response, 1/s (default 26). Lower drifts into and out of motion; higher is snappy. */ groundAcceleration?: number; /** Air velocity response, 1/s (default 12). `0` commits to the jump arc; high values steer freely in the air. */ airAcceleration?: number; /** Stopping response on the ground with no input, 1/s (default 18). Lower slides like ice. */ groundFriction?: number; /** Sprint speed as a multiple of walk speed (default 2.25). */ runMultiplier?: number; /** Crouch speed as a multiple of walk speed (default 0.45). */ crouchMultiplier?: number; /** A jump pressed up to this many ms before landing fires on landing (default 0). */ jumpBufferMs?: number; /** A jump pressed up to this many ms after walking off a ledge still fires (default 0). */ coyoteMs?: number; /** Rising speed kept when jump is released early, 0..1 (default 1: no cut). Lower makes a tap a hop. */ jumpCutFactor?: number; /** Gravity multiplier near the peak, while vertical speed is under `apexSpeed` (default 1). Below 1 hangs. */ apexGravityScale?: number; /** Vertical speed under which `apexGravityScale` applies, m/s (default 1.5). */ apexSpeed?: number; /** Gravity multiplier while falling (default 1). Above 1 drops faster than it rose. */ fallGravityScale?: number; /** After landing, jumps wait and walk speed ramps back from `landingSpeedScale` over this many ms (default 0). */ landingRecoveryMs?: number; /** Walk speed share right after landing when `landingRecoveryMs` is set (default 0.5). */ landingSpeedScale?: number; } /** Movement-control levers for the shell-driven local player walk controller. */ export interface PlayerMovementConfig { /** "free" (default) moves camera-relative across the plane; "axis" locks travel to one world axis; "grid" snaps each committed position to cell centers. */ mode?: "free" | "axis" | "grid"; /** World axis for mode "axis". Default "x". */ axis?: "x" | "z"; /** Cell size for mode "grid". Default 1. */ cellSize?: number; /** * Collide the walking player against placed scene objects' blocking colliders even without * `collision.voxel`. Default **true** — a placed object with a blocking physical collider stops, * supports, and is stood upon by the player; set `false` to walk through everything (ghost/spectator * modes, pure-board presentations). Objects that should never block should carry non-blocking * colliders instead of relying on this switch. */ collideObjects?: boolean; /** Heightfield collision span from feet, finite and positive; default 1.8 meters. Live growth waits for clear headroom. Capsule and voxel paths retain their own dimensions; model proportions and camera placement remain game-owned. */ collisionHeight?: number; /** Tallest object ledge stepped up while walking (and largest drop still snapped down instead of falling). Default 0.4. */ stepHeight?: number; /** Intercepts each frame's resolved position before the pose commits (and before onTick): return a replacement [x,y,z] to constrain or redirect the step, or nothing to accept it. */ beforeCommit?: (frame: MovementCommitFrame) => readonly [number, number, number] | undefined | void; /** Gates the built-in sprint (`runSpeedMultiplier`) behind live game state — a stamina stat, an encumbrance check (#282.3). Called each frame while sprint is held; `false` walks. */ canSprint?: (ctx: GameContext) => boolean; /** Fraction of walk speed while backpedalling (holding `moveBack`). Overrides the engine default (0.65). */ backpedalMult?: number; /** * Walk-controller feel. Acceleration and friction are exponential response rates (1/s): higher reaches * the target velocity or stops sooner. Omitted fields keep the engine defaults, so nothing changes until a * game sets one. The heightfield and physics-backed capsule paths share jump feel; a `collision.voxel` * body retains press-frame jumping with plain gravity. Tune against `measureMovement` (`@jgengine/core/movement/movementProbe`). */ feel?: MovementFeelConfig; /** Radians/second the rendered body rotates toward its movement heading (shortest arc), so strafing/backpedalling read as a turn rather than an instant flip; also the rate the internally-integrated `turnLeft`/`turnRight` heading turns when the shell doesn't own yaw. Unset = body facing snaps instantly (no change to existing feel). */ turnSpeed?: number; /** On-foot swimming when the terrain declares a `waterLevel` and the player is submerged: caps speed and floats them at the surface. `true` uses defaults; default off. */ swim?: { speedMultiplier?: number; } | boolean; /** Slide the player downhill on terrain steeper than they can stand on (heightfield worlds only). `true` uses defaults; default off. */ slopeSlide?: { maxClimbSlope?: number; } | boolean; /** * Maximum sampled uphill rise/run for heightfield walking, including jumps. A rejected step * slides along a traversable X or Z axis and feet use the accepted ground. Finite, nonnegative; * omitted means unrestricted. Capsule, voxel and flight paths retain their own collision rules. */ maxClimbGrade?: number; /** * Optional terrain-policy sampler for `maxClimbGrade`; defaults to the resolved ground sampler. * Use when grade rules follow a different terrain field (for example before road cuts). This * does not change foot grounding. Called at most four times per constrained walking step; * must return finite heights or the step throws with the invalid sampling coordinate. */ climbGradeHeight?: (x: number, z: number) => number; /** * Free-flight — creative/spectator/noclip/hover — folded into the same walk controller so a game * doesn't hand-roll a second movement loop to get Minecraft-like flight. `true` uses creative defaults; * an object tunes speed/vertical/sprint/collide/canFly. Horizontal is always yaw-relative strafe * (A = left, D = right — never roll/bank), vertical is Space/Ctrl, and aircraft, rotorcraft and rockets * use `physics/aircraftDynamics`. The shell drives this automatically when present; `canFly` toggles * between walking and flying per tick without rebuilding the config. */ flight?: FlightConfig | boolean; } /** One frame's movement resolution handed to `PlayerMovementConfig.beforeCommit`. */ export interface MovementCommitFrame { entityId: string; current: readonly [number, number, number]; next: readonly [number, number, number]; dt: number; /** The live game context (#282.2) — movement rules read stores/stats directly instead of module-level bridge objects. */ ctx: GameContext; } /** * Per-channel combat presentation toggles for the 3D shell canvas. * Missing keys default to enabled when the parent `presentationEffects` is an object. */ export interface PresentationEffectsConfig { /** Ground telegraphs for AoE / cast indicators. Default true. */ telegraphs?: boolean; /** One-shot spell VFX + retained (persistent) VFX instances. Default true. */ vfx?: boolean; /** Floating combat text (damage numbers, etc.). Default true. */ floatText?: boolean; /** * Straight muzzle→impact tracer lines for direct-fire shots. Opt-in (default false): unlike the * other channels, a missing `tracers` key leaves them OFF, because `fireProjectile` is a generic * seam and a tracer is only meaningful for a game that fires direct, bullet-like projectiles. * Arced/exploding shots (grenades, launchers) never draw a tracer even when this is on. */ tracers?: boolean; /** Trauma-driven combat camera shake. Default true. */ shake?: boolean; } /** How a screenshot host reaches live gameplay in this game — the data behind `shoot --mode play`. */ /** * One named framing in {@link GameCaptureConfig.views}: everything about a capture except the host-side * viewport (`--device`, `--size`) and `--mode`, which the runner routes on before the game module loads. * Every field mirrors the equivalent capture flag, and an explicit flag on the command line wins over the * shot's value so a declared framing stays a starting point rather than a cage. */ export interface GameCaptureView { /** One line naming what this shot is for — printed by `shoot --list-views`. */ description?: string; /** `--look`: `x,z`, `x,y,z`, `@marker:`, or `@entity:`. */ look?: string; /** `--look-from`: `dist[,height[,angle]]`. */ lookFrom?: string; /** `--spawn`: `x,y,z` or `x,y,z,yaw`, for a shot framed from the player's vantage instead of a detached camera. */ spawn?: string; /** `--state`: a {@link GameCaptureConfig.states} entry to reach before capturing. */ state?: string; /** `--run`: commands to dispatch before capturing, when the shot needs staging no declared state covers. */ run?: readonly string[]; /** `--settle`: milliseconds to wait before the frame, for a shot behind an animation. */ settleMs?: number; } /** * How a game makes itself screenshottable: the commands that reach live play or a named screen, the * framings worth re-capturing, and the progress metrics a bot playtest samples. Declaring these once * is what lets a capture host reproduce a view instead of re-deriving it by hand every run. */ export interface GameCaptureConfig { /** * Commands run (in order, via `ctx.game.commands.run`) right after boot when a capture host requests the * play screen — the same commands the start-menu buttons dispatch. A bare string runs with a default input; * the object form carries the input a command needs (e.g. `[{ name: "class.select", input: { classId: "siren" } }, "startRun"]`). * A name the game never registers fails the capture loudly instead of shipping a menu screenshot. * * Timing contract: these dispatch **once**, synchronously, at `onContextReady` — after `onNewPlayer` but * before any async boot work it kicked off has settled. If the game restores state asynchronously (a * whole-world save load, network hydration) and that restore resets the same "started" gate these commands * flip, the restore lands *after* the play commands and re-shows the menu — the capture then fails with a * start menu still on screen even though `play` is declared. Make such a restore preserve an already-live * session (skip the reset when the start gate is already set) so it does not bounce capture back to the title. */ play?: readonly (string | { name: string; input?: unknown; })[]; /** Named capture states beyond live gameplay — any screen worth screenshotting on demand (`lobby`, `store`, `game_over`), each mapping to the command sequence that reaches it from boot. `shoot --state ` runs that sequence and captures whatever is on screen — menus included, no live-play guard. An unknown state name fails the capture with the declared list. */ states?: Record; /** * Named framings worth re-capturing — the views a reviewer actually asks about (`street`, `keep-exterior`, * `boss-arena`), each pinning the camera, the staging, and the settle for that shot. `shoot --view ` * reproduces one exactly, so a before/after pair is the same framing by construction instead of two hand-typed * flag sets that drifted. An unknown name fails the capture listing the declared views. */ views?: Record; /** Extra milliseconds the capture host waits after `play` commands before taking the play-mode screenshot — cover an intro cinematic or spawn-in fade. Default 2500. */ settleMs?: number; /** Bot-playtest read hook: maps live game state to a flat vector of numeric progress metrics the `drive --playtest` harness samples over time (e.g. `{ x, z, score, phase }`). Genre-agnostic — "progress" is whatever the game reports; the engine stays neutral. The harness watches these numbers move under scripted input: any metric changing beyond an epsilon counts as forward progress, all metrics flat past the softlock threshold under active input flags a softlock. Return every value that should count as advancing; omit to opt the game out of the playtest rung. */ probe?: (ctx: GameContext) => Record; } export interface PlayableGame { game: GameDefinition; content: GameContextContent; loop: Required, "onPlayerLeave">> & Pick, "onPlayerLeave">; GameUI: TUi; /** Which shell mount to use. Default `"3d"` (canvas, camera rig, pointer, world rendering). `"hud"` mounts no 3D canvas, camera rig, or pointer — the game is `GameUI` plus the command/input loop, for board/card/menu games. */ presentation?: "3d" | "hud"; /** Optional canvas-layer VFX component (e.g. traveling projectiles); receives `{ ctx }` (#542) so overlay VFX read live engine state without a separate hook or a module-global workaround. */ WorldOverlay?: TOverlay; /** Replaces the default demo backdrop (ground + grid + rocks) with the game's own scene — ground, sky, structures. Camera, input, HUD, entity rendering, and the loop stay shell-provided; supply your world without forking the shell. When unset and `game.world` is an `environment()` descriptor, the shell auto-renders that world here — no manual wiring needed. */ environment?: TWorldOverlay | EnvironmentSource; /** The game's authored scene document (`editor.scene.json`, normalized). When set, `defineGame` always mounts `AuthoredScene` over it (draped paths, scatter, studios, placed catalog props) — any `WorldOverlay` renders alongside it as VFX only — and the embedded editor opens it as its default layers: the zero-wiring path from document to play and edit modes. */ editorLayers?: EditorDocument; /** Game-exported gameplay catalog definitions (schemas + defaults). `GameHost` forwards them to the summoned editor's Data panel; no per-game editor bootstrap needed. */ editorCatalogs?: readonly EditorCatalogDefinition[]; /** Custom first-person viewmodel (#542), read when the active rig is first-person. Rendered inside the shell's camera-locked, muzzle-tracked anchor in place of the built-in three-mesh gun; receives a live `cuesRef` (velocity/bob/firing/reloading/recoil) driven from the followed entity — see `@jgengine/shell/camera`'s `ViewmodelProps`. Set `camera.firstPerson.viewmodel: false` to render no viewmodel at all regardless of this field. */ viewmodel?: TViewmodel; /** Per-entity visual override: return your own mesh for an entity and the shell still positions it and drives selection/targeting. Return null/undefined to fall back to model → sprite → primitive. */ renderEntity?: TRenderEntity; /** Per-object visual override: return your own mesh for a placed scene object and the shell still positions it and drives picking. Return null/undefined to fall back to objectModels → styled box. */ renderObject?: TRenderObject; /** Billboard sprites keyed by entity kind name; unmatched entities get primitive markers. */ entitySprites?: Record; /** GLB models keyed by entity kind name; a string resolves as an asset-catalog key via game.assets, a ModelConfig renders directly. Takes priority over sprites, then primitives. */ entityModels?: Record; /** GLB models keyed by object catalog id; a string resolves via game.assets, a ModelConfig renders directly. Replaces the colored box when present. */ objectModels?: Record; /** Styling for the default colored-box object render, keyed by catalog id: color override, opacity (< 1 sets transparent), hidden (skips the mesh but keeps the positioning group + picking). */ objectStyles?: Record; /** Optional scroll-selected hotbar index for primary ability (mouse0). */ hotbarSelection?: () => number; /** Positioned proximity prompts for the interact key + HUD; single source shared with useActivePrompt. */ prompts?: (ctx: GameContext) => readonly PositionedPrompt[]; /** Camera tuning (perspective, orbit, first-person) for the dev game player shell. */ camera?: GameCameraConfig; /** Screenshot-host recipe for reaching live gameplay past a start menu (`shoot --mode play` runs `capture.play` automatically). Any game with a start/title screen declares this. */ capture?: GameCaptureConfig; /** Cast/receive shadows across the scene (R3F Canvas shadow pass). Default true. */ shadows?: boolean; /** Per-quality renderer budgets; omitted tiers use {@link DEFAULT_GRAPHICS_PROFILES}. */ graphics?: Partial>>; /** Default painted finish on loaded models (normalized glTF params plus procedural mottling, wear and micro-normal, scaled by the graphics tier's `surfaceDetail`). Default true; `false` renders every model with its exported material. */ surfaceDetail?: boolean; /** Pointer-driven input: click-to-move, box-select, right-click verbs, cursor aim (#22/#30/#31). */ pointer?: PointerConfig; /** Touch controls on coarse-pointer devices. Unset derives a scheme from `input` (virtual joystick for movement actions, on-screen buttons for the rest); a config refines it with gestures and curated buttons; `false` opts out. */ touch?: TouchControlsConfig | false; /** Gamepad stick deadzone, response curve and trigger deadzone the shell's pad poll applies before publishing analog values. Unset keeps the shell defaults (axial `0.12`/`0.95`, linear). */ gamepad?: GamepadFeelConfig; /** Local seats on this screen for couch co-op. With `maxSlots > 1` each extra pad hot-joins its own seat on its first button press, the shell calls `loop.onNewPlayer(ctx, { userId, isNew: true })` for it, and its input lands on `localPlayers(ctx).local(slotId).input` instead of `ctx.input`. Unset keeps one seat: every device drives `ctx.player`. */ localPlayers?: LocalPlayersConfig; /** Split-screen for local seats (`localPlayers`): one viewport per joined seat, each following its own entity. The primary seat keeps the main camera rig; post-processing is skipped while more than one viewport is drawn. */ viewports?: ViewportsConfig; /** Phone orientation contract. Legacy `"landscape"`/`"portrait"` stays advisory (a dismissible rotate hint). The object form `{ mobile: "landscape-required" }` is strict — the shell shows an engine-owned rotate screen and blocks gameplay until the device is turned. See `GameOrientation`. */ orientation?: GameOrientation; /** Where the game is meant to be played. Default `["web", "mobile"]` — design-resolution HUD fit is on for every game: `HudCanvas` auto-scales from `hudFit.designSize` down to the live viewport, so the desktop layout shrinks to fit a phone instead of overflowing it. Declare `["web"]` to opt a desktop-only game out (compact displays fall back to the legacy fixed 0.85 zoom). */ platforms?: readonly HudPlatform[]; /** HUD design resolution + scale clamps (default 1600×900, scale 0.4–1). `mobile` overrides tune the phone fit separately — the same resolution system drives desktop UI-scale and phone shrink. */ hudFit?: HudViewportConfig; /** Opt in to world-space health bars floating over non-local entities that carry the stat. `roles` restricts bars to entities whose catalog entry declares one of the given roles; `maxDistance` hides bars beyond this many world units from the render camera, and unset derives the range from the camera and the target height (an overhead camera reaches the units it looks at). */ worldHealthBars?: boolean | { statId?: string; roles?: readonly CatalogEntityRole[]; maxDistance?: number; /** Maximum overlays and visibility rays per refresh. Default 64, capped at 256. */ maxSamples?: number; /** Minimum refresh interval in ms. Default 80 for bars, 120 for plates. */ tickMs?: number; /** Hide bars behind blocking geometry from the render camera. Default true; false explicitly reveals through walls. */ occlude?: boolean; }; /** * Opt in to billboarded nameplates (name + optional HP bar) floating over non-local entities — the * MMO "who's this and how hurt are they" readout. `roles` restricts to entities whose catalog entry * declares one of the given roles (default: all); `maxDistance` hides nameplates beyond this many * world units from the render camera, and unset derives the range the same way the bars do. Headless: skin every part via `className`/`data-*` hooks * on `WorldNameplates` (`@jgengine/shell/world/WorldHud`) — this flag only turns the readout on and * scopes which entities it covers. Set `showHealth: false` for a name-only plate when a game already * draws the HP bar itself (its own HUD, or `worldHealthBars`) — otherwise the nameplate's built-in bar * stacks a second health bar over every entity. */ nameplates?: boolean | { statId?: string; roles?: readonly CatalogEntityRole[]; maxDistance?: number; /** Maximum overlays and visibility rays per refresh. Default 64, capped at 256. */ maxSamples?: number; /** Minimum refresh interval in ms. Default 80 for bars, 120 for plates. */ tickMs?: number; showHealth?: boolean; /** Hide plates behind blocking geometry from the render camera. Default true; false explicitly reveals through walls. */ occlude?: boolean; /** Authored display name. Return null to omit a plate; default suppresses machine identifiers. */ resolveName?: (entity: SceneEntity) => string | null; }; /** * Combat presentation stack mounted inside the 3D canvas (telegraphs, spell VFX, retained VFX, * float text, projectile tracers, camera shake). Default `true` preserves historical always-on * behavior. `false` mounts none. An object mounts only the listed pieces (missing keys default * on so games can disable a single expensive channel, e.g. `{ tracers: false }`). */ presentationEffects?: boolean | PresentationEffectsConfig; /** Sound catalog + mix buses (music/sfx/ambient/…) the shell's Web Audio glue plays from. Catalog-first — no per-game audio wiring. `sounds` may be sample (`url`) or procedural (`synth`); `music` holds procedural themes crossfaded via `ctx.game.audio.music(id)`. */ audio?: { sounds: Record; /** SFX playback/patch budget, including pending samples; individual synth oscillators share one slot. Default 64; zero disables SFX. */ maxVoices?: number; /** Overflow rejects new cues or steals the lowest-priority oldest cue. Default "steal-lowest". */ voiceOverflow?: "steal-lowest" | "reject"; buses?: Record; music?: Record; musicBus?: string; /** Play UI cues for engine HUD controls (`HudButton` click/hover, `HudSurface` and window open/close, action slots). Independent of notice sounds (`notices.cues`). Default true. */ hudCues?: boolean; /** Default UI cues (`ui.click`, `ui.deny`, ...) on a "ui" bus. A sound named `ui.` in `sounds` replaces one; a map entry replaces or (`false`) silences one; `false` silences all. */ uiCues?: UiCueOverrides; /** Default gameplay reactions (`feel.hit`, `feel.kill`, `feel.pickup`, ...) on an "sfx" bus, played by the shell from the events the engine announces. A sound named `feel.` in `sounds` replaces one; a map entry replaces or (`false`) silences one; `false` silences all. */ feedbackCues?: FeedbackCueOverrides; }; /** Continuous positional emitter keyed by entity kind name: while a matching entity exists, the shell plays and repositions the mapped `audio.sounds` id (looping engine hum, campfire crackle, footstep loop) with listener-distance falloff. */ entitySounds?: Record; /** Same as `entitySounds` but keyed by placed-object catalog id (torches, machinery). */ objectSounds?: Record; /** Rarity render binding + loot filter for dropped-item ground presentation (#32/#33). */ worldItem?: WorldItemRenderConfig; /** Player-vs-world collision for the first-person controller (block/voxel worlds). Off by default (flat ground). */ collision?: VoxelCollisionConfig; /** Movement-control levers (axis/grid constraints, object collision, pre-commit hook) for the shell-driven walk controller. */ movement?: PlayerMovementConfig; /** Graphics look profile (#1680). Unset/`"neutral"` keeps the output unstyled; `"photoreal"` and `"toon"` select authored presentation profiles, while `"cinematic"` and `"flat"` remain compatibility aliases. Explicit `lighting`/`backdrop`/`postProcessing` always wins. */ look?: LookPreset; /** Declarative ambient/directional/hemisphere lighting (#207.5); replaces the shell's hardcoded default lights when present, regardless of world kind. */ lighting?: LightingConfig; /** Image-based environment source; omit to use the procedural gradient probe. */ environmentSource?: EnvironmentSource; /** Generic background/sky/fog (#207.6), applied for any world kind including a custom `environment` component. */ backdrop?: BackdropConfig; /** Declarative post-processing chain (AO/bloom/tone-map/grade). When set, the shell mounts an EffectComposer and owns the render; absent leaves the renderer drawing directly (unchanged). */ postProcessing?: PostProcessingConfig; /** F2+D debug overlay (frame/sim timing, logs, backend latency, keybinds, live tunables). On for every game by default; `false` disables the toggle. */ devtools?: boolean; /** * Shell-mounted notice feed for `ctx.game.notices`: toned, stacked, self-expiring messages with a UI cue * per push, plus automatic deny notices for refused commands that return a `message`. On by default; * `false` removes the renderer, the cues and the refusal hookup (the feed itself stays on ctx). */ notices?: NoticePresentationConfig | false; /** Player settings menu. Auto-mounted for every game (Sound / Graphics / Gameplay / Controls); unset uses the defaults, `false` opts out. Add game-specific rows via `extra`, switch overlay/full-page via `mode`, or swap the gear for compact on-screen buttons via `surface: "quick"`. */ settings?: GameSettingsConfig | false; /** Automatic camera frustum + distance culling and asset streaming. On by default with conservative margins; unset uses engine defaults, `{ enabled: false }` opts out. Override bounds/distance/pins per entity kind or object catalog id. */ visibility?: VisibilityConfig; } export declare function worldHealthBarAllowsRole(roles: readonly CatalogEntityRole[] | undefined, role: CatalogEntityRole | undefined): boolean;