Skip to content

Frame Lifecycle

The Plantangenet runtime coordinates via a clock-driven FrameManager managing a series of deterministic FramePhase steps per tick.

This explicit stage progression guarantees predictable dispatch of our WorldContext services, eliminating race conditions while enforcing an "update then read-drain" loop.

Phase Progression

A clock step (tick) advances the FrameManager from Idle through its active phases, eventually returning it to Idle.

stateDiagram-v2
    direction LR

    Idle --> BeginningFrame: begin_frame()
    BeginningFrame --> Servicing
    Servicing --> Active
    Active --> Aggregation
    Aggregation --> EndingFrame: end_frame()
    EndingFrame --> Idle

    note right of BeginningFrame : on_frame_init
    note right of Active : on_frame_active
    note right of Aggregation : on_frame_aggregation
    note right of EndingFrame : on_frame_cleanup

Phase Permissions and Service Triggers

Not all FramePhases execute mutations or drive services. Below is the behavioral reference table for phase-aligned triggers.

Phase Mutations Allowed Services Fire Notes
Idle yes none Safe state between frames, mutations apply immediately.
BeginningFrame no on_frame_init Setup/Housekeeping tasks only.
Servicing yes none (deferred) In the future: Obligation payments, skippable.
Active yes Tier 1 (hob, deform) Core simulation loop runs. Mutations occur.
Aggregation no Tier 2 (road/inv/ach) Drains queues/events produced during Active phase.
EndingFrame no on_frame_cleanup Read-only bookkeeping.

:::info Soft / Hard Close

During end_frame(), there are internal LifecycleHook transitions for OnFrameSoftClose and OnFrameHardClose. These are skipped by most services, but may be used by the string-keyed HookDispatcher system for internal callback cleanup.

:::

Concrete Example: Clock Tick

When the server runs with a fixed interval (e.g. set_interval(100ms)), what exactly happens on that clock tick?

  1. Tick initiates: FrameManager asserts it is currently in the Idle phase.
  2. begin_frame(None) is invoked.
  3. Phase BeginningFrame: Dispatcher fires on_frame_init globally.
  4. Phase Servicing: (if enable_servicing config is true). Currently, no simulation services mutate here.
  5. Phase Active: Tier 1 services (HobService, DeformationService) grab their execution budgets. HobService flushes its batch queue, calling move_hob mutations, which stamp records for Deformation.
  6. (begin_frame completes, returning the FrameId). end_frame() is immediately invoked by the clock task.
  7. Phase Aggregation: Tier 2 services (RoadService, InventoryService, AchievementService) iterate over newly appended events and drain them completely.
  8. Phase EndingFrame: Drains the internal memory pools and calculates system DustAmount conservation (if configured). on_frame_cleanup is dispatched.
  9. Return to Idle: The FrameManager returns to Idle, waiting for the next 100ms scheduled tick.