Skip to content

Commit 798aaa1

Browse files
author
DavidQ
committed
PR_07_05_NETWORK_PREDICTION
1 parent d47862c commit 798aaa1

5 files changed

Lines changed: 108 additions & 22 deletions

File tree

docs/dev/CODEX_COMMANDS.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,19 @@ MODEL: GPT-5.4
22
REASONING: high
33

44
COMMAND:
5-
Implement PR_07_04_NETWORK_RECONCILIATION.
5+
Implement PR_07_05_NETWORK_PREDICTION.
66

77
Goal:
8-
Add reconciliation to simulation.
8+
Add client-side prediction to simulation.
99

1010
Steps:
11-
1. Define authoritative vs local state.
12-
2. Detect divergence.
13-
3. Apply correction.
11+
1. Define prediction model.
12+
2. Apply prediction for local inputs.
13+
3. Integrate with reconciliation.
1414

1515
Rules:
1616
- no real networking
17-
- no prediction yet
1817
- no behavior changes outside simulation layer
1918

2019
Return ZIP:
21-
<project folder>/tmp/PR_07_04_NETWORK_RECONCILIATION.zip
20+
<project folder>/tmp/PR_07_05_NETWORK_PREDICTION.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_04_NETWORK_RECONCILIATION
1+
PR_07_05_NETWORK_PREDICTION
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[ ] reconciliation model defined
2-
[ ] divergence detection working
3-
[ ] correction applied
1+
[ ] prediction model defined
2+
[ ] prediction applied
3+
[ ] reconciliation integration working
44
[ ] validation complete
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# PR_07_05_NETWORK_PREDICTION
2+
3+
## Purpose
4+
Introduce client-side prediction model for Phase 13 simulation.
5+
6+
## Scope
7+
- prediction logic only
8+
- no real networking
9+
- builds on latency + reconciliation
10+
- no behavior changes outside simulation layer
11+
12+
## Tasks
13+
1. Define prediction model for local inputs.
14+
2. Apply prediction ahead of authoritative confirmation.
15+
3. Integrate with reconciliation corrections.
16+
17+
## Deliverables
18+
- docs/dev/reports/prediction_model.txt
19+
- docs/dev/reports/validation_checklist.txt
20+
21+
## Validation
22+
- prediction smooths perceived latency
23+
- reconciliation corrects drift cleanly
24+
- no regressions
25+
26+
## Output
27+
<project folder>/tmp/PR_07_05_NETWORK_PREDICTION.zip

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

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,13 @@ export default class FakeLoopbackNetworkModel {
4343
this.replicationTick = 0;
4444
this.authoritativeFrame = 0;
4545
this.localFrame = 0;
46+
this.predictedFrame = 0;
47+
this.predictionLeadFrames = 0;
4648
this.lastReconcileAtMs = null;
4749
this.reconciliationCount = 0;
50+
this.predictionCount = 0;
51+
this.lastPredictedSequence = 0;
52+
this.predictionRecords = [];
4853

4954
this.autoPacketTimerSeconds = 0;
5055
this.autoPacketIntervalSeconds = 0.65;
@@ -148,9 +153,34 @@ export default class FakeLoopbackNetworkModel {
148153
oneWayDelayMs
149154
});
150155

156+
this.applyLocalPrediction(sequence, label);
157+
151158
return true;
152159
}
153160

161+
applyLocalPrediction(sequence, label) {
162+
const nextPredicted = this.predictedFrame + 1;
163+
this.predictedFrame = nextPredicted;
164+
this.localFrame = Math.max(this.localFrame, nextPredicted);
165+
this.lastPredictedSequence = Math.max(this.lastPredictedSequence, Number(sequence) || 0);
166+
this.predictionCount += 1;
167+
this.predictionRecords.push({
168+
sequence: Number(sequence) || 0,
169+
frame: nextPredicted,
170+
label: String(label || ""),
171+
timestampMs: Math.round(this.elapsedSeconds * 1000)
172+
});
173+
if (this.predictionRecords.length > 128) {
174+
const trimCount = this.predictionRecords.length - 128;
175+
this.predictionRecords.splice(0, trimCount);
176+
}
177+
178+
this.pushTrace("PREDICTION_APPLIED", {
179+
sequence: Number(sequence) || 0,
180+
predictedFrame: nextPredicted
181+
});
182+
}
183+
154184
updatePhaseProgress(dtSeconds) {
155185
if (this.phase === "disconnected") {
156186
return;
@@ -210,35 +240,60 @@ export default class FakeLoopbackNetworkModel {
210240
}
211241

212242
updateDivergenceState() {
213-
this.authoritativeFrame = Math.max(0, Number(this.replicationTick) || 0);
243+
this.authoritativeFrame = Math.max(0, Number(this.ackedSequence) || 0);
214244
const ackedFloor = Math.max(0, Number(this.ackedSequence) || 0);
215245
if (this.localFrame < ackedFloor) {
216246
this.localFrame = ackedFloor;
217247
}
218-
this.localFrame = clamp(this.localFrame, 0, this.authoritativeFrame);
248+
this.predictedFrame = Math.max(this.predictedFrame, this.localFrame);
249+
this.predictionLeadFrames = Math.max(0, this.localFrame - this.authoritativeFrame);
219250
}
220251

221252
applyReconciliation() {
222253
if (this.phase !== "connected") {
223254
return;
224255
}
225256

226-
const divergence = Math.max(0, this.authoritativeFrame - this.localFrame);
257+
const delta = this.localFrame - this.authoritativeFrame;
227258
const stableWindow = 2;
228-
if (divergence <= stableWindow) {
259+
const maxLead = 6;
260+
261+
if (delta >= 0 && delta <= maxLead) {
262+
this.predictionLeadFrames = delta;
229263
return;
230264
}
231265

232-
const correction = Math.min(2, divergence - stableWindow);
233-
this.localFrame = clamp(this.localFrame + correction, 0, this.authoritativeFrame);
266+
if (delta < 0) {
267+
const correctionForward = Math.min(2, Math.abs(delta));
268+
this.localFrame += correctionForward;
269+
this.predictedFrame = Math.max(this.predictedFrame, this.localFrame);
270+
this.reconciliationCount += 1;
271+
this.lastReconcileAtMs = Math.round(this.elapsedSeconds * 1000);
272+
this.pushTrace("RECONCILE_APPLIED", {
273+
correctionFrames: correctionForward,
274+
correctionDirection: "forward",
275+
authoritativeFrame: this.authoritativeFrame,
276+
localFrame: this.localFrame,
277+
divergenceAfter: this.localFrame - this.authoritativeFrame
278+
});
279+
return;
280+
}
281+
282+
const excess = Math.max(0, delta - maxLead);
283+
const correctionBack = Math.min(2, excess || Math.max(0, delta - stableWindow));
284+
if (correctionBack <= 0) {
285+
return;
286+
}
287+
this.localFrame = Math.max(this.authoritativeFrame, this.localFrame - correctionBack);
234288
this.reconciliationCount += 1;
235289
this.lastReconcileAtMs = Math.round(this.elapsedSeconds * 1000);
236290

237291
this.pushTrace("RECONCILE_APPLIED", {
238-
correctionFrames: correction,
292+
correctionFrames: correctionBack,
293+
correctionDirection: "backward",
239294
authoritativeFrame: this.authoritativeFrame,
240295
localFrame: this.localFrame,
241-
divergenceAfter: Math.max(0, this.authoritativeFrame - this.localFrame)
296+
divergenceAfter: this.localFrame - this.authoritativeFrame
242297
});
243298
}
244299

@@ -261,9 +316,10 @@ export default class FakeLoopbackNetworkModel {
261316
getSnapshot() {
262317
const traceTail = this.traceEvents.slice(-18);
263318

264-
const frameDelta = Math.max(0, this.authoritativeFrame - this.localFrame);
265-
const divergenceStatus = frameDelta > 8 ? "diverged" : (frameDelta > 2 ? "drifting" : "stable");
266-
const divergenceScore = frameDelta > 4 ? "warning" : "ok";
319+
const frameDelta = this.localFrame - this.authoritativeFrame;
320+
const absoluteDelta = Math.abs(frameDelta);
321+
const divergenceStatus = absoluteDelta > 8 ? "diverged" : (absoluteDelta > 2 ? "drifting" : "stable");
322+
const divergenceScore = absoluteDelta > 4 ? "warning" : "ok";
267323

268324
return {
269325
sessionId: this.sessionId,
@@ -288,6 +344,10 @@ export default class FakeLoopbackNetworkModel {
288344
backlog: this.replicationBacklog,
289345
authoritativeFrame: this.authoritativeFrame,
290346
localFrame: this.localFrame,
347+
predictedFrame: this.predictedFrame,
348+
predictionLeadFrames: this.predictionLeadFrames,
349+
predictionCount: this.predictionCount,
350+
lastPredictedSequence: this.lastPredictedSequence,
291351
reconciliationCount: this.reconciliationCount,
292352
lastReconcileAtMs: this.lastReconcileAtMs
293353
},

0 commit comments

Comments
 (0)