Desync while shooting under cover

Before continuing, please make sure that your issue/crash has not been filed before on this forum. If it has, please provide additional information in the existing forum topic by filling out the template there.

To provide valuable feedback for OneSync issues or crashes, please fill out the following template as much as possible.

Client

Using canary? No
Windows version: Windows 10 version 1803, build 17134.829
System specifications:
AMD Ryzen 7 2700x Eight-Core Processor @ 3.7 GHz
32 GB DDR4-2666 MHz of memory
NVidia GeForce GTX 1660 Ti
500 GB SSD

Server

Operating system: Windows Server (Version 10.0.17763.593). Also reproduced on Debian Stretch 9.8.
Artifact version: 1398. Also reproduced on 1387 (linux artifact).
IP address: private servers (N/A)
Resources: No public resources
System specifications:
Intel i7-7700K 4-Core @ 4.2 GHz
10 GB of memory

Incident

Summary: While shooting under cover, all other clients are unable to see or hear the shooting, and only see the ped aiming to shoot. The only one who can see/hear the shooting is the shooter.

Weā€™ve done this a few times under different circumstances, such as under different cover, and the amount of times that Client B hears/sees the shooting varies from 0 to only 1-2 times.

Expected behavior: All clients should be able to see and hear the player shooting.
Actual behavior: See summary.
Steps to reproduce:

  1. Have two clients. Have Client A shoot under normal circumstances 5 times. This should be normal.
  2. Have Client A now shoot while under cover 5 times. This should result in Client B unable to hear most/all shots or see the animation.

Any additional info:

Here is a video demonstration.

I still need to investigate more but after a brief look it seems like the task tree node and task data nodes are out of sync here. Since I already had the code setup, I experimented with manually dirtying the data nodes based on the tree node and that seems to ā€œfixā€ the issue. Next is to check to see if the server or client is causing the nodes to be out of sync.

1 Like

Took another look. Seems data nodes can depend on another data node which affects how nodes are synced. The dependencies in the CPedSyncTree are

//child->parent
CGlobalFlagsDataNode->CEntityScriptInfoDataNode
CPedTaskSpecificDataNode->CPedTaskTreeDataNode
CPedSectorPosMapNode->CSectorDataNode
CPedSectorPosNavMeshNode->CSectorDataNode

This behavior can be seen at the start of CProjectBaseSyncDataNode::ShouldBeWrittenToPlayer. Also looks like CPedTaskTreeDataNode overrides ShouldBeWrittenToPlayer and does some checks on the child data nodes. This is a ā€œcleanedā€ up version of the class I was using to manually dirty the child data nodes.

class netSyncNodeBase
{
public:
	virtual ~netSyncNodeBase() = 0;

public:
	netSyncNodeBase* nextSibling;
	netSyncNodeBase* prevSibling;
	netSyncNodeBase* root;
	netSyncNodeBase* parent;

	uint32_t flags1;
	uint32_t flags2;
	uint32_t flags3;

	uint32_t pad2;

	netSyncNodeBase* firstChild; //ParentNode field
};

class netSyncDataNodeBase : netSyncNodeBase
{
	uint32_t flags;
	uint32_t pad3;
	uint64_t pad4;
	netSyncDataNodeBase* parentData; //0x50
	uint32_t childCount;
	netSyncDataNodeBase* children[8];
	uint8_t syncFrequencies[8];
	void* nodeBuffer;
};

:open_mouth:

What defines these, actually? Sync tree ctors?

That might be problematic since the original functions are very much dependent on netSyncData with its fixed sizes :confused:

AHA! That explains the sector-edge teleporting behavior seen in earlier iterations of 1s.

Ya they are defined in the sync tree ctors. The function which adds the children is at 0x14161A0F0. You can find it referenced in the second loop after initializing the TaskTreeNode.

What are some of the problematic functions? I thought this would just be additional checks to add to WriteTreeCfx on the client. Although on second thought you may also need these checks on the server since Iā€™m guessing child/parent data nodes will be synced together in some cases regardless of frequency, flags, etc.

Also even more fun, CPedTaskSequenceDataNode overrides ShouldBeWrittenToPlayer and checks for some flag on the CTaskUseSequence task.

Technically, yes, but itā€™s a bit of a mess to have ā€˜separateā€™ support for specific node types duplicating game code (as in, other than ā€˜genericā€™ dependent node support).

Ya it would be good to avoid re-implementing node specific syncing. I was thinking it might work to detour the base ShouldBeWrittenToPlayer and implement it based on how WriteTreeCfx determines if a node needs to be synced. Then ShouldBeWrittenToPlayer can be called to cover the nodes which override it. However thatā€™s assuming there is any desync caused by ShouldBeWrittenToPlayer not being called. For example, I had a breakpoint on CPedTaskTreeDataNode::ShouldBeWrittenToPlayer returning true and it never tripped while I was working on this(onesync was disabled, 2 players in session).

Separately syncing the child node when the parent data node is synced would cover these task desync issues and is generic enough. Iā€™ll likely put together a PR this weekend for this piece specifically unless you are planning to take a stab at it.

Righto, yes, this function is actually rage::netSyncDataNode::SetNodeDependency.

Then, thereā€™s also theoretically rage::netSyncDataNode::AddExternalNodeDependency which only adds to the max-8 list but doesnā€™t set a parent, but Iā€™m not sure where these get used. :confused:

Ah, evidently, itā€™s the use of rage::AddNodeAndExternalDependentNodes (1604: 0x1415FFF48) in rage::netSyncTree::Update and such.