A group restore re-establishes a committed boundary this epoch did not run a transition into, so its snapshot carries no decision and no controls for any agent, and a fresh epoch is a new compositionDigest, so the descriptor takes the next revision rather than republishing revision 1 with different contents. CommittedSnapshot's rule becomes: null at boundary 0 and at an installed boundary, always together, and for every agent or none -- a snapshot where one fly acted and another did not would be two boundaries in one value. Dated amendment to publishing-v1 section 3, with the schema set, the fixtures and the TypeScript package moved together and the digest regenerated.
208 lines
12 KiB
Markdown
208 lines
12 KiB
Markdown
# Application, presentation and audience contracts
|
||
|
||
Status: **draft 2**, 2026-09-18. All internal communication uses [Flybus](bus-v1.md). This
|
||
document defines ownership and logical data contracts, not the public feed v2 byte format.
|
||
Existing public v1 contracts remain unchanged for the legacy application.
|
||
|
||
## 1. Applications orchestrate; presentation owns the show
|
||
|
||
Fly Plays Pokémon is an application assembled from simulation and presentation components.
|
||
Its supervisory code and interface share an application-owned, versioned state/event schema.
|
||
A Melee competition or ecosystem can choose another schema. Director, tournament, bracket,
|
||
cast of 32 personas and story segments are examples, not mandatory framework services/types.
|
||
|
||
The framework supplies sessions, agents, backend/task interfaces, capability descriptions,
|
||
native observations and generic UI/client primitives. The application chooses lifecycle,
|
||
profiles, game-aware macros/recovery, identities/history, interventions and narrative behavior.
|
||
Frame-by-frame scheduling remains inside the session; the application need not RPC each tick.
|
||
|
||
Presentation owns selection/layout, resizing, overlays, compositing, audio mixing, encoding,
|
||
browser-facing delivery, recording choices and stream output. Native 480p game data is a
|
||
perfectly valid framework output. Neither the router nor generic session assumes Twitch,
|
||
1920×1080, particular colors, specific React components or an automatic tournament dashboard.
|
||
|
||
## 2. One bus, complementary data sources
|
||
|
||
```text
|
||
Session ── committed observations/events + artifact handles ──┐
|
||
├─ Flybus ── presentation application
|
||
Application ── own state/events/presentation cues ────────────┘
|
||
Presentation gateway ── application-defined browser delivery ── frontend
|
||
```
|
||
|
||
Example addresses (chosen by composition, not recognized by router code):
|
||
|
||
| Address | Pattern / purpose |
|
||
| --- | --- |
|
||
| `session.demo` | RPC: domain lifecycle/status/capabilities; exact methods require session API schemas |
|
||
| `session.demo.descriptor` | Pub/sub, retained latest: framework descriptor |
|
||
| `session.demo.snapshots` | Pub/sub, latest: committed simulation values and native media refs |
|
||
| `session.demo.events` | Pub/sub, bounded: scoped domain events; not a durable log |
|
||
| `app.pokemon.state` | Pub/sub, retained latest: application-specific state |
|
||
| `app.pokemon.cues` | Pub/sub: application narrative/presentation events under declared delivery policy |
|
||
| `app.pokemon` | RPC: application queries/admission, e.g. restore UI state or request a supported effect |
|
||
|
||
**Amendment, 2026-09-22 (PUBLISH-01).** The repair path above needs exact methods, and
|
||
"exact methods require session API schemas" left the row unbuildable. The session registers
|
||
one **read-only** service, `session.<id>.query`, with exactly two methods, both ordinary
|
||
[session RPCs](ipc-v1.md) answering from what the session already published:
|
||
`Session.GetDescriptor` takes an optional `{revision: U64}` and returns that `SessionDescriptor`
|
||
or, with no revision, the newest; `Session.GetSnapshot` takes no parameters and returns the
|
||
latest `CommittedSnapshot`. A revision the session never published is `IDENTITY_MISMATCH`, not
|
||
an empty answer. Nothing on this service mutates, selects a participant or reaches a worker, so
|
||
it is not the controller API section 7 rules out; adding a third method that did would be.
|
||
These two names are **internal and provisional**: they are what the internal boundary needs in
|
||
order to be buildable now, and the later public v2 step is free to rename them, supersede them
|
||
or expose a different repair surface entirely. Nothing about them is browser-facing, and the
|
||
public step does not inherit them by default merely because they landed first.
|
||
|
||
Descriptor revisions and scope link observations to schemas. Cross-topic ordering is not
|
||
guaranteed; a subscriber receiving an unknown descriptor revision must fetch it through the
|
||
application/session query contract or buffer a bounded number of snapshots, not infer shape.
|
||
Latest retained descriptors accelerate startup; RPC querying remains the repair path.
|
||
|
||
## 3. Common simulation descriptors and committed values
|
||
|
||
Types use [session RPC](ipc-v1.md), [workers](workers-v1.md), [state/media](state-media-v1.md):
|
||
|
||
```ts
|
||
interface SessionDescriptor {
|
||
sessionId: Id; revision: U64; compositionDigest: Digest;
|
||
schedulerId: "lockstep-v1";
|
||
environment: EnvironmentDescriptor; taskSchema: SchemaRef;
|
||
agents: {
|
||
agentId: Id; portId: Id; profileDigest: Digest;
|
||
datasetDigest: Digest; indexDigest: Digest; neuronCount: U64;
|
||
rateRoles: Id[]; supportedStimuli: Id[];
|
||
}[];
|
||
assets: AssetRef[];
|
||
}
|
||
interface CommittedSnapshot {
|
||
descriptorRevision: U64; publisherIncarnation: Id;
|
||
scope: Scope; episodeId: Id; sequence: U64; worldTime: RationalNs;
|
||
agents: {
|
||
agentId: Id; telemetry: AgentTelemetry;
|
||
selectedDecision: TypedValue | null;
|
||
appliedControls: PortControl | null;
|
||
}[];
|
||
progress: TypedValue;
|
||
media: { views: ViewRef[]; audio: AudioRef[] };
|
||
eventIds: Id[];
|
||
}
|
||
```
|
||
|
||
**Amendment, 2026-09-22 (PUBLISH-01).** "Null at initial boundary 0" is the rule for a
|
||
boundary this epoch *produced*. A group restore ([state/media](state-media-v1.md) section 5)
|
||
re-establishes a committed boundary `k > 0` that this epoch did not run a transition into, and
|
||
the abandoned epoch's decisions are not this session's to republish under a new epoch. So the
|
||
rule is: `selectedDecision` and `appliedControls` are null at boundary 0 and at a boundary
|
||
*installed* by a restore, present otherwise, and always **together** and for **every agent or
|
||
none**. A snapshot where one fly carries an action and another does not would be two different
|
||
boundaries in one value, and is refused. Without this, the section 6 requirement to publish the
|
||
recovery could not be met at all: the restored boundary's snapshot would be unrepresentable.
|
||
|
||
Publish only after all agent commits establish Ready(k). Decisions/controls describe the
|
||
transition ending at that boundary, null at initial boundary 0. Health updates are separate
|
||
and never claim an uncommitted future boundary. Every transient media reference is a declared
|
||
bus attachment held through publication admission. Ordinary snapshot publication is latest/
|
||
bounded and never waits for a spectator to consume it.
|
||
|
||
Publication sequence is monotonic within publisherIncarnation. Epoch determines simulation
|
||
timeline; router topicSequence only determines bus acceptance order. Never equate these.
|
||
Geometry/spike mapping requires indexDigest, not merely the same number of neurons. Persistent
|
||
AssetRefs survive release packaging; ephemeral ArtifactRefs never become permanent asset URLs.
|
||
|
||
## 4. Flexible data, authored UI
|
||
|
||
Common UI primitives should understand media, agents, controllers, typed measurements,
|
||
progressions, collections and events. Descriptors change infrequently; values change frequently.
|
||
Application-specific structures remain namespaced, schema-validated extensions, such as
|
||
`pokemon.progress.v1` or `melee.match.v1`. They are not mandatory fields on every snapshot.
|
||
|
||
Proposed measurement vocabulary to formalize with public v2 schemas:
|
||
|
||
```text
|
||
Definition: id, owner, label, kind, unit, optional range, schema revision
|
||
Sample: id, producing scope/time, validity, value
|
||
Validity: measured | unknown | unsupported | stale
|
||
```
|
||
|
||
Kinds include number/counter, gauge, duration, state, progression and collection with typed
|
||
item schemas. A measured zero is distinct from unknown. Stale values retain original timestamps.
|
||
Units/ranges are metadata, not pixel sizes. Unknown optional extensions may be omitted or shown
|
||
generically; unsupported required schemas are visible errors. No remote executable UI payloads.
|
||
|
||
Application state carries whatever the experience needs: progress history, featured fly,
|
||
competition records, season state or sponsor effects. It is developed with its presentation,
|
||
not forced into a framework-wide “show state”/tournament schema. An application can reuse
|
||
generic components and add its own panels without changing worker/transport contracts.
|
||
|
||
## 5. Artifact consumption and browser boundary
|
||
|
||
The native presentation client is a regular bus subscriber. Its renderer may hold an extracted
|
||
Artifact after dropping the message; the SDK delays consumption until actual use finishes.
|
||
Latest coalescing only drops queued values. A stalled consumer is constrained by finite credits,
|
||
owners and store budgets; it cannot make the router overwrite an in-use image.
|
||
|
||
The browser does not receive private owner tokens or local storage paths. A presentation
|
||
gateway resolves/copies/encodes artifacts into its chosen browser transport and then drops
|
||
its bus handles. That is an application-edge adapter, not a second framework communications
|
||
stack. Compositor/encoder/recorder processes inside the application can exchange their own
|
||
artifacts through the same bus when useful.
|
||
|
||
Dense spike publication is optional and identifies agent, index digest and covered ticks.
|
||
The runtime need not publish every neuron every millisecond. Required sensory data and
|
||
optional spectator data have distinct budgets; UI focus never changes an agent's input,
|
||
controller assignment or an already resolved stimulation/effect target.
|
||
|
||
## 6. Events, persistence and recovery visibility
|
||
|
||
Events identify session/epoch, source boundary, episode, optional agent, kind and typed payload.
|
||
Task events are emitted after committed transitions; capture/admission events describe their
|
||
actual phase. Bus publish acceptance and delivery consumption are not durable acknowledgments.
|
||
|
||
When durability is required, call a configured event-store client/service (over the same bus)
|
||
and await its append/commit acknowledgment under the session's configured policy. Pub/sub
|
||
remains useful for live observers; reconnecting clients query durable history through ordinary
|
||
RPCs. The initial conformance policy pauses at the next safe boundary if durable event
|
||
admission/commit fails, retaining only a bounded pending batch. No hidden durable broker queue.
|
||
|
||
After rollback publish old/new epochs, checkpoint identity and abandoned step ranges.
|
||
Application history can mark outcomes aborted/superseded; it does not erase records merely
|
||
because emulator time moved backward. Media reports the corresponding discontinuity.
|
||
|
||
## 7. Supervisory and audience effects
|
||
|
||
Application supervision uses bus RPCs for configured lifecycle/intervention capabilities
|
||
and pub/sub for application state/cues. The Twitch adapter can be a constrained bus client
|
||
of the application's admission service; viewers/browser clients never obtain worker control.
|
||
Legacy HTTP bridge behavior remains until deliberately migrated.
|
||
|
||
Effects are declared by the task/backend/profile: valid targets, parameter schema, timing,
|
||
duration/stacking, implementation capability and outcome events. Examples include neural
|
||
stimulation or future game items/modifiers, where verified implementations exist. Generic
|
||
game boons and a public v2 admission schema are follow-on work; initially new-session audience
|
||
effects remain disabled. No arbitrary controller/game-memory write endpoint is introduced.
|
||
|
||
```text
|
||
requested → rejected
|
||
→ accepted(target, epoch, earliestStep) → scheduled → applied → expired
|
||
└─ failed / cancelled
|
||
applied → rolled-back
|
||
```
|
||
|
||
The application persists interaction identity/target and defines retry, redemption, refund
|
||
and recovery policy. A gift is an intervention, not automatically an earned neural reward.
|
||
Presentation cues may be immediate; simulation effects apply at declared boundaries. The
|
||
supervisor does not bypass the complete-batch step barrier. Chat text remains presentation
|
||
data; template-only replies/quiet mode and existing no-public-button rules continue.
|
||
|
||
## 8. Presentation acceptance criteria
|
||
|
||
- Per-agent/session stores and rate/afterglow state, not one mutable global fly.
|
||
- Framework plus application schema streams, with descriptor repair/reconnect behavior.
|
||
- Native view dimensions and aspect; application-controlled output resolution and composition.
|
||
- Explicit audio ownership, timestamped overlays and bounded queues/discontinuities.
|
||
- Last-use artifact release, cached/replay-safe references and slow-observer isolation.
|
||
- Game-specific labels/views without Pokémon fields in generic runtime/router schemas.
|
||
- Actual UI changes reviewed as PNGs with browser/legibility gates. This contract is not screen approval.
|