Skip to content

Architecture: v2 as archival bridge, v3 as engine-native — and the contract that separates them #46

Description

@thesprockee

Recording a direction from Andrew (2026-08-26) plus the measurements that support it, because it decides #35, #38 and #39 and none of those can be settled without it.

The change in premise

Two facts reframe every open schema question:

  1. echoreplay is going away. The archive is converted once and then deleted.
  2. Tape generation is moving into the game engine — sourced from the broadcaster and the game state machine, not from polling /session.

Andrew's proposal:

maintain precision for echoreplay translation, but optimize for the real message/event/values that the echoreplay /session endpoint fields are derived from. tape v2 can be a transition to tape v3... which would completely shed the unoptimized, delta-unfriendly, or non-deterministic/non-atomic nature of an echoreplay file.

Why /session is the wrong thing to model

/session is a projection of engine state into hand-parseable JSON, sampled by polling. Three consequences, each measurable:

It renders derived values next to their inputs. Corpus measurements:

Relationship Result
team stats == sum of player stats 68,680 / 68,680, zero mismatches
pot_speed_from_rot / rot_per_sec one constant, 0.7539821…, spread 3.6e-08 across 3,600 samples
arm_speed vs speed_from_arm 54,312 bit-identical, 2,400 differing

That constant is ~2*pi*0.12 — tangential speed at the disc radius. It is physics computed from rot_per_sec, shipped so a client does not have to. v2 already drops team stats and rebuilds them with sumTeamStats (reconstruct.go:449), so the pattern is established; it simply was not applied to the rest.

It renders orientation into a lossy form. The engine's own wire protocol carries a quaternion:

broadcaster/v1/r15net.proto:229    spatial.v1.Quat rotation = 6;

(now living in nevr-server-rs/rad-protocol/proto/, and it does import "spatial/v1/types.proto" — so the blast-radius constraint on spatial.v1.Pose still holds.)

/session renders that quaternion as forward/left/up: nine numbers for three degrees of freedom. Measured across 740,588 bases, they are orthonormal only to ~1e-4 — about three orders of magnitude worse than float32 epsilon — and uniformly left-handed. The deviation is rendering noise, not information, and it is what the round-trip's 2.4e-3 orientation tolerance has actually been measuring.

It is a sample, not a transaction. Events between polls are inferred by diffing snapshots, and the proto already records where that fails (echo_arena.proto:580-600): the engine reported 0 stuns and 0 catches for a player across a whole match while tape's sensors emitted 3 and 5. Engine-native generation makes events authoritative instead of inferred. This is the strongest argument for v3.

The contract that separates the two

I asked earlier whether v2 is semantically lossless or byte-lossless. The answer is per-provenance, not global:

source contract governing risk
v2 echoreplay lane the archive, converted once byte-lossless the source is deleted; a wrong call is unrecoverable
v3 engine lane broadcaster + state machine semantically lossless none — the source is live and re-derivable

What that implies for v2 right now

Be maximally conservative. 'Derivable' is not the same as 'bit-reproducible', and the precision data shows the gap is real: speed_from_arm matches arm_speed 96% of the time and differs 4%; the pot_speed_from_rot ratio is constant only to float32 resolution. Dropping either as redundant would be a one-way loss against an archive that no longer exists to check.

So: do not apply the redundancy audit to v2. It applies to v3.

What that implies for v3

Carry the inputs, not the projections: quaternions rather than bases, rot_per_sec rather than pot_speed_from_rot, engine events rather than diffed snapshots, per-player stats rather than team sums.

Effect on the open issues

Open question

Whether the v2 archival lane must be byte-exact on float values or only on number formatting. The repo currently asserts byte-compatibility for the writer's formatting (FixProtojsonUint64Encoding, FixEngineFloatFormatting) which is the narrower claim. If float values must also be reproduced exactly, the basis noise has to be stored as a residual. That is the one thing still to settle before the archive is converted.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions