GDScript 86 lines
extends GutTest
## The N5 network-condition simulator, exercised without any networking.
##
## NetSim shapes the client's incoming snapshot stream — holding each snapshot a
## base latency plus random jitter, dropping a loss fraction — so the smoothing can
## be exercised under a worse link than the local machine provides. These tests pin
## its contract: identity pass-through, the latency hold, the seeded loss rate,
## the jitter band, release ordering, and that a not-yet-due snapshot stays queued.
## It conditions opaque payloads and takes plain millisecond times, so the whole
## round trip is checked headlessly like the protocol, sim, and interpolation cores.
func test_identity_releases_immediately() -> void:
# No latency, jitter, or loss: a snapshot is due the instant it arrives.
var sim := NetSim.new(0.0, 0.0, 0.0, 1)
assert_true(sim.receive("a", 100.0), "an unconditioned snapshot is always accepted")
var due := sim.drain(100.0)
assert_eq(due.size(), 1, "the snapshot is released at its arrival time")
assert_eq(due[0]["data"], "a")
assert_eq(due[0]["release"], 100.0)
assert_false(sim.has_pending(), "nothing is left queued")
func test_latency_holds_until_due() -> void:
var sim := NetSim.new(50.0, 0.0, 0.0, 1)
sim.receive("a", 100.0)
assert_eq(sim.drain(149.0).size(), 0, "before arrival + latency the snapshot is held")
assert_true(sim.has_pending(), "and stays queued")
var due := sim.drain(150.0)
assert_eq(due.size(), 1, "at arrival + latency it is released")
assert_eq(due[0]["release"], 150.0)
func test_loss_drops_at_its_rate_and_drops_stay_gone() -> void:
# Seeded, so the drop pattern is fixed: at 50% loss over 1000 arrivals the
# delivered count sits near 500, and a dropped snapshot is never released later.
var sim := NetSim.new(0.0, 0.0, 0.5, 12345)
var accepted := 0
for i in range(1000):
if sim.receive(i, float(i)):
accepted += 1
assert_between(accepted, 440, 560, "roughly half the snapshots survive the loss roll")
assert_eq(sim.drain(2000.0).size(), accepted, "only the accepted snapshots are ever released")
func test_jitter_spreads_release_within_the_band() -> void:
# Every snapshot shares one arrival time, so their spread comes purely from jitter:
# each is held the base latency plus a random [0, jitter), never outside that band.
var sim := NetSim.new(100.0, 40.0, 0.0, 7)
for i in range(50):
sim.receive(i, 0.0)
var due := sim.drain(1000.0)
assert_eq(due.size(), 50, "no loss, so all are released once due")
var lowest := INF
var highest := -INF
for packet in due:
lowest = minf(lowest, packet["release"])
highest = maxf(highest, packet["release"])
assert_gte(lowest, 100.0, "never released before the base latency")
assert_lt(highest, 140.0, "never held beyond latency + jitter")
assert_gt(highest - lowest, 0.0, "jitter actually spreads the release times")
func test_drain_returns_due_in_release_order() -> void:
# Released oldest-first regardless of the order they were offered, so the
# downstream buffer sees them in arrival order.
var sim := NetSim.new(0.0, 0.0, 0.0, 1)
sim.receive("c", 30.0)
sim.receive("a", 10.0)
sim.receive("b", 20.0)
var order: Array = []
for packet in sim.drain(100.0):
order.append(packet["data"])
assert_eq(order, ["a", "b", "c"], "drained in ascending release time, not offer order")
func test_a_not_yet_due_snapshot_waits_for_a_later_drain() -> void:
var sim := NetSim.new(0.0, 0.0, 0.0, 1)
sim.receive("a", 10.0)
sim.receive("b", 30.0)
assert_eq(sim.drain(20.0).size(), 1, "only the snapshot already due is released")
assert_eq(sim.drain(20.0).size(), 0, "the released one is not handed out twice")
var later := sim.drain(40.0)
assert_eq(later.size(), 1, "the held snapshot is released once its time comes")
assert_eq(later[0]["data"], "b")