Skip to content

Added Unreliable mode to Prediction#48

Open
feiyuren233 wants to merge 5 commits into
PurrNet:devfrom
feiyuren233:dev
Open

Added Unreliable mode to Prediction#48
feiyuren233 wants to merge 5 commits into
PurrNet:devfrom
feiyuren233:dev

Conversation

@feiyuren233
Copy link
Copy Markdown

@feiyuren233 feiyuren233 commented Apr 29, 2026

Add unreliable frame delivery mode

Problem:

Frames are sent on Channel.ReliableOrdered. A dropped packet stalls the entire stream until retransmission, producing visible freeze-then-jump lag on remotes — even though newer frames have already
arrived.

Solution:

Added UnreliableSequenced frame delivery mode so that packet losses are better tolerated

Changes:

  • New FrameChannelMode (Reliable / Unreliable) on PredictionManager. Reliable keeps existing baselined delta encoding (WriteReliable/ReadReliable); Unreliable sends self-contained frames over
    Channel.UnreliableSequenced (Write/Read, no baseline).
  • SendFrameToRemote splits into two [TargetRpc]s routed by mode.
  • State/input write+read paths take a bool reliable parameter selecting the delta path.
  • DeterministicIdentity gains opt-in _syncDeterministicData: under unreliable mode, syncs full state per tick instead of relying on lockstep (which desyncs under loss). Trades bandwidth +
    determinism guarantees for loss resilience.
  • PredictionManagerEditor surfaces the tradeoff: warning when sync is on, error when off in unreliable mode
  • DeltaKey / DeltaKey<T,S> lifted to top-level so DeterministicIdentity can reuse them.
  • Rollback fix: RollbackToFrame(..., inPlaceTick, verifiedTick) → RollbackToFrame(..., verifiedTick, verifiedTick).
image

Summary by CodeRabbit

  • New Features

    • Frame channel mode to select reliable or unreliable frame delivery.
    • Inspector UI: contextual warnings and read-only guidance for deterministic sync settings.
  • Improvements

    • Deterministic-data syncing now follows transport and configured options.
    • State, module and input transmission adapt between reliable and unreliable modes, including delta-based syncing to reduce bandwidth and improve rollback/validation behavior.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d4bc396-5e62-4ccc-a808-002178756950

📥 Commits

Reviewing files that changed from the base of the PR and between 92b6d87 and 1d5d33c.

📒 Files selected for processing (8)
  • Assets/PurrDiction/Runtime/Core/DeterministicIdentity.cs
  • Assets/PurrDiction/Runtime/Core/PredictedIdentity.Lifecycle.cs
  • Assets/PurrDiction/Runtime/Core/PredictedIdentity.Module.cs
  • Assets/PurrDiction/Runtime/Core/PredictedIdentity.cs
  • Assets/PurrDiction/Runtime/Core/PredictedIdentityStatefull.cs
  • Assets/PurrDiction/Runtime/Core/PredictedModule.cs
  • Assets/PurrDiction/Runtime/Core/PredictedModuleState.cs
  • Assets/PurrDiction/Runtime/Core/PredictionManager.cs

📝 Walkthrough

Walkthrough

Threads a reliable flag through prediction serialization: abstract APIs and lifecycle entry points accept it, modules and identities branch between reliable/unreliable delta-module read/write paths, deterministic identity can optionally sync full state via delta keys, and a custom PredictionManager inspector was added.

Changes

Reliability-aware serialization

Layer / File(s) Summary
Abstract APIs & lifecycle propagation
Assets/PurrDiction/Runtime/Core/PredictedIdentity.cs, Assets/PurrDiction/Runtime/Core/PredictedIdentity.Lifecycle.cs, Assets/PurrDiction/Runtime/Core/PredictedIdentity.Module.cs, Assets/PurrDiction/Runtime/Core/PredictedModule.cs
Add a bool reliable parameter to abstract WriteCurrentState/ReadState, RunWriteCurrentState/RunReadState, WriteModules/ReadModules, and WriteStateInternal/ReadStateInternal, and propagate the flag to module-level calls.
Module/state serialization branching
Assets/PurrDiction/Runtime/Core/PredictedModuleState.cs, Assets/PurrDiction/Runtime/Core/PredictedIdentityStatefull.cs
Implement reliability-aware serialization: use deltaModule.WriteReliable/ReadReliable when reliable==true (with changed-flag and bit rewind semantics) and deltaModule.Write/Read when reliable==false. Removed in-file DeltaKey structs and use internal/state key helpers.
Deterministic identity delta sync
Assets/PurrDiction/Runtime/Core/DeterministicIdentity.cs
isDeterministic becomes conditional on a new syncData check; when enabled, write/read serialize both predicted and actual state using delta-module keys and report bandwidth metrics. Method signatures updated to accept reliable.

Editor & Config

Layer / File(s) Summary
Editor Interface
Assets/PurrDiction/Editor/PredictionManagerEditor.cs, Assets/PurrDiction/Editor/PredictionManagerEditor.cs.meta
Adds a custom Inspector for PredictionManager showing read-only properties, conditionally drawing/enabling _syncDeterministicData and _validateDeterministicData with contextual help or error boxes based on frame channel mode.
Configuration & Dispatch
Assets/PurrDiction/Runtime/Core/PredictionManager.cs
Adds FrameChannelMode enum and isReliable/syncDeterministicData properties; dispatches frames over reliable vs unreliable RPC channels and threads isReliable into state/input write/read calls and rollback processing.

Sequence Diagram

sequenceDiagram
    participant PM as PredictionManager
    participant RPC as SendFrameToRemote (dispatcher)
    participant RelCh as SendFrameToRemoteReliable
    participant UnrelCh as SendFrameToRemoteUnreliable
    participant Identity as PredictedIdentity
    participant DeltaMod as DeltaModule

    PM->>RPC: SendFrame(isReliable)
    alt isReliable == true
        RPC->>RelCh: ReliableOrdered RPC
        RelCh->>PM: Enqueue delta (clientTick)
    else isReliable == false
        RPC->>UnrelCh: UnreliableSequenced RPC
        UnrelCh->>PM: Enqueue delta (clientTick)
    end

    Identity->>DeltaMod: ReadState(..., reliable)
    alt reliable == true
        DeltaMod->>DeltaMod: ReadReliable() with changed-flag
    else
        DeltaMod->>DeltaMod: Read() with changed semantics
    end
    DeltaMod-->>Identity: Deserialized state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PurrNet/PurrDiction#24: Updates PredictedModule/PredictedModuleState APIs with reliability-aware signatures and delta serialization logic.
  • PurrNet/PurrDiction#19: Modifies deterministic identity serialization and related input/delta key logic intersecting with this PR.

Suggested labels

released on @dev, `released on `@latest

Poem

🐰 I hopped through bits and channels bright,

Reliable beats and lossy flight,
Delta keys tucked in my paw,
Flags that tell which path to draw,
A rabbit cheers the network's night.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 'Added Unreliable mode to Prediction' directly and clearly describes the main feature addition—unreliable frame delivery mode for the prediction system.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
Assets/PurrDiction/Runtime/Core/DeltaKey.cs (1)

8-9: Rename the new private fields to _camelCase.

id and scene in both structs don't follow the repo's C# private-field convention. Renaming them to _id / _scene will keep these new core types consistent with the rest of the runtime code.

As per coding guidelines "Private fields must use _camelCase prefix naming convention".

Also applies to: 33-34

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Assets/PurrDiction/Runtime/Core/DeltaKey.cs` around lines 8 - 9, The private
readonly fields named id and scene in the structs (types PredictedComponentID
and SceneID defined in DeltaKey.cs) must be renamed to follow the repo
convention: change id -> _id and scene -> _scene; update all references
(constructors, property getters, Equals/GetHashCode/ToString implementations,
and any usage within the same file) to use the new names so compilation and
behavior remain identical. Ensure the fields remain readonly and visibility
unchanged, and run a quick compile to catch any missed references.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Assets/PurrDiction/Runtime/Core/PredictedModule.cs`:
- Around line 100-112: The new bool reliable parameter on the protected abstract
methods breaks external subclasses; restore the original protected abstract
signatures WriteState(PlayerID, BitPacker, DeltaModule) and ReadState(ulong,
BitPacker, DeltaModule) so existing implementations keep compiling, then add new
protected virtual overloads WriteState(PlayerID, BitPacker, DeltaModule, bool
reliable) and ReadState(ulong, BitPacker, DeltaModule, bool reliable) that by
default call the original abstract methods (ignoring reliable) to provide the
extended behavior; update the internal entry points WriteStateInternal and
ReadStateInternal to call the new overloads so internal code can pass reliable
without forcing a breaking change on subclasses.

In `@Assets/PurrDiction/Runtime/Core/PredictionManager.cs`:
- Around line 913-917: The queued frame payloads are being serialized with the
sender's isReliable but deserialized later using the receiver's current
isReliable, which can corrupt parsing; fix this by adding a reliability flag to
the FrameDelta payload (e.g., add a reliable boolean/member to FrameDelta when
created in
SendFrameToRemote/SendFrameToRemoteReliable/SendFrameToRemoteUnreliable) and
propagate that flag through RollbackToFrame, RunReadState, and ReadInput (use
the passed-in reliable value instead of reading PredictionManager.isReliable) so
the same FrameChannelMode used when writing is used when reading; update the
dequeue/parse code paths that currently access FrameDelta.packer and clientTick
to also read FrameDelta.reliable and use it to select the correct delta read
path.

In `@Assets/PurrDiction/Runtime/UnityPhysics/PredictedRigidbody.cs`:
- Around line 136-145: The delta key is constructed with
predictionManager.sceneId when writing (in the paths creating
DeltaKey<UnityRigidbodyCompressedState> and DeltaKey<UnityRigidbodyHalfState>)
but read back using sceneId elsewhere, causing mismatched hashes; update the
write and/or read paths so both use the same source (either always
predictionManager.sceneId or always the local sceneId variable) for DeltaKey<T>
construction (affecting DeltaKey<UnityRigidbodyCompressedState>,
DeltaKey<UnityRigidbodyHalfState> and their corresponding read paths) so the
keys match across Write/WriteReliable and Read.

---

Nitpick comments:
In `@Assets/PurrDiction/Runtime/Core/DeltaKey.cs`:
- Around line 8-9: The private readonly fields named id and scene in the structs
(types PredictedComponentID and SceneID defined in DeltaKey.cs) must be renamed
to follow the repo convention: change id -> _id and scene -> _scene; update all
references (constructors, property getters, Equals/GetHashCode/ToString
implementations, and any usage within the same file) to use the new names so
compilation and behavior remain identical. Ensure the fields remain readonly and
visibility unchanged, and run a quick compile to catch any missed references.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: df6bd57a-4f2d-419f-89f1-81d080807c94

📥 Commits

Reviewing files that changed from the base of the PR and between be18984 and 92b6d87.

📒 Files selected for processing (17)
  • Assets/PurrDiction/Editor/PredictionManagerEditor.cs
  • Assets/PurrDiction/Editor/PredictionManagerEditor.cs.meta
  • Assets/PurrDiction/Runtime/Core/DeltaKey.cs
  • Assets/PurrDiction/Runtime/Core/DeltaKey.cs.meta
  • Assets/PurrDiction/Runtime/Core/DeterministicIdentity.cs
  • Assets/PurrDiction/Runtime/Core/DeterministicIdentityWithInput.cs
  • Assets/PurrDiction/Runtime/Core/PredictedIdentity.Lifecycle.cs
  • Assets/PurrDiction/Runtime/Core/PredictedIdentity.Module.cs
  • Assets/PurrDiction/Runtime/Core/PredictedIdentity.cs
  • Assets/PurrDiction/Runtime/Core/PredictedIdentityStatefull.cs
  • Assets/PurrDiction/Runtime/Core/PredictedModule.cs
  • Assets/PurrDiction/Runtime/Core/PredictedModuleState.cs
  • Assets/PurrDiction/Runtime/Core/PredictionManager.cs
  • Assets/PurrDiction/Runtime/Core/StatelessPredictedIdentity.cs
  • Assets/PurrDiction/Runtime/Core/StaticPredictedIdentity.cs
  • Assets/PurrDiction/Runtime/Transform/PredictedTransform.cs
  • Assets/PurrDiction/Runtime/UnityPhysics/PredictedRigidbody.cs
💤 Files with no reviewable changes (1)
  • Assets/PurrDiction/Runtime/Core/DeterministicIdentityWithInput.cs

Comment on lines +100 to +112
internal bool WriteStateInternal(PlayerID receiver, BitPacker packer, DeltaModule deltaModule, bool reliable) => WriteState(receiver, packer, deltaModule, reliable);

/// <summary>
/// Serializes the current state of the module for network transmission.
/// </summary>
/// <returns>True if any data was written (i.e., state changed), otherwise False.</returns>
protected abstract bool WriteState(PlayerID receiver, BitPacker packer, DeltaModule deltaModule);
internal void ReadStateInternal(ulong tick, BitPacker packer, DeltaModule deltaModule) => ReadState(tick, packer, deltaModule);
protected abstract bool WriteState(PlayerID receiver, BitPacker packer, DeltaModule deltaModule, bool reliable);
internal void ReadStateInternal(ulong tick, BitPacker packer, DeltaModule deltaModule, bool reliable) => ReadState(tick, packer, deltaModule, reliable);

/// <summary>
/// Deserializes incoming network data and applies it to the module's state.
/// </summary>
protected abstract void ReadState(ulong tick, BitPacker packer, DeltaModule deltaModule);
protected abstract void ReadState(ulong tick, BitPacker packer, DeltaModule deltaModule, bool reliable);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and inspect the full PredictedModule.cs file
fd -t f "PredictedModule\.cs$" -x wc -l {}

Repository: PurrNet/PurrDiction

Length of output: 120


🏁 Script executed:

# Get the actual file to see the class declaration and method signatures
fd -t f "PredictedModule\.cs$" -x cat -n {}

Repository: PurrNet/PurrDiction

Length of output: 8387


🏁 Script executed:

# Check for any version or changelog files that might document breaking changes
fd -t f "CHANGELOG\|VERSION\|ReleaseNotes" -i

Repository: PurrNet/PurrDiction

Length of output: 45


🏁 Script executed:

# Search for references to breaking changes or migration guides
rg -i "breaking|migration|version" --max-count 20 -t cs

Repository: PurrNet/PurrDiction

Length of output: 27936


🏁 Script executed:

# Check if PredictedModule is sealed or if there are other constraints
rg "class PredictedModule|abstract.*PredictedModule" -A 5 -B 2 -t cs

Repository: PurrNet/PurrDiction

Length of output: 1545


This breaks the public PredictedModule extension contract.

Adding bool reliable to the protected abstract WriteState and ReadState method signatures will cause any external PredictedModule subclass to stop compiling. Since PredictedModule is explicitly public abstract, custom module implementations are a supported extension point and this source-breaking change requires an explicit migration path or release-note callout.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Assets/PurrDiction/Runtime/Core/PredictedModule.cs` around lines 100 - 112,
The new bool reliable parameter on the protected abstract methods breaks
external subclasses; restore the original protected abstract signatures
WriteState(PlayerID, BitPacker, DeltaModule) and ReadState(ulong, BitPacker,
DeltaModule) so existing implementations keep compiling, then add new protected
virtual overloads WriteState(PlayerID, BitPacker, DeltaModule, bool reliable)
and ReadState(ulong, BitPacker, DeltaModule, bool reliable) that by default call
the original abstract methods (ignoring reliable) to provide the extended
behavior; update the internal entry points WriteStateInternal and
ReadStateInternal to call the new overloads so internal code can pass reliable
without forcing a breaking change on subclasses.

Comment on lines +913 to +917
private void SendFrameToRemote(PlayerID player, ulong localTick, BitPackerWithLength delta)
{
if (isReliable) SendFrameToRemoteReliable(player, localTick, delta);
else SendFrameToRemoteUnreliable(player, localTick, delta);
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Store the frame's reliability mode with the queued payload.

These RPCs choose the wire format from the sender's isReliable, but RollbackToFrame(...) later parses the queued bytes using the receiver's current isReliable. FrameDelta only keeps packer and clientTick, so a server/client FrameChannelMode mismatch — or any mode change before dequeue — will deserialize the same payload with the wrong delta path and corrupt state/input parsing.

Suggested fix
 struct FrameDelta : IDisposable
 {
     public BitPacker packer;
     public ulong clientTick;
+    public bool reliable;

     public void Dispose()
     {
         packer?.Dispose();
     }
 }
 private void SendFrameToRemoteReliable([UsedImplicitly] PlayerID player, ulong localTick, BitPackerWithLength delta)
 {
     delta.packer.SkipBytes(delta.originalLength);
     _deltas.Enqueue(new FrameDelta
     {
         packer = delta.packer,
-        clientTick = localTick
+        clientTick = localTick,
+        reliable = true
     });
 }

 private void SendFrameToRemoteUnreliable([UsedImplicitly] PlayerID player, ulong localTick, BitPackerWithLength delta)
 {
     delta.packer.SkipBytes(delta.originalLength);
     _deltas.Enqueue(new FrameDelta
     {
         packer = delta.packer,
-        clientTick = localTick
+        clientTick = localTick,
+        reliable = false
     });
 }

Then thread previousFrame.reliable through RollbackToFrame(...), RunReadState(...), and ReadInput(...) instead of re-reading PredictionManager.isReliable.

Also applies to: 919-939, 964-978

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Assets/PurrDiction/Runtime/Core/PredictionManager.cs` around lines 913 - 917,
The queued frame payloads are being serialized with the sender's isReliable but
deserialized later using the receiver's current isReliable, which can corrupt
parsing; fix this by adding a reliability flag to the FrameDelta payload (e.g.,
add a reliable boolean/member to FrameDelta when created in
SendFrameToRemote/SendFrameToRemoteReliable/SendFrameToRemoteUnreliable) and
propagate that flag through RollbackToFrame, RunReadState, and ReadInput (use
the passed-in reliable value instead of reading PredictionManager.isReliable) so
the same FrameChannelMode used when writing is used when reading; update the
dequeue/parse code paths that currently access FrameDelta.packer and clientTick
to also read FrameDelta.reliable and use it to select the correct delta read
path.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isReliable is set at build time and will not be changed at runtime.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@feiyuren233, understood — since _frameChannelMode is marked [PurrLock] and fixed at configuration time, both the write path and the read path will always observe the same isReliable value for the lifetime of the session. The concern about a mid-flight mismatch doesn't apply here. Thanks for the clarification!


✏️ Learnings added
Learnt from: feiyuren233
Repo: PurrNet/PurrDiction PR: 48
File: Assets/PurrDiction/Runtime/Core/PredictionManager.cs:913-917
Timestamp: 2026-04-29T04:57:43.438Z
Learning: In PurrDiction (`Assets/PurrDiction/Runtime/Core/PredictionManager.cs`), `_frameChannelMode` (and thus `isReliable`) is a `[SerializeField, PurrLock]` field set at build/configuration time and is immutable at runtime. It is safe to read `isReliable` at both enqueue and dequeue sites without storing it per-frame.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines 136 to +145
var key = new DeltaKey<UnityRigidbodyCompressedState>(predictionManager.sceneId, id);
return deltaModule.WriteReliable(packer, target, key, new UnityRigidbodyCompressedState(currentState));
var value = new UnityRigidbodyCompressedState(currentState);
return reliable
? deltaModule.WriteReliable(packer, target, key, value)
: deltaModule.Write(packer, target, key, value);
}
case FloatAccuracy.Low:
{
var key = new DeltaKey<UnityRigidbodyHalfState>(predictionManager.sceneId, id);
var res = deltaModule.WriteReliable(packer, target, key, new UnityRigidbodyHalfState(currentState));
return res;
var value = new UnityRigidbodyHalfState(currentState);
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use the same scene source for the delta key on both paths.

Lines 136 and 145 build the key with predictionManager.sceneId, but Lines 163 and 177 read it back with sceneId. Those hashes need to stay identical across write/read; if those values ever diverge during setup, pooling, or scene moves, rigidbody deltas will stop matching.

Suggested fix
-                    var key = new DeltaKey<UnityRigidbodyCompressedState>(predictionManager.sceneId, id);
+                    var key = new DeltaKey<UnityRigidbodyCompressedState>(sceneId, id);
...
-                    var key = new DeltaKey<UnityRigidbodyHalfState>(predictionManager.sceneId, id);
+                    var key = new DeltaKey<UnityRigidbodyHalfState>(sceneId, id);

Also applies to: 163-180

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Assets/PurrDiction/Runtime/UnityPhysics/PredictedRigidbody.cs` around lines
136 - 145, The delta key is constructed with predictionManager.sceneId when
writing (in the paths creating DeltaKey<UnityRigidbodyCompressedState> and
DeltaKey<UnityRigidbodyHalfState>) but read back using sceneId elsewhere,
causing mismatched hashes; update the write and/or read paths so both use the
same source (either always predictionManager.sceneId or always the local sceneId
variable) for DeltaKey<T> construction (affecting
DeltaKey<UnityRigidbodyCompressedState>, DeltaKey<UnityRigidbodyHalfState> and
their corresponding read paths) so the keys match across Write/WriteReliable and
Read.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not introduced by this PR but this is indeed strange... I guess in practice the PRedictedIdentity's sceneId should be the same as the linked predictionManager's sceneId and we don't cross scene pooling or moving? but this divergence is weird and probably should be fixed?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find how sceneId is defined on PredictedIdentity
rg -n "sceneId" --type cs -C3

# Find predictionManager property/field definition on PredictedIdentity
rg -n "predictionManager" --type cs -C2 | grep -A2 -B2 "get\|=>"

Length of output: 20819


@feiyuren233, you're right that in practice they should never diverge. Looking at PredictedIdentity.cs:

// Line 102-103 in PredictedIdentity.cs (Setup method)
predictionManager = world;
sceneId = world.sceneId;  // copied from the same PredictionManager

sceneId is set from world.sceneId at Setup() time, has a private set, and there's no scene-move or pooling path visible that would update it independently. So sceneId == predictionManager.sceneId is a permanent invariant in normal use.

That said, PredictedTransform uses sceneId consistently on both write and read paths — PredictedRigidbody is the only outlier using predictionManager.sceneId on the write side. The fix is purely cosmetic but brings it in line with the established pattern and makes the contract explicit:

// WriteDeltaState – Medium
-  var key = new DeltaKey<UnityRigidbodyCompressedState>(predictionManager.sceneId, id);
+  var key = new DeltaKey<UnityRigidbodyCompressedState>(sceneId, id);

// WriteDeltaState – Low  
-  var key = new DeltaKey<UnityRigidbodyHalfState>(predictionManager.sceneId, id);
+  var key = new DeltaKey<UnityRigidbodyHalfState>(sceneId, id);

Worth a one-liner cleanup even if it's pre-existing, since consistency with PredictedTransform is the right pattern to follow. Would you like to track this as a follow-up issue?

(ᵒ ᵕ ᵒ)ノ🐰


🧠 Learnings used
Learnt from: feiyuren233
Repo: PurrNet/PurrDiction PR: 48
File: Assets/PurrDiction/Runtime/Core/PredictionManager.cs:913-917
Timestamp: 2026-04-29T04:57:43.438Z
Learning: In PurrDiction (`Assets/PurrDiction/Runtime/Core/PredictionManager.cs`), `_frameChannelMode` (and thus `isReliable`) is a `[SerializeField, PurrLock]` field set at build/configuration time and is immutable at runtime. It is safe to read `isReliable` at both enqueue and dequeue sites without storing it per-frame.

@feiyuren233 feiyuren233 reopened this May 25, 2026
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.

1 participant