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
HasSchemawhere single-schema types apply. - Layer 1 gap: replace ad hoc recent-event vectors with
DrainBuffer. - Layer 2 gap: migrate to
SignalBuffer<Payload>andSignalSnapshot<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.rsand addHasSchemawhere appropriate. - Layer 1 gap: separate engine logic and adopt
DrainBufferfor recent activity events. - Layer 2 gap: add a dedicated telemetry signal layer distinct from chem projection updates.
- Layer 3 gap: add
tick()adapter and implementActivityGame. - 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
HasSchemawhere 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
Vecis a smell in recent-event and recent-signal paths whenDrainBufferorSignalBufferexists. - 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.