Behavior runner (sim/core)¶
Overview¶
Periodic actions for sim entities (e.g. colonists) are defined as behaviors in sim/core and ticked by a generic BehaviorRunner. The runner is entity-agnostic; the client provides a context implementation that turns behavior requests into jobs or other side effects.
Paths:
- sim/core/behaviors/ –
BehaviorRunner, typeBehavior<TEntity, TContext> - sim/core/objects/colonist/ColonistBehaviors.ts – colonist behaviors and
ColonistBehaviorContextinterface - client: src/client/ui/utils/ColonistBehaviorContextImpl.ts – client implementation of the context
BehaviorRunner (generic)¶
- Behavior<TEntity, TContext>:
id,intervalMs,run(entity, ctx, deltaTimeMs). intervalMs === 0→ run every tick; otherwise run when accumulated time ≥intervalMs.- BehaviorRunner<TEntity, TContext> constructor:
behaviors,context,getEntities(),getEntityId(entity). - tick(deltaTimeMs): for each entity, runs each behavior (with per-entity per-behavior accumulators for intervals).
The runner lives entirely in sim/core and has no dependency on jobs or the client. Context is injected so the same mechanism can be reused for other entity types (e.g. trees, creatures) with different context interfaces.
Colonist behaviors¶
ColonistBehaviorContext (interface in sim):
addWanderJob(colonistId)– request a one-tile wander move (client picks valid adjacent tile and adds MoveToJob).requestNeedJob(colonistId, needType: 'drinking' | 'sleeping')– request a drink or sleep job.
Default colonist behaviors (createDefaultColonistBehaviors()):
| Behavior | intervalMs | What it does |
|---|---|---|
| needCheck | 0 | Calls colonist.tickNeeds(deltaTime); if drinking/sleeping crossed ≤10%, calls ctx.requestNeedJob(colonistId, 'drinking'|'sleeping'). |
| wander | 1000 | If colonist.isFree(), calls ctx.addWanderJob(colonist.getUUID()). |
So need decay and “want to drink/sleep” are defined at colonist level; job creation is delegated to the context.
Client context implementation¶
ColonistBehaviorContextImpl (createColonistBehaviorContext(deps)):
- Deps: objectManager, jobSystem, needJobScheduler, canMoveTo, mapWidth, mapHeight.
- addWanderJob: if colonist free, pick random adjacent tile with
canMoveTo, addMoveToJob(newX, newY, colonistId)via jobSystem. - requestNeedJob: call
needJobScheduler.createJobForColonist(colonistId, needType).
When DEBUG_HAUL_ONLY is true, both methods no-op (wander and auto drink/sleep disabled).
Wiring in the game loop¶
IsometricMap builds the colonist runner once:
new BehaviorRunner(createDefaultColonistBehaviors(), createColonistBehaviorContext(deps), getColonists, c => c.getUUID()).- Each frame:
colonistBehaviorRunner.tick(deltaTime)(before the job system tick).
So all colonist periodic logic (needs + wander) is centralized in the behavior runner; the map only ticks the runner and the job system.
Extending to other entities¶
To add behaviors for another entity type (e.g. trees, animals):
- In sim/core: define a context interface and
createDefaultXxxBehaviors()returningBehavior<ThatEntity, ThatContext>[]. - In client: implement the context (e.g. create jobs, update state) in a separate module.
- In the game loop: create another
BehaviorRunner(behaviors, contextImpl, getEntities, getEntityId)and call itstick(deltaTime).