Skip to content

Commit f6d4896

Browse files
author
DavidQ
committed
PR_07_03_NETWORK_LATENCY_MODELING
1 parent 5e4c288 commit f6d4896

6 files changed

Lines changed: 105 additions & 35 deletions

File tree

docs/dev/CODEX_COMMANDS.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@ MODEL: GPT-5.4
22
REASONING: medium
33

44
COMMAND:
5-
Implement PR_07_02_NETWORK_SIMULATION_BASELINE.
5+
Implement PR_07_03_NETWORK_LATENCY_MODELING.
66

77
Goal:
8-
Establish deterministic simulation baseline.
8+
Add latency modeling to simulation.
99

1010
Steps:
11-
1. Define simulation tick model.
12-
2. Ensure deterministic update loop.
13-
3. Validate consistency.
11+
1. Define latency model (delay/jitter).
12+
2. Inject into simulation inputs.
13+
3. Validate behavior.
1414

1515
Rules:
1616
- no networking
17-
- no latency modeling
18-
- no behavior changes
17+
- no reconciliation
18+
- no behavior changes outside latency simulation
1919

2020
Return ZIP:
21-
<project folder>/tmp/PR_07_02_NETWORK_SIMULATION_BASELINE.zip
21+
<project folder>/tmp/PR_07_03_NETWORK_LATENCY_MODELING.zip

docs/dev/COMMIT_COMMENT.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
PR_07_02_NETWORK_SIMULATION_BASELINE
1+
PR_07_03_NETWORK_LATENCY_MODELING
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
[ ] simulation tick model defined
2-
[ ] deterministic loop confirmed
3-
[ ] consistency validated
1+
[ ] latency model defined
2+
[ ] latency injection applied
3+
[ ] validation complete
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# PR_07_03_NETWORK_LATENCY_MODELING
2+
3+
## Purpose
4+
Introduce latency modeling for Phase 13 simulation.
5+
6+
## Scope
7+
- latency simulation only
8+
- no real networking
9+
- no reconciliation yet
10+
- no behavior changes
11+
12+
## Tasks
13+
1. Define latency injection model (delay, jitter).
14+
2. Apply latency to simulation inputs/events.
15+
3. Validate delayed state propagation.
16+
17+
## Deliverables
18+
- docs/dev/reports/latency_model.txt
19+
- docs/dev/reports/validation_checklist.txt
20+
21+
## Validation
22+
- latency effects observable
23+
- deterministic baseline preserved when disabled
24+
- no regressions
25+
26+
## Output
27+
<project folder>/tmp/PR_07_03_NETWORK_LATENCY_MODELING.zip

samples/phase-13/1316/game/FakeLoopbackNetworkModel.js

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ FakeLoopbackNetworkModel.js
77

88
import { clamp } from "/src/engine/utils/index.js";
99
import { asPositiveNumber } from '../../../_shared/numberUtils.js';
10+
import { createLatencyModel } from "/samples/phase-13/_shared/latencyModel.js";
1011

1112
const MAX_TRACE_EVENTS = 80;
1213

@@ -43,7 +44,12 @@ export default class FakeLoopbackNetworkModel {
4344

4445
this.autoPacketTimerSeconds = 0;
4546
this.autoPacketIntervalSeconds = 0.65;
46-
this.lastAckTimerSeconds = 0;
47+
this.inFlightPackets = [];
48+
this.latencyModel = createLatencyModel({
49+
baseRttMs: 30,
50+
jitterMs: 4,
51+
minOneWayMs: 8
52+
});
4753

4854
this.traceEvents = [];
4955

@@ -92,7 +98,7 @@ export default class FakeLoopbackNetworkModel {
9298
if (normalizedNext === "disconnected") {
9399
this.pendingPackets = 0;
94100
this.replicationBacklog = 0;
95-
this.lastAckTimerSeconds = 0;
101+
this.inFlightPackets = [];
96102
}
97103
}
98104

@@ -122,11 +128,20 @@ export default class FakeLoopbackNetworkModel {
122128
this.sentPackets += 1;
123129
this.pendingPackets += 1;
124130
this.replicationTick += 1;
131+
const oneWayDelayMs = this.latencyModel.sampleOneWayDelayMs(this.elapsedSeconds, sequence);
132+
this.inFlightPackets.push({
133+
sequence,
134+
label,
135+
deliverAtSeconds: this.elapsedSeconds + (oneWayDelayMs / 1000),
136+
oneWayDelayMs
137+
});
138+
this.inFlightPackets.sort((left, right) => left.deliverAtSeconds - right.deliverAtSeconds);
125139

126140
this.pushTrace("PACKET_SENT", {
127141
label,
128142
sequence,
129-
pendingPackets: this.pendingPackets
143+
pendingPackets: this.pendingPackets,
144+
oneWayDelayMs
130145
});
131146

132147
return true;
@@ -162,37 +177,32 @@ export default class FakeLoopbackNetworkModel {
162177
this.jitterMs = 0;
163178
return;
164179
}
165-
166-
const waveform = Math.sin(this.elapsedSeconds * 1.8);
167-
const harmonic = Math.sin(this.elapsedSeconds * 0.45);
168-
this.rttMs = Math.round(30 + (waveform * 7) + (harmonic * 3));
169-
this.rttMs = clamp(this.rttMs, 16, 70);
170-
171-
this.jitterMs = Math.round(Math.abs(Math.cos(this.elapsedSeconds * 2.2)) * 4);
172-
this.jitterMs = clamp(this.jitterMs, 1, 8);
180+
const snapshot = this.latencyModel.sampleSnapshot(this.elapsedSeconds, this.nextSequence);
181+
this.rttMs = snapshot.rttMs;
182+
this.jitterMs = snapshot.jitterMs;
173183
}
174184

175-
updateAcks(dtSeconds) {
185+
updateAcks() {
176186
if (this.phase !== "connected") {
177187
return;
178188
}
179-
180-
this.lastAckTimerSeconds += dtSeconds;
181-
const ackInterval = 0.22 + (this.jitterMs / 1000);
182-
183-
if (this.pendingPackets > 0 && this.lastAckTimerSeconds >= ackInterval) {
184-
this.lastAckTimerSeconds = 0;
185-
this.pendingPackets -= 1;
186-
this.ackedSequence += 1;
189+
while (this.inFlightPackets.length > 0 && this.inFlightPackets[0].deliverAtSeconds <= this.elapsedSeconds) {
190+
const delivered = this.inFlightPackets.shift();
191+
if (!delivered) break;
192+
if (this.pendingPackets > 0) {
193+
this.pendingPackets -= 1;
194+
}
195+
this.ackedSequence = Math.max(this.ackedSequence, Number(delivered.sequence) || 0);
187196
this.receivedPackets += 1;
188197

189198
this.pushTrace("PACKET_ACKED", {
190199
sequence: this.ackedSequence,
191-
pendingPackets: this.pendingPackets
200+
pendingPackets: this.pendingPackets,
201+
oneWayDelayMs: Number(delivered.oneWayDelayMs || 0)
192202
});
193203
}
194204

195-
this.replicationBacklog = Math.max(0, this.pendingPackets - 1);
205+
this.replicationBacklog = Math.max(0, this.inFlightPackets.length);
196206
}
197207

198208
update(dtSeconds, options = {}) {
@@ -206,7 +216,7 @@ export default class FakeLoopbackNetworkModel {
206216

207217
this.updatePhaseProgress(safeDt);
208218
this.updateLatencySnapshot();
209-
this.updateAcks(safeDt);
219+
this.updateAcks();
210220
}
211221

212222
getSnapshot() {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { clamp } from "/src/engine/utils/index.js";
2+
3+
export function createLatencyModel(options = {}) {
4+
const baseRttMs = clamp(Number(options.baseRttMs) || 30, 16, 250);
5+
const jitterMs = clamp(Number(options.jitterMs) || 4, 0, 40);
6+
const minOneWayMs = clamp(Number(options.minOneWayMs) || 8, 1, 200);
7+
8+
function sampleOneWayDelayMs(elapsedSeconds, sequence = 0) {
9+
const t = Math.max(0, Number(elapsedSeconds) || 0);
10+
const seq = Math.max(0, Number(sequence) || 0);
11+
const phase = t * 1.9 + (seq * 0.17);
12+
const harmonic = Math.sin(phase) * jitterMs;
13+
const microJitter = Math.sin((t * 0.53) + (seq * 0.11)) * (jitterMs * 0.5);
14+
const oneWayMs = (baseRttMs * 0.5) + harmonic + microJitter;
15+
return clamp(Math.round(oneWayMs), minOneWayMs, 200);
16+
}
17+
18+
function sampleSnapshot(elapsedSeconds, sequence = 0) {
19+
const oneWayMs = sampleOneWayDelayMs(elapsedSeconds, sequence);
20+
const estimatedRttMs = clamp(Math.round(oneWayMs * 2), 0, 400);
21+
const estimatedJitterMs = clamp(Math.round(Math.abs(oneWayMs - (baseRttMs * 0.5))), 0, 120);
22+
return {
23+
oneWayMs,
24+
rttMs: estimatedRttMs,
25+
jitterMs: estimatedJitterMs
26+
};
27+
}
28+
29+
return {
30+
sampleOneWayDelayMs,
31+
sampleSnapshot
32+
};
33+
}

0 commit comments

Comments
 (0)