Skip to content

feat(net): generalize snapshot interpolation/reconciliation to a per-component policy registry (#462) - #483

Merged
drsnuggles8 merged 2 commits into
masterfrom
feature/netcode-snapshot-interp-components
Jun 30, 2026
Merged

drsnuggles8 merged 2 commits into
masterfrom
feature/netcode-snapshot-interp-components

Conversation

@drsnuggles8

@drsnuggles8 drsnuggles8 commented Jun 30, 2026 •

Copy link
Copy Markdown
Owner

@

What & why

Closes #462. SnapshotInterpolator previously interpolated only TransformComponent, and client reconciliation blended only transforms — so animated / rotating / physics-driven networked entities jittered or popped on remote clients. This generalizes interpolation and reconciliation to a data-driven registry of interpolatable components, each with a per-component blend policy (lerp / slerp / step), built on the existing ComponentReplicator path.

All changes live in Networking/Replication/ (+ Networking/Prediction/ClientPrediction.cpp).

How

  • New ComponentInterpolationRegistry (EInterpolationPolicy { Lerp, Slerp, Step } + a type-erased InterpolationEntry with stateless Has/Capture/Interpolate/Snap/Smooth fn-pointers). Adding a networked-and-interpolated component is now one Register() call, not a new code path. Lazily self-registers its defaults so the snapshot paths work headless (no NetworkManager::Initialize needed).
    • Defaults: Transform (translation/scale lerp + rotation slerp — composite), Rigidbody3D (velocity lerp, body-type/mass step), AnimationState (step — discrete clip/state).
  • EntitySnapshot now uses a self-describing multi-component wire format — per entity [uuid][count] then [componentId:u32][len][bytes], where componentId is the FNV-1a-32 of the component name. Unknown ids skip by length, so the format tolerates registry drift. A single Parse() owns decoding (reused by Apply, CaptureDelta baseline, and the interpolator cache). Capture/Apply stay byte-consistent, so LagCompensator / RollbackManager round-trips are unaffected.
  • SnapshotInterpolator iterates the registry per entity instead of hardcoding transform.
  • ClientPrediction::Reconcile smoothing routes through the registry Smooth fns (transform hard-snaps past threshold; step components dont smooth), dedupd per predicted entity — identical behaviour for transforms, now extended to the registered set.
  • ComponentReplicator gains an AnimationStateComponent net-subset serializer (discrete state + clip + cursor; every wire float std::isfinite-guarded per fix(net,task): isfinite-guard untrusted float ingestion (network snap… #400).

Tests

  • ComponentInterpolationRegistryTest (unit) — policy table, FNV-1a wire-id hashing/uniqueness, custom-registration, multi-component snapshot round-trip, and lerp/slerp/step semantics at a fixed alpha.
  • InterpolatedComponentsSmoothOnRemoteClientTest (Functional, the issues acceptance criterion) — a real physics body that moves and spins, snapshotted through the real EntitySnapshot path, interpolated on a separate client scene (mirror entity, no physics). Sweeps alpha 0.25 → 0.5 → 0.75 and asserts the pose blends smoothly between the two server states — strictly interior, monotonic, never snapping to an endpoint (no per-snapshot popping).

Verification

  • Builds clean (engine + tests).
  • 3729 / 3730 tests pass. The one failure (ProceduralSkyBakeTest.GeneratesNonBlackEnvironmentMapWithIBL) is an unrelated GPU env-map bake test that fails headless — no networking references.
  • All 10 codegen coverage guards pass (ComponentTupleCoverage, …HandlerCoverage, …SerializerCoverage, SaveGameComponentSerializerCoverage).

Note for reviewers

The two helper structs are deliberately not named *Component (InterpolationEntry, SnapshotComponentData) — OloHeaderTools struct …Component scan would otherwise sweep them into the generated AllComponents / SaveGame / SceneSerializer .inl and break the build. Documented as a CLAUDE.md pitfall.

🤖 Generated with Claude Code
@

Summary by CodeRabbit

  • New Features

    • Added component-based network snapshotting and interpolation for replicated entities, with smoother client-side updates across multiple component types.
    • Added support for animation state replication over the network.
    • Expanded remote-client smoothing for physics-driven entities, including rotation and movement.
  • Bug Fixes

    • Improved validation of incoming network data to reduce crashes from invalid or corrupted state.
    • Added guidance to avoid accidental build issues from helper types being treated as components.
  • Tests

    • Added coverage for registry behavior, snapshot round-tripping, and smooth interpolation on remote clients.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@drsnuggles8, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 14 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bbe0e5a5-4862-46cf-85e7-42912c9355d2

📥 Commits

Reviewing files that changed from the base of the PR and between 78c8fc5 and ad19d28.

📒 Files selected for processing (5)
  • OloEngine/src/OloEngine/Networking/Replication/ComponentInterpolationRegistry.cpp
  • OloEngine/src/OloEngine/Networking/Replication/ComponentReplicator.cpp
  • OloEngine/src/OloEngine/Networking/Replication/EntitySnapshot.cpp
  • OloEngine/tests/Networking/ComponentInterpolationRegistryTest.cpp
  • OloEngine/tests/Networking/ComponentReplicatorTest.cpp
📝 Walkthrough

Walkthrough

Replaces hardcoded TransformComponent-only interpolation and reconciliation smoothing with a pluggable ComponentInterpolationRegistry that stores type-erased InterpolationEntry records per component (lerp/slerp/step policies). EntitySnapshot, SnapshotInterpolator, and ClientPrediction::Reconcile are all updated to use the registry. AnimationStateComponent gains a network serializer. Unit and functional tests are added.

Changes

Generalized Component Interpolation Registry

Layer / File(s) Summary
Registry contract: types, InterpolationEntry, HashName
OloEngine/src/OloEngine/Networking/Replication/ComponentInterpolationRegistry.h
Defines EInterpolationPolicy (Lerp/Slerp/Step), InterpolationEntry with type-erased Has/Capture/Interpolate/Snap/Smooth function pointers, ComponentInterpolationRegistry public API, and constexpr FNV-1a-32 HashName helper.
AnimationStateComponent replicator
OloEngine/src/OloEngine/Networking/Replication/ComponentReplicator.h, ComponentReplicator.cpp
Adds forward declaration and ComponentReplicator::Serialize overload for AnimationStateComponent, serializing animation state, clip index, current time, and play flag with wire-validation on load (clamp negative clip index, sanitize non-finite time).
Registry implementation: per-component handlers and lifecycle
OloEngine/src/OloEngine/Networking/Replication/ComponentInterpolationRegistry.cpp, OloEngine/src/CMakeLists.txt
Implements internal ParseComponent helper; interpolate/snap/smooth handlers for TransformComponent (linear mix + slerp + hard-snap), Rigidbody3DComponent (velocity lerp, discrete field step), and AnimationStateComponent (step-only); lazy EnsureInitialized; and all public registry methods (Register, GetEntries, FindById, FindByName, Clear). Adds files to the engine build.
EntitySnapshot wire format and ParsedSnapshot
OloEngine/src/OloEngine/Networking/Replication/EntitySnapshot.h, EntitySnapshot.cpp
Introduces SnapshotComponentData, SnapshotEntity, ParsedSnapshot types and a Parse method. Rewrites Capture, Parse, Apply, and CaptureDelta to emit and consume [uuid][count]{[id][len][bytes]...} records driven by the registry, with length validation against buffer bounds.
SnapshotInterpolator: registry-driven per-component blending
OloEngine/src/OloEngine/Networking/Replication/SnapshotInterpolator.h, SnapshotInterpolator.cpp
Replaces hardcoded TransformComponent lerp/slerp with FindComponentBytes lookup and per-entry Interpolate/Snap dispatch. Cached state changes from UUID→TransformComponent maps to two ParsedSnapshot members (m_CachedBefore/m_CachedAfter).
ClientPrediction reconciliation: registry-driven smoothing
OloEngine/src/OloEngine/Networking/Prediction/ClientPrediction.cpp
Rewrites ClientPrediction::Reconcile to capture pre-resimulation serialized bytes for all registry entries per predicted entity, re-apply unconfirmed inputs, then invoke InterpolationEntry::Smooth per component, replacing the prior Transform-only blend with hard-snap.
Tests: registry unit and functional interpolation
OloEngine/tests/Networking/ComponentInterpolationRegistryTest.cpp, OloEngine/tests/Functional/Networking/InterpolatedComponentsSmoothOnRemoteClientTest.cpp, OloEngine/tests/CMakeLists.txt
Adds unit tests for default policies, wire id hashing/distinctness, custom registration, snapshot round-trip for Transform+Rigidbody3D+AnimationState, and midpoint interpolation semantics per policy. Adds a functional test asserting interior/monotonic translation and slerped yaw for a physics-driven entity on a client interpolator.
CLAUDE.md pitfall note
CLAUDE.md
Adds a common pitfalls entry warning that helper structs named with a Component suffix are picked up by OloHeaderTool and included in generated ECS bindings, causing build errors.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main netcode change and the registry-based interpolation/reconciliation scope.
Linked Issues check ✅ Passed The PR generalizes interpolation and reconciliation beyond TransformComponent with per-component policies and tests smooth remote playback.
Out of Scope Changes check ✅ Passed The changes stay focused on registry-driven replication, interpolation, reconciliation, and related tests/documentation.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

coderabbitai[bot]

This comment was marked as outdated.

Repository owner deleted a comment from coderabbitai Bot Jun 30, 2026
…mp animation-state enum, guard snapshot field widths
@sonarqubecloud

Copy link
Copy Markdown

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Networking: extend snapshot interpolation/reconciliation beyond TransformComponent

1 participant