refactor: move world concepts into engine

This commit is contained in:
Toad 2026-08-24 15:17:53 +02:00
parent eeedcb8e48
commit 2d15c7ab8d
52 changed files with 3298 additions and 1558 deletions

View file

@ -1,26 +1,48 @@
import type { Mesh } from "./Mesh"
import type { RenderPrototype, RenderTransform } from "../render/RenderScene"
import type { Collider } from "../world/Collider"
/** Definition of an **Entity** actor kind: something that lives in the world with
* its own behavior and a live transform (mobs, and later the npc / powerups) -- as
* opposed to a baked, static `Prop`. `State` is the per-instance runtime record the
* behavior mutates; `World` is whatever that behavior reads (e.g. `Terrain`).
*
* Every field is plain data or a module function, so a definition is **imported
* into each context** (main thread + each render worker) rather than structured-
* cloned across the wire -- the per-kind polymorphism is code, not serialized
* state. That's what lets a registry of these stay compatible with the worker
* renderer (only plain instance data ever crosses; behavior is loaded per side). */
export type Entity<State, World> = {
/** Stable tag for the kind (also the texture key today). The registry's order,
* not this string, is what becomes the id packed into the mob SAB. */
name: string
/** Build the canonical local-space mesh once; every instance shares it, differing
* only by its per-frame model matrix. */
build: (mesh: Mesh) => void
/** Advance one instance by `dt` seconds. */
/** Open actor behavior and representation. Concrete definitions are ordinary game
* objects referenced directly by instances. */
export type ActorDefinition<State, World> = {
prototype: RenderPrototype
update: (state: State, dt: number, world: World) => void
/** Local bounding radius (pre-scale) for the per-frame cull AABB. */
boundingRadius: number
/** Local body height (pre-scale) for the top of the stand-on collider. */
bodyHeight: number
transform: (state: State) => RenderTransform
collider?: (state: State, world: World) => Collider | null
}
/** Type-erased live actor. `create` captures concrete state safely, allowing one
* level to hold unrelated actor state types without a content union. */
export type Actor<World> = {
readonly definition: object
readonly prototype: RenderPrototype
readonly updateState: (dt: number, world: World) => void
readonly readTransform: () => RenderTransform
readonly readCollider: (world: World) => Collider | null
}
export namespace Actor {
export function create<State, World>(
definition: ActorDefinition<State, World>,
state: State,
): Actor<World> {
return {
definition,
prototype: definition.prototype,
updateState: (dt, world) => definition.update(state, dt, world),
readTransform: () => definition.transform(state),
readCollider: (world) => definition.collider?.(state, world) ?? null,
}
}
export function update<World>(actor: Actor<World>, dt: number, world: World): void {
actor.updateState(dt, world)
}
export function transform<World>(actor: Actor<World>): RenderTransform {
return actor.readTransform()
}
export function collider<World>(actor: Actor<World>, world: World): Collider | null {
return actor.readCollider(world)
}
}