Skip to content

Activity Game Pipeline

Use plain ascii in this document only.

Goal: Define the canonical layer-by-layer structure for rally activity game crates so new activities can be added and existing ones can be audited without reverse engineering brand orchestration.

Prerequisite plans:

  • plan-buffer-management.md
  • plan-schema-hygiene-C.md
  • plan-unified-signals.md
  • plan-activitygame-trait.md

The pipeline has six layers. Each layer has a fixed responsibility, a standard home, and a simple compliance test.

Layers

Layer Name Responsibility Standard home Expected dependencies
0 Domain Types Event, state, identifier, and payload types. Single-schema types implement HasSchema; multi-schema families use constants. <activity>_game/src/types.rs serde, serde_json, domain-local types
1 Engine Tick evaluator and state mutation. Reads world state and accumulates recent domain events into DrainBuffer<Event>. No signal emission logic. <activity>_game/src/engine.rs Layer 0, plant_atom::DrainBuffer, world context
2 Signals Domain payload plus SignalBuffer<Payload> and typed emit_*() helpers. Consumer-facing drain returns SignalSnapshot<Payload>. <activity>_game/src/signals.rs Layer 0, signals::SignalBuffer
3 Game Struct Orchestrator owning engine and signals. Implements ActivityGame where applicable and delegates tick, remove_hob, and reset. <activity>_game/src/game.rs Layers 0-2, game::ActivityGame
4 Brand Integration WASM-facing API for seed, start, reset, query, and drain operations on the activity. rally/brand/src/<activity>_api.rs Layer 3
5 Consumer Snapshot Per-frame signal drain and query usage through SignalSnapshot methods such as filter_schema, latest_by_schema, and count_by_schema. brand or client call site Layer 4, signals::SignalSnapshot

Checklist

Use this checklist when creating or auditing an activity.

Layer Checklist
0 Types are isolated from orchestration code, schema IDs are not hardcoded at call sites when a single-schema type can implement HasSchema.
1 Recent domain events use DrainBuffer<T> instead of ad hoc Vec<T> accumulation.
2 Activity telemetry uses SignalBuffer<P> and drains to SignalSnapshot<P>. Chem projection updates are not misclassified as telemetry signals.
3 Public activity state is wrapped in one game struct with lifecycle methods. ActivityGame is implemented when the tick model matches the shared contract.
4 Brand exposes activity-specific commands and queries without embedding domain logic into plantangenet crates.
5 Signal consumers query snapshots through helper methods instead of manually filtering raw vectors.

Compliance Matrix

Legend:

  • Yes: layer exists in the expected role.
  • Partial: layer exists but misses part of the target pattern.
  • No: layer is absent or uses a materially different pattern.
Activity L0 Types L1 Engine L2 Signals L3 Game Struct L4 Brand API L5 Snapshot Consumer Notes
delivery_game Yes Partial Partial Partial Yes No Has the closest shape already. Remaining gaps are HasSchema, DrainBuffer, SignalBuffer, and ActivityGame adoption.
exploration_game Partial Partial No Partial Yes No Logic is collapsed into lib.rs. Discovery progress is a chem projection flow, not yet a telemetry signal layer.
racing_game Partial Partial Partial No Yes No Simulation structure diverges from the shared ActivityGame contract. Signal layer exists but still uses a local buffer pattern.
future activity Target Target Target Target Target Target New activities should start from the six-layer layout rather than collapsing files together.

Current Gap Map

Delivery game:

  • Layer 0 gap: add HasSchema where single-schema types apply.
  • Layer 1 gap: replace ad hoc recent-event vectors with DrainBuffer.
  • Layer 2 gap: migrate to SignalBuffer<Payload> and SignalSnapshot<Payload>.
  • Layer 3 gap: implement ActivityGame.
  • Layer 5 gap: consume drained signals through snapshot queries.

Exploration game:

  • Layer 0 gap: extract domain types from lib.rs and add HasSchema where appropriate.
  • Layer 1 gap: separate engine logic and adopt DrainBuffer for recent activity events.
  • Layer 2 gap: add a dedicated telemetry signal layer distinct from chem projection updates.
  • Layer 3 gap: add tick() adapter and implement ActivityGame.
  • Layer 5 gap: expose snapshot-based signal consumption once a telemetry layer exists.

Racing game:

  • Layer 0 gap: replace hardcoded schema strings with constants or HasSchema where appropriate.
  • Layer 2 gap: replace local signal accumulation with SignalBuffer<Value>.
  • Layer 3 gap: no single game struct today; this remains intentionally outside the shared trait for now.
  • Layer 5 gap: add snapshot-based signal consumption if cross-activity drain becomes required.

Rules Of Thumb

  • Keep rally domain types out of plantangenet crates.
  • Layers only depend downward in the stack.
  • A raw Vec is a smell in recent-event and recent-signal paths when DrainBuffer or SignalBuffer exists.
  • A signal consumer should almost never need to hand-roll filtering over drained envelopes.
  • Racing is allowed to be a partial participant when the shared tick contract would distort its simulation model.