From 4f13eb6689ce4d97644af28e0cc97e23df5f376f Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 30 Sep 2026 00:57:21 +0200 Subject: [PATCH] Ack Cut 3: a direct send behind a queued packet is refused and takes no sequence The FIFO test now also pins the single-packet send's refusal while an earlier packet waits in the queue, in Rust, C#, TypeScript and Python. Co-Authored-By: Claude Sonnet 5.5 --- packages/cultnet-py/tests/test_cultnet.py | 4 ++++ packages/cultnet-rs/tests/rudp_flow_window.rs | 3 +++ packages/cultnet-ts/test/cultnet.test.ts | 3 +++ tests/GameCult.Networking.Tests/NetworkingTests.cs | 3 +++ 4 files changed, 13 insertions(+) diff --git a/packages/cultnet-py/tests/test_cultnet.py b/packages/cultnet-py/tests/test_cultnet.py index 2161be04..e1205fef 100644 --- a/packages/cultnet-py/tests/test_cultnet.py +++ b/packages/cultnet-py/tests/test_cultnet.py @@ -963,6 +963,10 @@ class CultNetTests(unittest.TestCase): g = self._flow_send(sender, 1)[0] self.assertEqual(len(self._flow_send(sender, 3 * self.MIB)), 1) self.assertEqual(len(self._flow_send(sender, 2 * self.MIB)), 0) + # A direct send refuses instead of queueing behind it, and takes no sequence. + with self.assertRaises(ValueError): + sender.send("state", bytes(1), CultNetRudpSendOptions(reliable=True)) + self.assertEqual(sender.queued_reliable_packet_count, 1) # One byte would fit above g, but the 2 MiB packet is ahead of it. self.assertEqual(len(self._flow_send(sender, 1)), 0) promoted = sender.receive(self._flow_ack(g.sequence), 1).ready_to_send diff --git a/packages/cultnet-rs/tests/rudp_flow_window.rs b/packages/cultnet-rs/tests/rudp_flow_window.rs index b288de44..b052834c 100644 --- a/packages/cultnet-rs/tests/rudp_flow_window.rs +++ b/packages/cultnet-rs/tests/rudp_flow_window.rs @@ -164,6 +164,9 @@ fn a_small_packet_does_not_overtake_a_queued_large_one() -> Result<()> { let g = send(&mut sender, 1)?.remove(0); assert_eq!(send(&mut sender, 3 * MIB)?.len(), 1); assert!(send(&mut sender, 2 * MIB)?.is_empty()); + // A direct send refuses instead of queueing behind it, and takes no sequence. + assert!(sender.send("state", vec![7], reliable()).is_err()); + assert_eq!(sender.queued_reliable_packet_count(), 1); // One byte would fit above g, but the 2 MiB packet is ahead of it. assert!(send(&mut sender, 1)?.is_empty()); let promoted = sender.receive(&ack_for(g.sequence), 1)?.ready_to_send; diff --git a/packages/cultnet-ts/test/cultnet.test.ts b/packages/cultnet-ts/test/cultnet.test.ts index 6751b568..a3efa323 100644 --- a/packages/cultnet-ts/test/cultnet.test.ts +++ b/packages/cultnet-ts/test/cultnet.test.ts @@ -1503,6 +1503,9 @@ test("a small packet does not overtake a queued large one", () => { const g = flowSend(sender, 1)[0]!; assert.equal(flowSend(sender, 3 * MIB).length, 1); assert.equal(flowSend(sender, 2 * MIB).length, 0); + // A direct send refuses instead of queueing behind it, and takes no sequence. + assert.throws(() => sender.send("state", new Uint8Array(1), { reliable: true })); + assert.equal(sender.queuedReliablePacketCount, 1); // One byte would fit above g, but the 2 MiB packet is ahead of it. assert.equal(flowSend(sender, 1).length, 0); assert.deepEqual(sequencesOf(sender.receive(flowAck(g.sequence), 1).readyToSend), [g.sequence + 2, g.sequence + 3]); diff --git a/tests/GameCult.Networking.Tests/NetworkingTests.cs b/tests/GameCult.Networking.Tests/NetworkingTests.cs index b9bda3ad..7cdf256f 100644 --- a/tests/GameCult.Networking.Tests/NetworkingTests.cs +++ b/tests/GameCult.Networking.Tests/NetworkingTests.cs @@ -1268,6 +1268,9 @@ namespace GameCult.Networking.Tests var g = FlowSend(sender, 1).Single(); Assert.That(FlowSend(sender, 3 * Mib), Has.Count.EqualTo(1)); Assert.That(FlowSend(sender, 2 * Mib), Is.Empty); + // A direct send refuses instead of queueing behind it, and takes no sequence. + Assert.Throws(() => sender.Send("state", new byte[1], new CultNetRudpSendOptions { Reliable = true })); + Assert.That(sender.QueuedReliablePacketCount, Is.EqualTo(1)); // One byte would fit above g, but the 2 MiB packet is ahead of it. Assert.That(FlowSend(sender, 1), Is.Empty); var promoted = sender.Receive(FlowAck(g.Sequence), 1).ReadyToSend;