import type { Terrain } from "../Terrain" import type { Vec3 } from "../../engine/math/Vec3" import type { Mesh } from "../../engine/scene/Mesh" import type { Entity } from "../../engine/scene/Actor" import { frog } from "./mobs/Frog" import { bee } from "./mobs/Bee" import { robin } from "./mobs/Robin" /** A roaming creature drawn as a moving low-poly mesh (unlike the static baked * world). Each kind is an `Entity` definition (geometry + behavior + bounds) living * in its own module under `mobs/`; this file just assembles them into a registry * and exposes a thin per-kind dispatch. Adding a kind = add a `mobs/.ts` + * one entry in `MOB_KINDS`/`DEFS`. * * A mob's geometry is a **canonical local-space mesh** built once per kind (front = * +Z, frog/robin feet / bee body at the origin); the live `position`/`heading`/ * `scale` are turned into a per-frame model matrix by the renderer. All wander * state lives on the instance so `update` is a pure stepping function of the mob + * dt (deterministic via the evolving `seed`), which keeps the sim on the main * thread and cloneable-free. */ export type MobKind = "frog" | "bee" | "robin" export type Mob = { kind: MobKind /** Leash anchor (where it was scattered); wandering is pulled back toward it. */ home: Vec3 /** Live feet-center (frog/robin) / body-center (bee), advanced each frame. */ position: Vec3 /** Facing yaw; the mesh's front is local +Z, so world dir = (sin h, 0, cos h). */ heading: number /** Per-instance size multiplier. */ scale: number /** Evolving RNG state (mutated by `update`) -- keeps the sim deterministic. */ seed: number /** Horizontal velocity (frog/robin: mid-hop or -flight; bee: cruise). */ vx: number vz: number /** Vertical velocity (frog/robin ballistic hop/flight; bee stays 0, uses a bob). */ vy: number /** Countdown to the next decision (frog/robin: next hop; bee: next heading change). */ timer: number /** Per-kind scratch clock: the bee's hover-bob phase; the robin's remaining * powered-flight cruise time (>0 while gliding between perches). */ phase: number /** Frog/robin: resting on the ground vs airborne (a hop or a flight). */ grounded: boolean } /** Canonical kind order. **The index is the id packed into the mob SAB** (see * renderer/worker), so this order must be identical in every context and must not * change under existing kinds -- `mobs.test.ts` guards it. Append new kinds. */ export const MOB_KINDS: MobKind[] = ["frog", "bee", "robin"] /** The per-kind `Entity` definitions, one module each. Imported (not cloned) into * whatever context uses it, so it works the same on the main thread and in workers. */ const DEFS: Record> = { frog, bee, robin } export namespace Mob { /** The definition for a kind (geometry, behavior, bounds). */ export function def(kind: MobKind): Entity { return DEFS[kind] } /** Advance one mob by `dt` seconds, sampling `terrain` for ground height. */ export function update(mob: Mob, dt: number, terrain: Terrain): void { DEFS[mob.kind].update(mob, dt, terrain) } /** Append the canonical local-space mesh for `kind` into `mesh` (once per kind at * load; every instance shares it, differing only by transform). */ export function build(kind: MobKind, mesh: Mesh): void { DEFS[kind].build(mesh) } /** Local bounding radius (pre-scale), for building the per-frame cull AABB. */ export function boundingRadius(kind: MobKind): number { return DEFS[kind].boundingRadius } /** Local body height (pre-scale), for the top of the stand-on collider. */ export function bodyHeight(kind: MobKind): number { return DEFS[kind].bodyHeight } }