Manual Udon sync fails despite successful serialization
えしりあ
Manual synchronization stops updating synced variables on non-owner clients with certain field configurations, despite the owner reporting successful serialization.
In the failing cases, the non-owner receives OnDeserialization, but the synced fields remain at their initial values.
ENVIRONMENT
────────────────────────
Unity: 2022.3.22f1
VRChat Worlds SDK: 3.10.4
Tested with actual VRChat clients in the same instance
STEPS TO REPRODUCE
────────────────────────
- Attach the provided Float20SyncProbe and Double20SyncProbe scripts to separate GameObjects.
- Assign a separate TMP text display to each script.
- Upload the world and join the same instance with two players.
- The owner assigns new values and calls RequestSerialization() every 10 seconds.
- Compare the owner's values with the non-owner's values on the displays and in the logs.
Both scripts use Manual synchronization. Only the owner writes the synced fields. Neither script uses FieldChangeCallback or calls Networking.SetOwner.
Despite their filenames, the attached scripts currently contain:
- Float20SyncProbe: 27 synced floats.
- Double20SyncProbe: 14 synced doubles and 1 synced int.
EXPECTED RESULT
────────────────────────
The non-owner's synced fields should update to match the owner's serialized values.
ACTUAL RESULT
────────────────────────
With the failing configurations, the non-owner's fields remain at their initial values.
Reducing the number of synced fields restores normal synchronization.
TEST RESULTS
────────────────────────
All configurations below were tested without FieldChangeCallback.
2 doubles : Worked
2 floats : Worked
13 doubles : Worked
26 floats : Worked
14 doubles : Worked
27 floats : Failed
14 doubles + 1 int : Failed
20 doubles : Failed
Earlier tests also reproduced the issue with 20 doubles using FieldChangeCallback, while 20 floats worked.
Removing FieldChangeCallback did not resolve the double20 failure.
The results suggest that the amount or layout of synced data may be involved, but the exact cause is unknown.
POSSIBLY RELATED REPORTS
────────────────────────
- A single UdonSharpBehavior with VRC Enable Persistence cannot store more than 108 bytes of the same variable type
- PlayerObject Persistence Fails to Respect Sync Mode
Those reports concern Persistence restoration. This issue occurs during ordinary owner-to-non-owner synchronization within the same instance.
REPRODUCTION MATERIALS
────────────────────────
The attached scripts reproduce the issue independently and display each client's local synced values, receive count, consistency check, and serialization result.
Log In
Nulllll
Reproduced on VRChat build 1921, SDK 3.10.5, using two real Desktop clients.
Root cause: VRChat’s FlatBuffers8 writer accepts unsigned relative offsets up to 255, but Table.__union<T> / Udon.Data<T> sign-extends the byte when calculating the target address. For example, offset 131 becomes −125, placing the read 256 bytes before its intended target.
Real-client evidence, covering late join and live updates:
- 26 floats pass; 27 fail.
- 15 doubles pass; 16 fail.
- 64 bools pass; 65 fail.
- Keeping all 27 floats unchanged and adding only an initialized empty array or a string restores synchronization([UdonSynced] private bool[] extra = new bool[0];). The Manual-mode selector then uses the 32-bit codec.
Suggested fix for official: zero-extend unsigned forward offsets in the affected FlatBuffers8 readers (Get instead of GetSbyte, or equivalent). Audit generated accessors and validators for the same mismatch. Preserve signed table-to-vtable displacements; do not globally replace signed reads.
An interim fix is to route affected Manual payloads through the existing 32-bit codec. The proposed patch has not been tested in a modified client.