@gameable/core
Classes
FeatureError
Thrown for an unknown feature name.
Example
ts
import { FeatureError, resolveFeatures } from 'gameable/core';
try {
await resolveFeatures({ nope: {} }, {});
} catch (err) {
if (err instanceof FeatureError) console.log(err.message); // unknown feature "nope"; known: (none)
}Extends
Error
Constructors
Constructor
ts
new FeatureError(message?): FeatureError;Parameters
| Parameter | Type |
|---|---|
message? | string |
Returns
Inherited from
ts
Error.constructorConstructor
ts
new FeatureError(message?, options?): FeatureError;Parameters
| Parameter | Type |
|---|---|
message? | string |
options? | ErrorOptions |
Returns
Inherited from
ts
Error.constructorProperties
name
ts
readonly name: "FeatureError" = 'FeatureError';Overrides
ts
Error.nameModuleError
A module that could not be registered, initialised or found.
Extends
Error
Constructors
Constructor
ts
new ModuleError(
moduleId,
message,
options?
): ModuleError;Build a module error.
Parameters
| Parameter | Type | Description |
|---|---|---|
moduleId | string | Module or service id involved. |
message | string | What went wrong. |
options? | ErrorOptions | Standard Error options, used to keep the cause. |
Returns
Overrides
ts
Error.constructorProperties
moduleId
ts
readonly moduleId: string;The module or service id involved.
SceneGraph
Maps entity ids to scene objects.
Constructors
Constructor
ts
new SceneGraph(scene, name?): SceneGraph;Build a scene graph and attach its root to the scene.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
scene | Scene | undefined | Scene to attach to. |
name | string | 'gameable:root' | Name given to the root group, for debugging. |
Returns
Properties
root
ts
readonly root: Group;Group every spawned object is parented under.
scene
ts
readonly scene: Scene;The scene the root was added to.
Accessors
size
Get Signature
ts
get size(): number;How many entities are in the graph.
Returns
number
The entity count.
Methods
despawn()
ts
despawn(id): boolean;Remove an entity.
Children are re-parented to the root rather than removed with it, so a despawn cannot silently take a subtree the caller still tracks. Despawn them explicitly if that is what you want.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | number | Entity id. |
Returns
boolean
True when the entity existed.
dispose()
ts
dispose(): void;Remove every entity and detach the root from the scene.
Returns
void
get()
ts
get(id): Object3D<Object3DEventMap> | undefined;Look up an entity's object.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | number | Entity id. |
Returns
Object3D<Object3DEventMap> | undefined
The object, or undefined when the id is not spawned.
idOf()
ts
idOf(object): number;The entity id an object was spawned under.
Parameters
| Parameter | Type | Description |
|---|---|---|
object | Object3D | Any object in the graph. |
Returns
number
The id, or 0 when the object was not spawned here.
ids()
ts
ids(): IterableIterator<number>;Every spawned entity id, in insertion order.
Returns
IterableIterator<number>
An iterator over ids.
setParent()
ts
setParent(id, parentId?): void;Re-parent an entity.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
id | number | undefined | Entity id to move. |
parentId | number | NO_ENTITY | New parent's entity id, or 0 for the root. |
Returns
void
spawn()
ts
spawn(
id,
object,
parentId?
): Object3D;Add an object under an entity id.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
id | number | undefined | Entity id. Must be a positive integer; 0 is the root. |
object | Object3D | undefined | The object to add. |
parentId | number | NO_ENTITY | Parent entity id, or 0 for the root. |
Returns
Object3D
The object, so calls can be chained.
Example
ts
import { SceneGraph } from 'gameable/core';
import { Group, Scene } from 'three/webgpu';
const graph = new SceneGraph(new Scene());
graph.spawn(1, new Group());
graph.spawn(2, new Group(), 1); // child of entity 1TransformStore
Previous and current position, rotation and scale for a set of entity ids.
Constructors
Constructor
ts
new TransformStore(capacity?): TransformStore;Build a transform store.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
capacity | number | DEFAULT_TRANSFORM_CAPACITY | Slots to reserve up front. |
Returns
Accessors
capacity
Get Signature
ts
get capacity(): number;Slots currently allocated.
Returns
number
The number of addressable entity slots.
highWater
Get Signature
ts
get highWater(): number;One past the highest slot ever written.
Returns
number
The bound commit scans to.
Methods
clear()
ts
clear(id): void;Forget an entity. Its slot is reset to the identity transform.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | number | Entity id. |
Returns
void
commit()
ts
commit(): void;Make the current transform the previous one, for every slot that changed.
Call once per fixed step, before that step writes new values. Costs a byte per slot to scan plus ten floats per slot that actually moved.
Returns
void
ensure()
ts
ensure(id): void;Grow so that id is addressable.
Capacity doubles until it fits, so a run of spawn calls is amortised O(1).
Parameters
| Parameter | Type | Description |
|---|---|---|
id | number | Entity id that must fit. |
Returns
void
getPosition()
ts
getPosition(id, out): number[] | Float32Array<ArrayBufferLike>;Read the current position into out.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | number | Entity id. |
out | number[] | Float32Array<ArrayBufferLike> | A length-3 array to fill. |
Returns
number[] | Float32Array<ArrayBufferLike>
out.
getQuaternion()
ts
getQuaternion(id, out): number[] | Float32Array<ArrayBufferLike>;Read the current rotation into out.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | number | Entity id. |
out | number[] | Float32Array<ArrayBufferLike> | A length-4 array to fill. |
Returns
number[] | Float32Array<ArrayBufferLike>
out.
has()
ts
has(id): boolean;Whether an entity has ever been written.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | number | Entity id. |
Returns
boolean
True when the slot holds a transform.
isDirty()
ts
isDirty(id): boolean;Whether an entity is mid-motion or still owes its object a write.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | number | Entity id. |
Returns
boolean
True when the next writeInterpolated will touch the object.
set()
ts
set(
id,
px,
py,
pz,
qx,
qy,
qz,
qw,
sx,
sy,
sz
): void;Write a whole transform at once. This is the packed-buffer entry point.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | number | Entity id. |
px | number | Position x. |
py | number | Position y. |
pz | number | Position z. |
qx | number | Quaternion x. |
qy | number | Quaternion y. |
qz | number | Quaternion z. |
qw | number | Quaternion w. |
sx | number | Scale x. |
sy | number | Scale y. |
sz | number | Scale z. |
Returns
void
setPosition()
ts
setPosition(
id,
x,
y,
z
): void;Write the current position of an entity.
The first write to a slot also seeds the previous transform, so a freshly spawned entity does not interpolate in from the origin.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | number | Entity id. |
x | number | Position x. |
y | number | Position y. |
z | number | Position z. |
Returns
void
setQuaternion()
ts
setQuaternion(
id,
x,
y,
z,
w
): void;Write the current rotation of an entity.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | number | Entity id. |
x | number | Quaternion x. |
y | number | Quaternion y. |
z | number | Quaternion z. |
w | number | Quaternion w. |
Returns
void
setScale()
ts
setScale(
id,
x,
y,
z
): void;Write the current scale of an entity.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | number | Entity id. |
x | number | Scale x. |
y | number | Scale y. |
z | number | Scale z. |
Returns
void
snap()
ts
snap(id): void;Collapse one entity's history, so the next frame does not interpolate.
Use after a teleport.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | number | Entity id. |
Returns
void
writeInterpolated()
ts
writeInterpolated(
id,
object,
alpha
): boolean;Write the blend of previous and current onto an Object3D.
Once previous and current agree and the object has received that value, this returns without touching the object — and so without dirtying its world matrix — until the entity moves again.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | number | Entity id. |
object | Object3D | Target object; its position, quaternion and scale are written. |
alpha | number | Interpolation factor, normally the loop's alpha in [0, 1). |
Returns
boolean
True when the entity existed, whether or not the object needed writing.
Interfaces
CreateEngineOptions
Options accepted by createEngine.
Properties
canvas
ts
readonly canvas: HTMLCanvasElement;The canvas to render into. The engine observes it for size changes.
debug?
ts
readonly optional debug?: boolean;Create the F3 debug overlay. Defaults to false.
dracoDecoderPath?
ts
readonly optional dracoDecoderPath?: string;Draco decoder directory handed to the default gltf loader.
fixedHz?
ts
readonly optional fixedHz?: number;Simulation rate. Defaults to 60.
ktx2TranscoderPath?
ts
readonly optional ktx2TranscoderPath?: string;Basis transcoder directory handed to the default gltf loader.
loaders?
ts
readonly optional loaders?: Readonly<Record<string, AssetLoader>>;Asset loaders, replacing the built-in gltf and audio pair.
Adding a type is engine.ctx.assets.registerLoader(...), not this.
manifest?
ts
readonly optional manifest?: string | object | AssetManifest;The asset manifest: a URL to fetch, or an already-parsed document.
Omit for an engine with an empty registry, which is what the unit tests and the splat viewer use.
maxSubsteps?
ts
readonly optional maxSubsteps?: number;Most fixed steps one frame may run. Defaults to 5.
modules?
ts
readonly optional modules?: readonly EngineModule[];Modules to register, in any order; order decides the run order.
renderer?
ts
readonly optional renderer?: EngineRendererOptions;Renderer options.
CreateHeadlessEngineOptions
Options for createHeadlessEngine.
Properties
fixedHz?
ts
readonly optional fixedHz?: number;Simulation rate. Defaults to 60.
manifest?
ts
readonly optional manifest?: string | object | AssetManifest;The asset manifest: a URL to fetch, or an already-parsed document. Omit for an empty registry. No loaders are registered; add them with engine.assets.registerLoader(...).
maxSubsteps?
ts
readonly optional maxSubsteps?: number;Most fixed steps one step may run. Defaults to 5.
modules?
ts
readonly optional modules?: readonly EngineModule[];Modules to register, in any order; order decides the run order.
DebugOverlay
The debug overlay.
Properties
element
ts
readonly element: HTMLElement | null;The panel element, or null when there is no DOM to mount into.
stats
ts
readonly stats: FrameStats;The frame-time window behind the numbers.
visible
ts
readonly visible: boolean;Whether the panel is currently shown.
Methods
dispose()
ts
dispose(): void;Remove the panel and the key listener.
Returns
void
sample()
ts
sample(frameMs, nowMs): void;Record a frame and, at most every intervalMs, redraw.
Parameters
| Parameter | Type | Description |
|---|---|---|
frameMs | number | How long the frame took, in milliseconds. |
nowMs | number | Current timestamp, in milliseconds. |
Returns
void
setExtra()
ts
setExtra(source): void;Set, replace or clear the extra line source. There is one: a second call replaces the first. The panel redraws now if it is shown.
Parameters
| Parameter | Type | Description |
|---|---|---|
source | (() => string) | null | Returns the text to show under the built-in lines, or null to show none. |
Returns
void
setVisible()
ts
setVisible(value): void;Show or hide the panel.
Parameters
| Parameter | Type | Description |
|---|---|---|
value | boolean | True to show. |
Returns
void
toggle()
ts
toggle(): void;Flip visibility. This is what the hotkey does.
Returns
void
DebugOverlayOptions
Options accepted by createDebugOverlay.
Properties
backendName
ts
readonly backendName: string;Backend label, for example webgpu or webgl.
container?
ts
readonly optional container?: HTMLElement;Where to mount the panel. Defaults to document.body.
extra?
ts
readonly optional extra?: () => string;A source for one more line (or several, joined by '\n') under the built-in ones, such as the net module's room line. Called only when the panel redraws, never per frame. Can also be set later with setExtra.
Returns
string
hotkey?
ts
readonly optional hotkey?: string;KeyboardEvent.code that toggles visibility. Defaults to F3.
intervalMs?
ts
readonly optional intervalMs?: number;How often the text is rewritten, in milliseconds. Defaults to 250.
renderer
ts
readonly renderer: WebGPURenderer;Renderer whose info.render counters are shown.
samples?
ts
readonly optional samples?: number;Frames in the percentile window. Defaults to 120.
visible?
ts
readonly optional visible?: boolean;Whether the panel starts visible. Defaults to true.
Engine
A booted engine.
Properties
assets
ts
readonly assets: AssetRegistry;The asset registry.
camera
ts
readonly camera: PerspectiveCamera;The camera being rendered from.
ctx
ts
readonly ctx: EngineContext;The module context, for code that is not a module.
events
ts
readonly events: Events<EngineEventMap>;The engine event bus.
graph
ts
readonly graph: SceneGraph;Entity-id to Object3D mapping, rooted in the scene.
loop
ts
readonly loop: FixedLoop;The fixed-step loop.
modules
ts
readonly modules: ModuleRegistry;The module registry.
overlay
ts
readonly overlay: DebugOverlay | null;The debug overlay, when debug was set.
renderer
ts
readonly renderer: WebGPURenderer;The initialised renderer.
running
ts
readonly running: boolean;Whether the loop is running.
scene
ts
readonly scene: Scene;The scene being rendered.
Methods
dispose()
ts
dispose(): Promise<void>;Stop, dispose every module in reverse order, then the renderer.
Returns
Promise<void>
Resolves once the renderer has released its device.
get()
ts
get<K>(id): EngineServices[K];Look up a module's service.
Type Parameters
| Type Parameter |
|---|
K extends "physics" |
Parameters
| Parameter | Type | Description |
|---|---|---|
id | K | Service id, typed through EngineServices. |
Returns
EngineServices[K]
The service.
resize()
ts
resize(width, height): void;Resize the drawing buffer and the camera.
Called automatically by the canvas ResizeObserver; call it yourself only when you manage the canvas size some other way.
Parameters
| Parameter | Type | Description |
|---|---|---|
width | number | CSS width in pixels. |
height | number | CSS height in pixels. |
Returns
void
start()
ts
start(): void;Start the loop.
Returns
void
stop()
ts
stop(): void;Stop the loop. Modules keep their resources; start resumes.
Returns
void
EngineCaps
What the host turned out to be able to do, decided once after renderer.init().
Properties
characters
ts
readonly characters: boolean;True when splat characters can run.
Characters need compute shaders and a shared GPUDevice, so this tracks webgpu exactly. Check it before calling createCharacter. Always false on a headless host.
webgpu
ts
readonly webgpu: boolean;True when the renderer got a real WebGPU backend rather than the WebGL fallback. Always false on a headless host.
EngineConfig
The engine options after defaults were applied.
Properties
antialias
ts
readonly antialias: boolean;Whether MSAA was requested.
backend
ts
readonly backend: RendererBackend;Backend that was requested (not necessarily the one that was obtained).
debug
ts
readonly debug: boolean;Whether the debug overlay was created.
fixedDt
ts
readonly fixedDt: number;Length of one fixed step, in seconds: 1 / fixedHz.
fixedHz
ts
readonly fixedHz: number;Simulation rate in hertz.
headless
ts
readonly headless: boolean;True when the host has no renderer, scene or camera (a server tick).
maxSubsteps
ts
readonly maxSubsteps: number;Most fixed steps one frame may run.
pixelRatioCap
ts
readonly pixelRatioCap: number;Upper bound applied to devicePixelRatio.
EngineContext
A host that renders: the scene, the camera and the renderer on top of HostContext.
Example
ts
import { requireRenderContext, type HostContext } from 'gameable/core';
export function init(host: HostContext) {
const ctx = requireRenderContext(host, 'splat');
ctx.scene.add(ctx.camera);
}Extends
Properties
assets
ts
readonly assets: AssetRegistry;The asset registry built from the manifest.
Inherited from
camera
ts
readonly camera: PerspectiveCamera;The camera the engine renders with. Camera rigs drive this.
caps
ts
readonly caps: EngineCaps;What this host can actually do.
Inherited from
config
ts
readonly config: EngineConfig;The resolved engine options.
Inherited from
events
ts
readonly events: Events<EngineEventMap>;The engine event bus.
Inherited from
renderer
ts
readonly renderer: WebGPURenderer;The initialised renderer. Its backend.device is the one shared GPU device.
scene
ts
readonly scene: Scene;The scene the engine renders.
time
ts
readonly time: Time;The engine clock.
Inherited from
Methods
get()
ts
get<K>(id): EngineServices[K];Look up another module's service.
Only valid after that module's init has run, which order controls.
Type Parameters
| Type Parameter |
|---|
K extends "physics" |
Parameters
| Parameter | Type | Description |
|---|---|---|
id | K | Service id. |
Returns
EngineServices[K]
The service.
Inherited from
registerService()
ts
registerService(id, service): void;Publish a service under an id, so other modules can get it.
Equivalent to returning the service from init; use this when a module exposes something before it has finished initialising, or exposes nothing but wants to register under a different id.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Service id, matching a key of EngineServices. |
service | object | The service object. |
Returns
void
Inherited from
EngineEventMap
Events the engine itself publishes.
Other packages add their own by declaration merging, exactly as they do for EngineServices:
ts
declare module 'gameable/core' {
interface EngineEventMap {
'physics:contact': { a: number; b: number };
}
}Properties
engine:frame
ts
engine:frame: object;A frame finished, after rendering.
The payload object is reused every frame; read it in the handler and never retain it.
alpha
ts
alpha: number;dtReal
ts
dtReal: number;frame
ts
frame: number;substeps
ts
substeps: number;engine:resize
ts
engine:resize: object;The drawing buffer changed size.
height
ts
height: number;width
ts
width: number;engine:start
ts
engine:start: undefined;The loop has started.
engine:stop
ts
engine:stop: undefined;The loop has stopped.
EngineModule
One host subsystem.
init runs once, in order. dispose runs in reverse order and must release everything the module took. The per-frame hooks are optional and are called only on the modules that implement them, so an empty hook costs nothing.
Nothing in beginFrame, fixedUpdate, update or endFrame may allocate: they run at least 60 times a second.
Extended by
Properties
id
ts
readonly id: string;Unique id. Also the service id when init returns a service.
order?
ts
readonly optional order?: number;Sort key. Lower runs first; equal keys keep registration order.
Rough convention: input -100, gameplay -50, physics 0, rendering helpers 200. Gameplay runs before physics so that the commands a guest tick emits are simulated by the step that follows rather than the next one.
Methods
beginFrame()?
ts
optional beginFrame(): void;Run before the frame's fixed steps. Input capture lives here.
Returns
void
dispose()
ts
dispose(): void;Release everything this module took.
Returns
void
endFrame()?
ts
optional endFrame(): void;Run after rendering. Input's end-of-frame bookkeeping lives here.
Returns
void
fixedUpdate()?
ts
optional fixedUpdate(dt): void;Run once per fixed step.
Parameters
| Parameter | Type | Description |
|---|---|---|
dt | number | Always ctx.config.fixedDt, whatever time.timeScale is; time scale changes how many steps a frame runs, not their length. |
Returns
void
init()
ts
init(ctx): void | object | Promise<void | object>;Acquire resources.
Parameters
| Parameter | Type | Description |
|---|---|---|
ctx | HostContext | The host surface. |
Returns
void | object | Promise<void | object>
Nothing, or the service to publish under id.
update()?
ts
optional update(dt, alpha): void;Run once per frame, after the fixed steps and before rendering.
Parameters
| Parameter | Type | Description |
|---|---|---|
dt | number | Clamped wall-clock seconds since the previous frame, scaled by time.timeScale. |
alpha | number | Interpolation factor in [0, 1) for smoothing transforms. |
Returns
void
EngineRendererOptions
Renderer options accepted by createEngine.
Properties
antialias?
ts
readonly optional antialias?: boolean;Request MSAA. Defaults to true.
backend?
ts
readonly optional backend?: RendererBackend;Which backend to target.
auto takes WebGPU when the browser has it and falls back to WebGL otherwise. webgpu refuses to boot without it. webgl forces the fallback, which is useful for reproducing what a WebGL player sees — characters are unavailable there.
pixelRatioCap?
ts
readonly optional pixelRatioCap?: number;Upper bound on devicePixelRatio. Defaults to 2.
Events
Typed pub/sub over the event map M.
Type Parameters
| Type Parameter |
|---|
M extends EventMap |
Methods
clear()
ts
clear(): void;Drop every handler.
Returns
void
emit()
ts
emit<K>(type, payload): void;Publish an event.
Type Parameters
| Type Parameter |
|---|
K extends string | number | symbol |
Parameters
| Parameter | Type | Description |
|---|---|---|
type | K | Event name. |
payload | M[K] | Payload, matching the map. |
Returns
void
listenerCount()
ts
listenerCount(type): number;How many handlers are subscribed to an event.
Parameters
| Parameter | Type | Description |
|---|---|---|
type | keyof M | Event name. |
Returns
number
The live listener count.
off()
ts
off<K>(type, listener): void;Unsubscribe a handler.
Type Parameters
| Type Parameter |
|---|
K extends string | number | symbol |
Parameters
| Parameter | Type | Description |
|---|---|---|
type | K | Event name. |
listener | Listener<M[K]> | The exact function passed to on. |
Returns
void
on()
ts
on<K>(type, listener): () => void;Subscribe to an event.
Type Parameters
| Type Parameter |
|---|
K extends string | number | symbol |
Parameters
| Parameter | Type | Description |
|---|---|---|
type | K | Event name. |
listener | Listener<M[K]> | Handler. |
Returns
An unsubscribe function, so callers need not keep the reference.
() => void
once()
ts
once<K>(type, listener): () => void;Subscribe to the next occurrence only.
Type Parameters
| Type Parameter |
|---|
K extends string | number | symbol |
Parameters
| Parameter | Type | Description |
|---|---|---|
type | K | Event name. |
listener | Listener<M[K]> | Handler. |
Returns
An unsubscribe function.
() => void
FirstPersonRig
A first-person camera rig.
Properties
camera
ts
readonly camera: PerspectiveCamera;The camera being driven.
eyeHeight
ts
eyeHeight: number;Metres from the pose position up to the eyes. Writable: crouching changes it.
pitch
ts
readonly pitch: number;Last applied pitch, in radians, after clamping.
yaw
ts
readonly yaw: number;Last applied yaw, in radians.
Methods
setPose()
ts
setPose(
position,
yaw,
pitch
): void;Place the camera.
position is the body's position — feet, or capsule base — not the eyes; eyeHeight is added to y.
Parameters
| Parameter | Type | Description |
|---|---|---|
position | Vector3Like | Body position in world space. |
yaw | number | Rotation about world +Y, in radians. 0 looks down −Z. |
pitch | number | Rotation about the camera's local +X, in radians. Positive looks up. |
Returns
void
FirstPersonRigOptions
Options accepted by createFirstPersonRig.
Properties
camera
ts
readonly camera: PerspectiveCamera;The camera to drive.
eyeHeight?
ts
readonly optional eyeHeight?: number;Metres from the given position up to the eyes. Defaults to 1.7.
maxPitch?
ts
readonly optional maxPitch?: number;Pitch is clamped to plus or minus this, in radians.
FixedLoop
A driven fixed-step accumulator.
Properties
accumulator
ts
readonly accumulator: number;Unconsumed simulation time, always in [0, fixedDt) after a step.
alpha
ts
readonly alpha: number;Interpolation factor for the last frame, in [0, 1).
fixedDt
ts
readonly fixedDt: number;Length of one fixed step, in seconds.
frame
ts
readonly frame: number;Frames stepped so far.
maxSubsteps
ts
readonly maxSubsteps: number;Most fixed steps one frame may run.
timeScale
ts
timeScale: number;Simulation speed multiplier. 1 is real time, 0.5 is half speed, 0 pauses the simulation while frames keep rendering.
Scales how much simulation time a frame buys, not the step length: a fixed step is always fixedDt long, so physics and the game see the same dt at every speed and a recording replays at any speed. Negative values are treated as 0.
Methods
reset()
ts
reset(): void;Forget the accumulator and the baseline timestamp.
Call after a long pause (tab hidden, breakpoint, level load) so the next step starts clean instead of clamping.
Returns
void
step()
ts
step(nowMs): FrameTiming;Advance the loop to nowMs.
The very first call only establishes the baseline: dtReal is 0 and no fixed step runs, so a slow boot cannot manufacture a burst of simulation.
Parameters
| Parameter | Type | Description |
|---|---|---|
nowMs | number | A monotonic timestamp in milliseconds, usually from rAF. |
Returns
What this frame did. The same record every call; do not retain it.
FixedLoopOptions
Options accepted by createFixedLoop.
Properties
fixedDt?
ts
readonly optional fixedDt?: number;Length of one fixed step, in seconds. Defaults to 1/60.
fixedUpdate?
ts
readonly optional fixedUpdate?: (dt) => void;Run once per fixed step, always with exactly fixedDt.
Parameters
| Parameter | Type | Description |
|---|---|---|
dt | number | Always fixedDt, whatever timeScale is. |
Returns
void
maxSubsteps?
ts
readonly optional maxSubsteps?: number;Most fixed steps one frame may run. Defaults to 5.
render?
ts
readonly optional render?: (alpha) => void;Run once per frame, last.
Parameters
| Parameter | Type | Description |
|---|---|---|
alpha | number | Interpolation factor in [0, 1). |
Returns
void
update?
ts
readonly optional update?: (dtReal, alpha) => void;Run once per frame, after the fixed steps.
Parameters
| Parameter | Type | Description |
|---|---|---|
dtReal | number | Clamped wall-clock seconds since the previous frame. Not scaled by timeScale; presentation code decides for itself. |
alpha | number | Interpolation factor in [0, 1). |
Returns
void
FrameStats
A rolling window of frame durations.
Properties
capacity
ts
readonly capacity: number;Frames the window holds.
count
ts
readonly count: number;Samples recorded so far, capped at capacity.
fps
ts
readonly fps: number;Smoothed frames per second.
last
ts
readonly last: number;Duration of the most recent frame, in milliseconds.
Methods
percentile()
ts
percentile(p): number;A percentile of the window.
Parameters
| Parameter | Type | Description |
|---|---|---|
p | number | Percentile in [0, 1]; 0.5 is the median. |
Returns
number
The frame time in milliseconds, or 0 before any sample.
push()
ts
push(frameMs): void;Record one frame.
Parameters
| Parameter | Type | Description |
|---|---|---|
frameMs | number | How long the frame took, in milliseconds. |
Returns
void
reset()
ts
reset(): void;Forget every sample.
Returns
void
FrameTiming
What a call to FixedLoop.step did.
The loop hands back the same record every frame. Read it before the next step; never retain it.
Properties
alpha
ts
readonly alpha: number;Interpolation factor for rendering, always in [0, 1).
clamped
ts
readonly clamped: boolean;True when time had to be thrown away to avoid a death spiral.
dtReal
ts
readonly dtReal: number;Wall-clock seconds since the previous frame, after clamping.
rawDt
ts
readonly rawDt: number;Wall-clock seconds since the previous frame, before clamping.
substeps
ts
readonly substeps: number;Fixed steps run this frame, 0 to maxSubsteps.
HeadlessEngine
An engine with no renderer.
Properties
assets
ts
readonly assets: AssetRegistry;The asset registry.
ctx
ts
readonly ctx: HostContext;The module context: a HostContext, with no scene, camera or renderer.
events
ts
readonly events: Events<EngineEventMap>;The engine event bus.
loop
ts
readonly loop: FixedLoop;The fixed-step loop.
modules
ts
readonly modules: ModuleRegistry;The module registry.
running
ts
readonly running: boolean;Whether the timer is running.
time
ts
readonly time: Time;The engine clock; the same object as ctx.time.
Methods
dispose()
ts
dispose(): Promise<void>;Stop, then dispose modules in reverse order, the assets and the listeners.
Returns
Promise<void>
Resolves once everything is released.
get()
ts
get<K>(id): EngineServices[K];Look up a module's service.
Type Parameters
| Type Parameter |
|---|
K extends "physics" |
Parameters
| Parameter | Type | Description |
|---|---|---|
id | K | Service id, typed through EngineServices. |
Returns
EngineServices[K]
The service.
start()
ts
start(): void;Tick on a timer, aiming each step at its due time (no drift).
Returns
void
step()
ts
step(nowMs): FrameTiming;Advance by hand.
Parameters
| Parameter | Type | Description |
|---|---|---|
nowMs | number | A monotonic wall clock in milliseconds; the first call is the baseline. |
Returns
What the frame did. The same record every call; do not retain it.
stop()
ts
stop(): void;Stop the timer. Modules keep their resources; start resumes.
Returns
void
HostContext
What every host has, with or without a renderer.
Example
ts
import 'gameable/physics';
import type { HostContext } from 'gameable/core';
export function init(ctx: HostContext) {
const physics = ctx.get('physics');
console.log(ctx.time.fixedDt, physics);
}Extended by
Properties
assets
ts
readonly assets: AssetRegistry;The asset registry built from the manifest.
caps
ts
readonly caps: EngineCaps;What this host can actually do.
config
ts
readonly config: EngineConfig;The resolved engine options.
events
ts
readonly events: Events<EngineEventMap>;The engine event bus.
time
ts
readonly time: Time;The engine clock.
Methods
get()
ts
get<K>(id): EngineServices[K];Look up another module's service.
Only valid after that module's init has run, which order controls.
Type Parameters
| Type Parameter |
|---|
K extends "physics" |
Parameters
| Parameter | Type | Description |
|---|---|---|
id | K | Service id. |
Returns
EngineServices[K]
The service.
registerService()
ts
registerService(id, service): void;Publish a service under an id, so other modules can get it.
Equivalent to returning the service from init; use this when a module exposes something before it has finished initialising, or exposes nothing but wants to register under a different id.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Service id, matching a key of EngineServices. |
service | object | The service object. |
Returns
void
LoadedFeature
What a loader hands back.
Example
ts
import type { LoadedFeature } from 'gameable/core';
const feature: LoadedFeature = {
name: 'characters',
modules: [],
bind: () => Promise.resolve('ready'),
};
console.log(feature.name); // 'characters'Properties
modules
ts
readonly modules: readonly EngineModule[];Modules to register with createEngine. May be empty.
name
ts
readonly name: string;The feature's name, as declared.
Methods
bind()?
ts
optional bind(engine): Promise<unknown>;Work that needs the booted engine (a bridge that wants the renderer, say).
A feature that needs a renderer narrows with isRenderEngine and throws a FeatureError when it is bound to a headless engine.
Parameters
| Parameter | Type | Description |
|---|---|---|
engine | Engine | HeadlessEngine | The booted engine, with or without a renderer. |
Returns
Promise<unknown>
Whatever the feature wants the application to hold, keyed by name in bindFeatures.
ModuleRegistry
Holds modules, orders them, initialises them and takes them down again.
Properties
beginFrameHooks
ts
readonly beginFrameHooks: readonly EngineModule[];Modules implementing beginFrame, in run order.
endFrameHooks
ts
readonly endFrameHooks: readonly EngineModule[];Modules implementing endFrame, in run order.
fixedUpdateHooks
ts
readonly fixedUpdateHooks: readonly EngineModule[];Modules implementing fixedUpdate, in run order.
modules
ts
readonly modules: readonly EngineModule[];Registered modules in run order.
updateHooks
ts
readonly updateHooks: readonly EngineModule[];Modules implementing update, in run order.
Methods
disposeAll()
ts
disposeAll(): void;Dispose every initialised module in reverse order.
A throwing dispose is collected and rethrown at the end, so one bad module cannot strand the others.
Returns
void
get()
ts
get<K>(id): EngineServices[K];Look up a service, typed through EngineServices.
Type Parameters
| Type Parameter |
|---|
K extends "physics" |
Parameters
| Parameter | Type | Description |
|---|---|---|
id | K | Service id. |
Returns
EngineServices[K]
The service.
has()
ts
has(id): boolean;Whether a service is registered under an id.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Service id. |
Returns
boolean
True when get would succeed.
initAll()
ts
initAll(ctx): Promise<void>;Initialise every module in order, awaiting each one.
Modules are initialised sequentially, not in parallel, because order is a dependency order: a module may ctx.get anything registered before it.
Parameters
| Parameter | Type | Description |
|---|---|---|
ctx | HostContext | The host surface. |
Returns
Promise<void>
Resolves once every module has initialised.
register()
ts
register(module): void;Add a module. Must happen before ModuleRegistry.initAll.
Parameters
| Parameter | Type | Description |
|---|---|---|
module | EngineModule | The module. |
Returns
void
registerService()
ts
registerService(id, service): void;Publish a service under an id.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Service id. |
service | object | The service object. |
Returns
void
tryGet()
ts
tryGet(id): unknown;Look up a service without the typed table.
The escape hatch for code that does not know the id at compile time, and for core itself, which must not know what modules exist.
Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Service id. |
Returns
unknown
The service, or undefined when nothing is registered.
MutableTime
The engine's own handle on the clock: the same object, writable.
Extends
Properties
elapsed
ts
elapsed: number;Scaled seconds simulated since start().
Overrides
fixedDt
ts
fixedDt: number;Length of one fixed step, in seconds.
Overrides
now
ts
now: number;performance.now() of the current frame, in milliseconds.
Overrides
renderFrame
ts
renderFrame: number;Frames rendered since start(), starting at 0.
Rendered frames, not fixed steps: on a 144 Hz display this runs ahead of the simulation's own step counter, which the guest sees as its frame.
Overrides
timeScale
ts
timeScale: number;Simulation speed multiplier. 0 pauses, 0.5 is half speed.
Changes how many fixed steps a frame buys, never how long a step is: a fixed step is always fixedDt.
Inherited from
PatchableAdapterPrototype
The parts of GPUAdapter the patch touches.
Methods
requestDevice()
ts
requestDevice(descriptor?): Promise<GPUDevice>;The method being wrapped.
Parameters
| Parameter | Type | Description |
|---|---|---|
descriptor? | GPUDeviceDescriptor | Device descriptor, as the caller wrote it. |
Returns
Promise<GPUDevice>
The requested device.
SrgbPass
One attachment of the sRGB splat pass to a scene.
Properties
composite
ts
readonly composite: Mesh;The mesh that lays the splats over the frame (a child of the scene).
Methods
begin()
ts
begin(camera): void;Draw this frame's depth pre-pass and splats for camera, into the renderer's current render target's size, and lay them over the next renderer.render(scene, camera). Call it just before that render, with end() just after (in the same task: a frame left open is closed once the current task's code has run). Not needed with the scene hook, which does the same; with it, the hook then leaves the render to this frame.
Parameters
| Parameter | Type | Description |
|---|---|---|
camera | Camera | The camera the app is about to render the scene with. |
Returns
void
Nothing.
dispose()
ts
dispose(): void;Release this attachment. The pass comes off the scene (its hooks, its mesh, its targets, its depth materials) when every attachment to it has been released.
Returns
void
Nothing.
end()
ts
end(): void;Close the frame begin opened: the splats go back on their own layers and the composite away. Does nothing when no frame is open.
Returns
void
Nothing.
SrgbPassOptions
How attachSrgbPass joins the app's renders.
Properties
hook?
ts
readonly optional hook?: boolean;True (the default): the pass hooks the scene's onBeforeRender / onAfterRender, and every renderer.render(scene, camera) draws the splats with no change to the app's loop. False: no hook; the app calls begin(camera) just before its render and end() just after. For an app that assigns scene.onBeforeRender itself, or renders the scene through something that bypasses the hook.
ThirdPersonRig
A third-person orbit camera with a collision-aware spring arm.
Properties
actualDistance
ts
readonly actualDistance: number;Arm length actually used last frame, after collision.
camera
ts
readonly camera: PerspectiveCamera;The camera being driven.
collisionProbe
ts
collisionProbe: CollisionProbe | null;Obstacle probe. Assign to swap it at runtime, or null to disable collision.
distance
ts
readonly distance: number;Requested arm length, before collision.
pitch
ts
readonly pitch: number;Last applied pitch, in radians, after clamping.
pivotHeight
ts
pivotHeight: number;Height of the orbit pivot above the target.
yaw
ts
readonly yaw: number;Last applied yaw, in radians.
Methods
apply()
ts
apply(): void;Re-run the probe and reposition the camera with the current target and orbit.
Both setters call this; you only need it when the world changed but the inputs did not.
Returns
void
setOrbit()
ts
setOrbit(
yaw,
pitch,
distance
): void;Set the orbit and apply the result.
Parameters
| Parameter | Type | Description |
|---|---|---|
yaw | number | Rotation about world +Y, in radians. 0 puts the camera on +Z. |
pitch | number | Rotation above the horizon, in radians. Positive looks down at the target. |
distance | number | Requested arm length, in metres. |
Returns
void
setTarget()
ts
setTarget(position): void;Point the rig at a target and apply the result.
Parameters
| Parameter | Type | Description |
|---|---|---|
position | Vector3Like | The target's world position, at its feet. |
Returns
void
setTargetAndOrbit()
ts
setTargetAndOrbit(
position,
yaw,
pitch,
distance
): void;Set the target and the orbit together and apply once.
The per-frame entry point: one probe, one camera write, instead of the two that calling setTarget then setOrbit would cost.
Parameters
| Parameter | Type | Description |
|---|---|---|
position | Vector3Like | The target's world position, at its feet. |
yaw | number | Rotation about world +Y, in radians. |
pitch | number | Rotation above the horizon, in radians. |
distance | number | Requested arm length, in metres. |
Returns
void
ThirdPersonRigOptions
Options accepted by createThirdPersonRig.
Properties
camera
ts
readonly camera: PerspectiveCamera;The camera to drive.
collisionPadding?
ts
readonly optional collisionPadding?: number;Gap left between camera and obstacle. Defaults to 0.15.
collisionProbe?
ts
readonly optional collisionProbe?: CollisionProbe;Obstacle probe. Omit for a rig that never collides.
maxPitch?
ts
readonly optional maxPitch?: number;Pitch is clamped to plus or minus this, in radians.
minDistance?
ts
readonly optional minDistance?: number;Closest the arm may pull in. Defaults to 0.4.
pivotHeight?
ts
readonly optional pivotHeight?: number;Height of the orbit pivot above the target position. Defaults to 1.5.
Time
Read-only view of the engine clock, as modules and game code see it.
Extended by
Properties
elapsed
ts
readonly elapsed: number;Scaled seconds simulated since start().
fixedDt
ts
readonly fixedDt: number;Length of one fixed step, in seconds.
now
ts
readonly now: number;performance.now() of the current frame, in milliseconds.
renderFrame
ts
readonly renderFrame: number;Frames rendered since start(), starting at 0.
Rendered frames, not fixed steps: on a 144 Hz display this runs ahead of the simulation's own step counter, which the guest sees as its frame.
timeScale
ts
timeScale: number;Simulation speed multiplier. 0 pauses, 0.5 is half speed.
Changes how many fixed steps a frame buys, never how long a step is: a fixed step is always fixedDt.
Vector3Like
Anything with x, y and z. Avoids allocating a Vector3 per call.
Properties
x
ts
readonly x: number;X component.
y
ts
readonly y: number;Y component.
z
ts
readonly z: number;Z component.
WebGPUPatchOptions
Options accepted by initWebGPUPatches.
Properties
maxStorageBuffersPerShaderStage?
ts
readonly optional maxStorageBuffersPerShaderStage?: number;Storage buffers per shader stage to ask for.
The patch requests min(this, adapter.limits.maxStorageBuffersPerShaderStage), so asking for more than the adapter has is harmless. Defaults to DEFAULT_MAX_STORAGE_BUFFERS_PER_SHADER_STAGE.
target?
ts
readonly optional target?: PatchableAdapterPrototype;Prototype to patch. Defaults to the global GPUAdapter.prototype.
Tests pass a fake here; production never sets it.
timestampQuery?
ts
readonly optional timestampQuery?: boolean;Also request the timestamp-query feature when the adapter has it.
Needed to time GPU passes in the debug overlay and the benchmarks. Off by default, because the feature has a cost.
Type Aliases
CollisionProbe
ts
type CollisionProbe = (from, to) => number | null;Asks how far a ray gets before it hits something.
Parameters
| Parameter | Type | Description |
|---|---|---|
from | Vector3Like | Start of the ray, the orbit pivot. |
to | Vector3Like | Where the camera would like to be. |
Returns
number | null
Distance from from to the hit, in metres, or null for a clear line.
EventMap
ts
type EventMap = object;Shape of an event map: an interface mapping event name to payload type.
Deliberately object rather than Record<string, unknown>: interfaces have no implicit index signature, and the whole point of EngineEventMap is that packages declaration-merge into it.
FeatureLoader
ts
type FeatureLoader = (options) => Promise<LoadedFeature>;Loads one feature.
Parameters
| Parameter | Type |
|---|---|
options | Readonly<Record<string, unknown>> |
Returns
Promise<LoadedFeature>
Example
ts
import type { FeatureLoader } from 'gameable/core';
const characters: FeatureLoader = () => Promise.resolve({ name: 'characters', modules: [] });
console.log(typeof characters); // 'function'FeatureOptions
ts
type FeatureOptions = Readonly<Record<string, Readonly<Record<string, unknown>>>>;Declared features: name to options. Mirrors FeatureOptions in gameable.
Example
ts
import type { FeatureOptions } from 'gameable/core';
const declared: FeatureOptions = { characters: {}, multiplayer: { maxPlayers: 6 } };
console.log(Object.keys(declared)); // ['characters', 'multiplayer']FeatureTable
ts
type FeatureTable = Readonly<Record<string, FeatureLoader>>;Name to loader. One table per side (page, server).
Example
ts
import type { FeatureTable } from 'gameable/core';
const table: FeatureTable = {
characters: () => Promise.resolve({ name: 'characters', modules: [] }),
};
console.log(Object.keys(table)); // ['characters']Listener
ts
type Listener<T> = (payload) => void;A handler for one event.
Type Parameters
| Type Parameter |
|---|
T |
Parameters
| Parameter | Type |
|---|---|
payload | T |
Returns
void
RendererBackend
ts
type RendererBackend = "auto" | "webgpu" | "webgl";Which three.js backend the renderer should target.
SrgbPassMember
ts
type SrgbPassMember = Object3D & object;An object the sRGB pass draws: its srgbOutput is 1 while it does.
Type Declaration
srgbOutput
ts
readonly srgbOutput: object;srgbOutput.value
ts
value: number;Variables
DEFAULT_COLLISION_PADDING
ts
const DEFAULT_COLLISION_PADDING: 0.15 = 0.15;Default gap left between the camera and whatever the probe hit, in metres.
DEFAULT_EYE_HEIGHT
ts
const DEFAULT_EYE_HEIGHT: 1.7 = 1.7;Default eye height above the feet, in metres.
DEFAULT_FIXED_DT
ts
const DEFAULT_FIXED_DT: number;Default simulation rate: 60 Hz.
DEFAULT_MAX_PITCH
ts
const DEFAULT_MAX_PITCH: number;Default pitch limit: just short of straight up or down, in radians.
DEFAULT_MAX_STORAGE_BUFFERS_PER_SHADER_STAGE
ts
const DEFAULT_MAX_STORAGE_BUFFERS_PER_SHADER_STAGE: 10 = 10;Storage buffers per shader stage the engine asks for. The WebGPU default is 8.
DEFAULT_MAX_SUBSTEPS
ts
const DEFAULT_MAX_SUBSTEPS: 5 = 5;Default cap on fixed steps per frame.
DEFAULT_MIN_DISTANCE
ts
const DEFAULT_MIN_DISTANCE: 0.4 = 0.4;Default closest the arm may pull in, in metres.
DEFAULT_OVERLAY_HOTKEY
ts
const DEFAULT_OVERLAY_HOTKEY: "F3" = 'F3';KeyboardEvent.code that toggles the panel.
DEFAULT_OVERLAY_INTERVAL_MS
ts
const DEFAULT_OVERLAY_INTERVAL_MS: 250 = 250;How often the panel's text is rewritten, in milliseconds.
DEFAULT_PIVOT_HEIGHT
ts
const DEFAULT_PIVOT_HEIGHT: 1.5 = 1.5;Default height of the orbit pivot above the target, in metres.
DEFAULT_PIXEL_RATIO_CAP
ts
const DEFAULT_PIXEL_RATIO_CAP: 2 = 2;Default upper bound on devicePixelRatio.
DEFAULT_SAMPLE_COUNT
ts
const DEFAULT_SAMPLE_COUNT: 120 = 120;Frames kept in the window by default: two seconds at 60 Hz.
DEFAULT_TRANSFORM_CAPACITY
ts
const DEFAULT_TRANSFORM_CAPACITY: 256 = 256;Default number of entity slots a new store reserves.
NO_ENTITY
ts
const NO_ENTITY: 0 = 0;The entity id that means "no entity"; also the id of the root group.
PACKAGE
ts
const PACKAGE: "@gameable/core";Package identity marker.
Example
ts
import { PACKAGE } from 'gameable/core';
console.log(PACKAGE); // 'gameable/core'SRGB_PASS_LAYER
ts
const SRGB_PASS_LAYER: 31 = 31;The layer the splat pass draws on: the splats alone, with no light or shadow work.
Functions
attachSrgbPass()
ts
function attachSrgbPass(
renderer,
scene,
options?
): SrgbPass;Attach the sRGB splat pass to a scene: from then on, renderer.render(scene, camera) draws the scene's sRGB splats blended on sRGB values, depth-tested against the scene. One pass per scene: a second attachment shares the first, and the pass comes off when every attachment has been disposed. gameable/three and the engine attach it themselves.
Each frame costs two extra renders of the scene before the app's: its opaque meshes, depth only, then the splats. With { hook: false } nothing is hooked: the app wraps its render in begin(camera) / end() itself.
Parameters
| Parameter | Type | Description |
|---|---|---|
renderer | WebGPURenderer | The initialised WebGPURenderer that draws the scene. |
scene | Scene | The scene. |
options | SrgbPassOptions | hook: false to call begin / end instead of hooking the scene. |
Returns
This attachment.
Example
ts
const pass = attachSrgbPass(renderer, scene);
renderer.setAnimationLoop(() => renderer.render(scene, camera));
// later: pass.dispose();
// An app that owns scene.onBeforeRender: no hook, begin and end around the render.
const manual = attachSrgbPass(renderer, scene, { hook: false });
renderer.setAnimationLoop(() => {
manual.begin(camera);
try {
renderer.render(scene, camera);
} finally {
manual.end();
}
});bindFeatures()
ts
function bindFeatures(loaded, engine): Promise<Map<string, unknown>>;Run every feature's bind against the booted engine.
Parameters
| Parameter | Type | Description |
|---|---|---|
loaded | readonly LoadedFeature[] | Features from resolveFeatures. |
engine | Engine | HeadlessEngine | The booted engine, with or without a renderer. |
Returns
Promise<Map<string, unknown>>
Bind results keyed by feature name; features without bind are absent.
Example
ts
import { bindFeatures, createEngine, resolveFeatures } from 'gameable/core';
const loaded = await resolveFeatures({ characters: {} }, {
characters: () => Promise.resolve({ name: 'characters', modules: [], bind: () => Promise.resolve('ready') }),
});
const engine = await createEngine({
canvas: document.querySelector('canvas')!,
modules: loaded.flatMap((f) => f.modules),
});
const bound = await bindFeatures(loaded, engine);
const characters = bound.get('characters');candleFlicker()
ts
function candleFlicker(seconds, index): number;Deterministic, continuous candle gain in [0.58, 1.2], with a steady core and shallow draught dips.
Parameters
| Parameter | Type | Description |
|---|---|---|
seconds | number | Elapsed scene time in seconds. |
index | number | Stable fixture seed; different fixtures flicker independently. |
Returns
number
Intensity multiplier; no random state or frame allocation.
Example
ts
light.intensity = 2.6 * candleFlicker(elapsed, 3);createDebugOverlay()
ts
function createDebugOverlay(options): DebugOverlay;Build the debug overlay.
On a host with no document — Node, a worker, a test — this returns a working overlay whose element is null: the statistics still accumulate and can be read, nothing is mounted, and nothing throws.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | DebugOverlayOptions | Renderer, backend label and panel settings. |
Returns
The overlay.
Example
ts
import { createDebugOverlay } from 'gameable/core';
import { WebGPURenderer } from 'three/webgpu';
const renderer = new WebGPURenderer({ canvas: document.createElement('canvas') });
const overlay = createDebugOverlay({ renderer, backendName: 'webgpu' });
overlay.sample(16.6, performance.now()); // call once per frame
overlay.setExtra(() => 'net joined KQTX'); // drawn under the built-in linescreateEngine()
ts
function createEngine(options): Promise<Engine>;Boot an engine around a canvas.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | CreateEngineOptions | Canvas, manifest, modules, loop rate and renderer settings. |
Returns
Promise<Engine>
The booted engine. The loop is not running; call start().
Example
ts
import { createEngine } from 'gameable/core';
const canvas = document.querySelector('canvas')!;
const engine = await createEngine({
canvas,
manifest: '/assets/assets.json',
modules: [],
fixedHz: 60,
renderer: { backend: 'auto' },
debug: import.meta.env?.DEV === true,
});
engine.start();createEvents()
ts
function createEvents<M>(): Events<M>;Build a typed event bus.
The type parameter is the event map. Packages extend the engine's map by declaration-merging into EngineEventMap; standalone buses pass their own.
Type Parameters
| Type Parameter |
|---|
M extends object |
Returns
Events<M>
An empty bus.
Example
ts
import { createEvents } from 'gameable/core';
const events = createEvents<{ hit: { damage: number } }>();
const off = events.on('hit', (e) => console.log(e.damage));
events.emit('hit', { damage: 7 }); // logs 7
off();createFirstPersonRig()
ts
function createFirstPersonRig(options): FirstPersonRig;Build a first-person rig.
The camera's Euler order is set to YXZ once, which is what makes yaw and pitch independent: yaw always turns about world up, pitch always about the camera's own right, and there is no roll.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | FirstPersonRigOptions | Camera, eye height and pitch limit. |
Returns
The rig.
Example
ts
import { createFirstPersonRig } from 'gameable/core';
import { PerspectiveCamera } from 'three/webgpu';
const rig = createFirstPersonRig({ camera: new PerspectiveCamera(), eyeHeight: 1.7 });
rig.setPose({ x: 0, y: 0, z: 0 }, Math.PI, 0);
console.log(rig.camera.position.y); // 1.7createFixedLoop()
ts
function createFixedLoop(options?): FixedLoop;Build a fixed-step loop.
The accumulator drains at most maxSubsteps times per frame. Anything left over is thrown away rather than carried, because carrying it is what turns a slow frame into a spiral of death: each frame owes more simulation than the last and the game never catches up. clamped in the returned FrameTiming says when that happened.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | FixedLoopOptions | Step length, substep cap and the three callbacks. |
Returns
The loop. Nothing runs until you call step.
Example
ts
import { createFixedLoop } from 'gameable/core';
let ticks = 0;
const loop = createFixedLoop({ fixedUpdate: () => { ticks += 1; } });
loop.step(0); // baseline: no fixed steps
loop.step(1000/30); // 33.3 ms buys two 60 Hz steps
console.log(ticks); // 2createFpsCounter()
ts
function createFpsCounter(engine, value): () => void;Count rendered frames in a supplied HUD element; update twice a second. Resets on tab visibility changes and returns an idempotent cleanup callback. The caller owns markup and styling; no overlay is created unless requested.
Parameters
| Parameter | Type | Description |
|---|---|---|
engine | Pick<EngineContext, "events"> | Host event source. |
value | HTMLElement | Element displaying only the numeric FPS value. |
Returns
Cleanup callback; call when disposing the owning module.
() => void
Example
ts
const dispose = createFpsCounter(engine, document.querySelector('#fps-value')!);createFrameStats()
ts
function createFrameStats(capacity?): FrameStats;Build a frame-time window.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
capacity | number | DEFAULT_SAMPLE_COUNT | Frames to keep. Defaults to 120. |
Returns
An empty window.
Example
ts
import { createFrameStats } from 'gameable/core';
const stats = createFrameStats(4);
for (const ms of [10, 20, 30, 40]) stats.push(ms);
console.log(stats.percentile(0.5)); // 20createHeadlessEngine()
ts
function createHeadlessEngine(options?): Promise<HeadlessEngine>;Boot an engine with no renderer.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | CreateHeadlessEngineOptions | Manifest, modules and the loop rate. |
Returns
Promise<HeadlessEngine>
The engine; call start() or drive step() yourself.
Example
ts
import { createHeadlessEngine } from 'gameable/core/headless';
const engine = await createHeadlessEngine({ modules: [], fixedHz: 60 });
engine.step(0);
engine.step(1000 / 60); // one fixed step
console.log(engine.time.elapsed); // 0.016666…
await engine.dispose();createModuleRegistry()
ts
function createModuleRegistry(): ModuleRegistry;Build a module registry.
Returns
An empty registry.
Example
ts
import { createModuleRegistry } from 'gameable/core';
const registry = createModuleRegistry();
registry.register({ id: 'clock', order: -10, init: () => ({ t: 0 }), dispose: () => {} });
console.log(registry.modules[0]?.id); // 'clock'createThirdPersonRig()
ts
function createThirdPersonRig(options): ThirdPersonRig;Build a third-person rig.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | ThirdPersonRigOptions | Camera, pivot height, arm limits and the collision probe. |
Returns
The rig.
Example
ts
import { createThirdPersonRig } from 'gameable/core';
import { PerspectiveCamera } from 'three/webgpu';
const rig = createThirdPersonRig({ camera: new PerspectiveCamera(), pivotHeight: 1.5 });
rig.setTarget({ x: 0, y: 0, z: 0 });
rig.setOrbit(0, 0, 4);
console.log(rig.camera.position.z); // 4createTime()
ts
function createTime(fixedDt): MutableTime;Build an engine clock.
Parameters
| Parameter | Type | Description |
|---|---|---|
fixedDt | number | Length of one fixed step, in seconds. |
Returns
A clock at render frame 0, elapsed 0, timeScale 1.
Example
ts
import { createTime } from 'gameable/core';
const time = createTime(1 / 60);
time.timeScale = 0.5; // slow motion
console.log(time.fixedDt); // 0.016666…initWebGPUPatches()
ts
function initWebGPUPatches(options?): boolean;Patch GPUAdapter.prototype.requestDevice so every device the page creates clears the engine's limits.
Idempotent. Calling it again does not wrap the method twice; it raises the requested limits if the new call asks for more. Safe to call on a host with no WebGPU at all, where it does nothing and returns false.
Must run before renderer.init(), and before anything else creates a device.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | WebGPUPatchOptions | Limits to request, and the prototype to patch. |
Returns
boolean
True when the patch is installed, false when the host has no WebGPU.
Example
ts
import { initWebGPUPatches } from 'gameable/core';
import { WebGPURenderer } from 'three/webgpu';
initWebGPUPatches({ maxStorageBuffersPerShaderStage: 10 });
const renderer = new WebGPURenderer({ canvas: document.createElement('canvas') });
await renderer.init();isRenderEngine()
ts
function isRenderEngine(engine): engine is Engine;Whether an engine has a renderer (it came from createEngine, not createHeadlessEngine).
Parameters
| Parameter | Type | Description |
|---|---|---|
engine | Engine | HeadlessEngine | Either kind of engine. |
Returns
engine is Engine
true for an engine with a scene, camera and renderer.
Example
ts
import { createHeadlessEngine, isRenderEngine } from 'gameable/core';
const engine = await createHeadlessEngine();
console.log(isRenderEngine(engine)); // false
await engine.dispose();isSrgbPassMember()
ts
function isSrgbPassMember(object): object is SrgbPassMember;Whether an object is drawn by the sRGB pass.
Parameters
| Parameter | Type | Description |
|---|---|---|
object | Object3D | Any object in the scene. |
Returns
object is SrgbPassMember
True when it carries an srgbOutput uniform.
Example
ts
isSrgbPassMember(character.object3D.children[0]);isWebGPUBackend()
ts
function isWebGPUBackend(renderer): boolean;Whether a renderer ended up on a real WebGPU backend.
three sets isWebGPUBackend on WebGPUBackend and isWebGLBackend on the fallback, but neither class is exported from three/webgpu, so this is a duck-type check rather than an instanceof.
Parameters
| Parameter | Type | Description |
|---|---|---|
renderer | WebGPURenderer | An initialised renderer. |
Returns
boolean
True on WebGPU, false on the WebGL fallback.
Example
ts
import { isWebGPUBackend } from 'gameable/core';
import { WebGPURenderer } from 'three/webgpu';
const renderer = new WebGPURenderer({ canvas: document.createElement('canvas') });
await renderer.init();
console.log(isWebGPUBackend(renderer));isWebGPUPatched()
ts
function isWebGPUPatched(target?): boolean;Whether a prototype already carries the patch.
Parameters
| Parameter | Type | Description |
|---|---|---|
target? | PatchableAdapterPrototype | Prototype to check. Defaults to the global GPUAdapter.prototype. |
Returns
boolean
True when initWebGPUPatches has run against it.
Example
ts
import { initWebGPUPatches, isWebGPUPatched } from 'gameable/core';
initWebGPUPatches();
console.log(isWebGPUPatched()); // true in a browser with WebGPUrequireRenderContext()
ts
function requireRenderContext(ctx, moduleId): EngineContext;Assert a module is running under a renderer.
Parameters
| Parameter | Type | Description |
|---|---|---|
ctx | HostContext | The context a module was handed. |
moduleId | string | The module asking, for the error message. |
Returns
The same context, typed as rendering.
Throws
When the host is headless.
Example
ts
import { requireRenderContext, type HostContext } from 'gameable/core';
declare const host: HostContext;
try {
const { scene } = requireRenderContext(host, 'splat');
console.log(scene.children.length);
} catch (error) {
console.log('headless host:', (error as Error).message);
}resolveEngineConfig()
ts
function resolveEngineConfig(options?): EngineConfig;Apply the documented defaults to the engine options.
Exported for the unit tests; createEngine is the real entry point.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | Omit<CreateEngineOptions, "canvas"> | Options as the caller wrote them. |
Returns
The resolved configuration.
Example
ts
import { resolveEngineConfig } from 'gameable/core';
const config = resolveEngineConfig({ fixedHz: 120 });
console.log(config.fixedDt); // 0.008333…resolveFeatures()
ts
function resolveFeatures(features, table): Promise<LoadedFeature[]>;Load the declared features from a table.
Parameters
| Parameter | Type | Description |
|---|---|---|
features | FeatureOptions | The game's declared features (featuresOf(definition)). |
table | FeatureTable | Known features for this side. |
Returns
Promise<LoadedFeature[]>
The loaded features, in declaration order.
Example
ts
import { resolveFeatures } from 'gameable/core';
const loaded = await resolveFeatures({ characters: {} }, {
characters: async () => ({ name: 'characters', modules: [] }),
});
console.log(loaded.map((f) => f.name)); // ['characters']