import * as THREE from 'three'; import type { Clock } from './clock.js'; import type { Store, Reducer } from './state.js'; import type { RendererOptions, ResizeHandler } from './renderer.js'; import type { FrameContext, SceneContext, Disposable } from '../types.js'; /** * A scene feature in the unidirectional flow. `build` creates objects once; * `update` projects the current state onto them every simulation tick. Modules * read state — they never write it back. */ export interface AppModule> { name: string; build(ctx: SceneContext): void; update?(state: S, frame: FrameContext, ctx: SceneContext): void; dispose(): void; } /** * Perspective-camera setup for {@link createApp}. Defaults: `fov` 50, * `near` 0.1, `far` 200, `position` [4, 3, 6], `lookAt` the origin. */ export interface AppCameraOptions { fov?: number; near?: number; far?: number; position?: readonly [number, number, number]; lookAt?: readonly [number, number, number]; } /** * Configuration for {@link createApp}. Only `canvas` is required — every other * option has a sensible default (lighting on, orbit on, wall clock, seed 1). */ export interface AppOptions> { /** Target canvas. The renderer sizes itself to the canvas parent. */ canvas: HTMLCanvasElement; /** Initial serializable app state. Defaults to an empty object. */ state?: S; /** Optional reducer enabling `app.dispatch(action)` alongside `setState`. */ reducer?: Reducer; /** * Seed for the injected {@link SceneContext.rng}. Same seed + same tick * sequence reproduce the same world. * @defaultValue 1 */ seed?: number; /** Injectable time source. Pass createClock({ mode: 'fixed' }) for determinism. */ clock?: Clock; /** Renderer factory options forwarded to `createRenderer` (minus `canvas`). */ renderer?: Omit; /** Perspective-camera options, or a prebuilt camera (e.g. an iso ortho rig). */ camera?: AppCameraOptions | THREE.Camera; background?: THREE.ColorRepresentation; /** Runs after the built-in resize handling — resize ortho frustums, composers, … */ onResize?: ResizeHandler; /** Replaces the default renderer.render(scene, camera) — wire a composer here. */ render?: () => void; /** Standard three-light rig. Default true. */ lighting?: boolean; /** Built-in pointer orbit. Default true; disable when using a camera controller. */ orbit?: boolean; /** Scene features built once at creation and updated every simulation tick. */ modules?: AppModule[]; /** Runs after module updates, before render — the place for app-level per-frame glue. */ onFrame?: (state: S, frame: FrameContext, ctx: SceneContext) => void; } /** * Running app shell returned by {@link createApp}. Drive it with the built-in * frame loop (`start`/`stop`) or pump the simulation manually with `tick`; * `dispose` tears down the loop, gestures, modules, scene, and renderer. */ export interface App> extends Disposable { ctx: SceneContext; store: Store; getState(): S; /** Shallow-merge `patch` into app state and notify store subscribers. */ setState(patch: Partial): void; /** Run the reducer. Throws when the app was created without one. */ dispatch(action: A): void; /** Advance the simulation by `realDelta` seconds (default: one clock step) and render. */ tick(realDelta?: number): void; /** Attach to the shared frame loop and animate continuously. */ start(): void; /** Detach from the frame loop; state and scene stay intact. */ stop(): void; } /** * Build a complete unidirectional app shell: renderer, scene, camera, seeded * rng, store, clock, and one shared frame loop wired together. Each simulation * tick flows store state through `module.update(state, frame, ctx)` and then * `onFrame` before a single render; input goes back through * `setState`/`dispatch`, never straight into the scene. * * The loop starts paused — call `start()` to animate, or `tick()` to step * deterministically (headless tests, replays). * * @param options - App configuration; see {@link AppOptions}. Only `canvas` is required. * @returns An {@link App} handle. `dispose()` stops the loop, detaches gestures * and the resize observer, disposes modules, lights, scene, and renderer. * @throws Error when `options.canvas` is missing. * @typeParam S - Serializable app state shape. * @typeParam A - Action type for the optional reducer; defaults to `Partial`. * @remarks Modules are built and the scene pre-compiled synchronously inside * this call. The built-in orbit is view-only camera manipulation, deliberately * outside app state; pass `orbit: false` when mounting your own controller. * @example * const app = createApp({ * canvas, * state: { speed: 1 }, * modules: [ turbineModule ], * onFrame: (state, frame) => hud.update(state, frame.delta), * }) * app.start() * // later: app.setState({ speed: 2 }); app.dispose() */ export declare function createApp, A = Partial>({ canvas, state, reducer, seed, clock, renderer: rendererOptions, camera: cameraOptions, background, lighting, orbit, modules, onFrame, onResize, render, }: AppOptions): App; //# sourceMappingURL=app.d.ts.map