Repository navigation
[M1-LOOP-02] Presentation snapshot + interpolation state - #30
Merged
Merged
Conversation
Per-tick presentation state (M1-LOOP-02 scope, nothing else): - Position2D<Backend>: the first built-in component (the entity's 2D simulation-space position as the selected SimMath backend's Vec2, ADR 0002); both backends registered (fpx16_16 default, fp32_pinned opt-in). - PresentationSnapshot<Backend> (header-only class template, the M1-ECS-02 pattern): per-completed-tick prev/curr capture over a pre-reserved per-slot table (no per-tick/per-frame heap, PERF-003); the exact-integer anchored alpha alpha = (R - A(T)) * rate / 1e9 clamped to [0, 1] - never extrapolates; sample_position(e) returns the SimMath lerp (synced entities), snaps entities added between ticks to their current value (documented), and rejects stale handles (warn-once) / live handles without a Position2D (no warn); moved-from snapshots are stopped (no world access, no logging). - GameLoop: Options::onTick per-completed-tick hook (fires only for completed ticks) + startReferenceNs() - the M1-HEAD-01 wiring shape. - Docs: docs/api/presentation.md (new, full contract + Performance), game_loop.md (hook + startReferenceNs), docs/README.md, the module README. - Tests: tests/laige-sim/presentation_tests.cpp (suite Presentation; CTest entry 'presentation' = the step's Verify), 10 cases incl. the exact Q16.16 linear-interpolation values, the alpha clamp matrix, new-entity snapping, catch-up per-tick refresh, the GameLoop hook integration, and the 500-entity x 100-frame zero-alloc window. - laige-api.json regenerated (555 symbols; api-real-tree green). Verify: ctest -R presentation green; full ctest 45/45 on the canonical g++ tree plus zero-warning 45/45 on build-asan, build-tsan, build-clang, build-release, build-shared; tools/laige-include-lint OK.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements M1-LOOP-02 — Presentation snapshot + interpolation state (roadmap/M1-heartbeat.md): per tick, the world produces a presentation snapshot of
Position2Dstate; render frames interpolate between the last two tick states with a clamped alpha.What's here (the step's Scope, nothing else)
Position2D<Backend>— the first built-in component: the entity's 2D simulation-space position as the selected SimMath backend'sVec2(ADR 0002). Both backends registered:Position2DFpx16(fpx16_16, default) /Position2DFp32(fp32_pinned, opt-in).PresentationSnapshot<Backend>(new header-only public header, class template — one instantiation per backend, the M1-ECS-02 pattern):onTick(tick): per completed tick,prev ← curr,curr ← world valuefor every live entity with aPosition2D— over a pre-reserved per-slot table (24 B/slot, one setup-path allocation; no per-tick/per-frame heap, PERF-003).curr(the documented scope behavior): entities added between ticks render at their spawn position, no phantom interpolation; generation-checked so slot recycling self-heals.onRenderFrame(renderNs): the alphaalpha = (R − A(T)) × rate / 10⁹in exact integer arithmetic (overflow-free for any 64-bit clock reading; no float accumulator, ARCH-010), clamped to [0, 1] — never extrapolates (before the anchor → 0; a clock jump a full tick or more past → 1).sample_position(e)(the roadmap's exact name):lerp(prev, curr, alpha)for synced entities, snap for entities first seen since the last refresh,InvalidArgument+ warn-once for stale handles (theWorld::checkprecedent),InvalidArgumentno-warn for live handles without aPosition2D, stopped (no world access, no log) when moved-from.create(world, startReferenceNs, options): validates the tick rate (20–120 Hz, the loop's range) with one rate-limitedpresentation/tick_rate_invalidwarn.GameLoopgains the M1-HEAD-01 wiring seam:Options::onTick(plainnoexceptfunction pointer — fires once per completed tick, never for a failed tick) +onTickContext+startReferenceNs()(the alpha's anchor base).docs/api/presentation.md(full contract + Performance section),docs/api/game_loop.md(hook section, Options table,startReferenceNs),docs/README.md, the module README.laige-api.jsonregenerated (555 symbols;api-real-treegreen).Verify (step's Verify command:
ctest -R presentation)ctest -R presentation— green (10 cases: tick-rate validation; exact Q16.16 linear interpolation at alpha 0/0.5/0.75/near-1 (raw-unit expectations, no float round-trips); the alpha clamp matrix incl. a 285-year clock reading; entity-added-between-ticks snap-then-interpolate; catch-up per-tick refresh; stale/missing rejection; the GameLoop hook integration incl. the failed-tick case; moved-snapshot stop; the 500-entity × 100-frame zero-alloc window (allocs=0, machine-greppable); the fp32 backend).ctest45/45.build-asan,build-tsan,build-clang,build-release,build-shared: all zero-warning, 45/45 each (the sanitizer trees prove the zero-alloc window leak-free).tools/laige-include-lint: OK.