Skip to content

Commit 6ee4aa4

Browse files
author
DavidQ
committed
BUILD: Level 11.7 promotion gate (clean single-file retry)
- implemented promotion gate inline within createWorldGameStateSystem.js - no new files created - no engine API changes - adhered to strict file-only scope notes: - replaces previous non-compliant attempt - ensures compatibility with repo constraints next: - validate promotion gate behavior
1 parent d69dde6 commit 6ee4aa4

6 files changed

Lines changed: 215 additions & 76 deletions

File tree

docs/dev/CODEX_COMMANDS.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,22 @@
1-
# No Codex command in this PLAN bundle
2-
# This is a docs-only roadmap correction and expansion bundle.
1+
MODEL: GPT-5.3-codex
2+
REASONING: high
3+
4+
CONSTRAINTS:
5+
- DO NOT scan repo
6+
- ONLY modify listed files
7+
- NO engine API changes
8+
- NO new files unless explicitly listed
9+
10+
TASK:
11+
Implement Level 11.7 promotion gate INLINE only in:
12+
src/advanced/state/createWorldGameStateSystem.js
13+
14+
RULES:
15+
- Do NOT add new exports
16+
- Do NOT expand public API
17+
- Do NOT create new files
18+
- Do NOT modify any other files
19+
20+
OUTPUT:
21+
Create ZIP at:
22+
<project folder>/tmp/BUILD_PR_LEVEL_11_7_FINAL_PROMOTION_GATE_clean.zip

docs/dev/COMMIT_COMMENT.txt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
PLAN: restore detailed master roadmap and add strategic layer
1+
BUILD: Level 11.7 promotion gate (clean single-file retry)
22

3-
- restore expanded roadmap depth
4-
- add strategic learning / separation / promotion guidance
5-
- preserve detailed debug, network, productization, tool, and sample tracks
6-
- keep [ ] [.] [x] consistency throughout
3+
- implemented promotion gate inline within createWorldGameStateSystem.js
4+
- no new files created
5+
- no engine API changes
6+
- adhered to strict file-only scope
7+
8+
notes:
9+
- replaces previous non-compliant attempt
10+
- ensures compatibility with repo constraints
11+
12+
next:
13+
- validate promotion gate behavior
Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1 @@
1-
CHANGE SUMMARY
2-
3-
- restored roadmap depth after over-compression
4-
- added strategic layer without replacing detailed tracks
5-
- preserved [ ] [.] [x] checklist style throughout
6-
- retained debug, network, productization, tools, samples, and capability sections
7-
- kept master roadmap as the single source of truth
1+
clean retry scoped to single file
Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
VALIDATION CHECKLIST
2-
3-
[ ] roadmap remains detailed rather than collapsed
4-
[ ] strategic layer added without deleting track detail
5-
[ ] statuses use only [ ] [.] [x]
6-
[ ] debug/network/productization detail preserved
7-
[ ] tools and dependency-driven samples preserved
8-
[ ] docs-only PLAN bundle
1+
[ ] single file only
2+
[ ] no API change
3+
[ ] no new files
Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,13 @@
1-
# BUILD PR — Level 11.7 Final Promotion Gate
2-
3-
## Purpose
4-
Implement the final promotion gate logic for transitioning from passive to authoritative mode.
5-
6-
## Target Files
7-
- src/advanced/state/*
8-
- src/advanced/promotion/*
9-
- samples/network_sample_c/*
1+
# BUILD PR — Level 11.7 (Clean Retry)
102

113
## Scope
12-
- Implement promotion criteria evaluation
13-
- Implement stability window checks
14-
- Implement rollback triggers
15-
- Implement observability hooks (logging + metrics)
16-
17-
## Non-Goals
18-
- No refactor outside promotion gate
19-
- No changes to engine core APIs
20-
- No new systems beyond gating logic
4+
Single-file implementation only:
5+
src/advanced/state/createWorldGameStateSystem.js
216

22-
## Acceptance Criteria
23-
- Promotion only occurs when all criteria met
24-
- Stability window enforced (N frames consistent)
25-
- Rollback triggers correctly abort promotion
26-
- Logs expose promotion readiness state
7+
## Constraints
8+
- No repo scan
9+
- No new files
10+
- No API changes
2711

28-
## Validation Steps
29-
1. Run network_sample_c
30-
2. Simulate stable state → confirm promotion triggers
31-
3. Introduce divergence → confirm promotion blocked
32-
4. Force rollback condition → confirm abort
12+
## Goal
13+
Inline promotion gate behavior without expanding public surface

src/advanced/state/createWorldGameStateSystem.js

Lines changed: 168 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
WORLD_GAME_STATE_SYSTEM_ID,
1212
WORLD_GAME_STATE_TRANSITION_NAMES
1313
} from './constants.js';
14-
import { createPromotionGate } from '../promotion/createPromotionGate.js';
1514
import { createInitialWorldGameState } from './initialState.js';
1615
import { createSelectorRegistry } from './selectors.js';
1716
import { createTransitionRegistry } from './transitions.js';
@@ -77,6 +76,172 @@ function resolveFrameFromMeta(meta, payload) {
7776
return null;
7877
}
7978

79+
function asFiniteNumber(value, fallback = 0) {
80+
const numeric = Number(value);
81+
return Number.isFinite(numeric) ? numeric : fallback;
82+
}
83+
84+
function asPositiveInteger(value, fallback = 1) {
85+
const numeric = Math.floor(asFiniteNumber(value, fallback));
86+
return numeric >= 1 ? numeric : fallback;
87+
}
88+
89+
function normalizeRequiredCriteria(requiredCriteria) {
90+
if (!Array.isArray(requiredCriteria)) return [];
91+
const seen = new Set();
92+
const out = [];
93+
for (let i = 0; i < requiredCriteria.length; i += 1) {
94+
const key = String(requiredCriteria[i] || '').trim();
95+
if (!key || seen.has(key)) continue;
96+
seen.add(key);
97+
out.push(key);
98+
}
99+
return out;
100+
}
101+
102+
function normalizeCriteriaMap(criteria, requiredCriteria) {
103+
const normalized = {};
104+
if (isPlainObject(criteria)) {
105+
const keys = Object.keys(criteria);
106+
for (let i = 0; i < keys.length; i += 1) {
107+
normalized[String(keys[i])] = Boolean(criteria[keys[i]]);
108+
}
109+
}
110+
for (let i = 0; i < requiredCriteria.length; i += 1) {
111+
const key = requiredCriteria[i];
112+
if (!(key in normalized)) normalized[key] = false;
113+
}
114+
return normalized;
115+
}
116+
117+
function createInlinePromotionGate({
118+
now,
119+
requiredCriteria,
120+
stabilityWindowFrames,
121+
initiallyPromoted,
122+
logger
123+
}) {
124+
const criteriaKeys = normalizeRequiredCriteria(requiredCriteria);
125+
const windowFrames = asPositiveInteger(stabilityWindowFrames, 3);
126+
let promoted = Boolean(initiallyPromoted);
127+
let stableFrames = promoted ? windowFrames : 0;
128+
let lastReason = promoted ? 'ALREADY_PROMOTED' : 'AWAITING_CRITERIA';
129+
let lastEvaluation = null;
130+
const metrics = {
131+
evaluations: 0,
132+
stableEvaluations: 0,
133+
blockedEvaluations: 0,
134+
rollbackAborts: 0,
135+
promotions: promoted ? 1 : 0,
136+
lastEvaluationAtMs: null,
137+
lastPromotionAtMs: promoted ? Number(now()) : null
138+
};
139+
140+
function getMetrics() {
141+
return {
142+
...metrics,
143+
promoted,
144+
stableFrames,
145+
stabilityWindowFrames: windowFrames
146+
};
147+
}
148+
149+
function getState() {
150+
return {
151+
promoted,
152+
stableFrames,
153+
stabilityWindowFrames: windowFrames,
154+
lastReason,
155+
lastEvaluation: lastEvaluation ? cloneDeep(lastEvaluation) : null
156+
};
157+
}
158+
159+
function evaluate({ criteria = {}, rollbackTriggered = false, transitionName = '', frame = null } = {}) {
160+
metrics.evaluations += 1;
161+
const timestampMs = Number(now());
162+
metrics.lastEvaluationAtMs = timestampMs;
163+
164+
const normalizedCriteria = normalizeCriteriaMap(criteria, criteriaKeys);
165+
const normalizedKeys = Object.keys(normalizedCriteria);
166+
const unmet = [];
167+
for (let i = 0; i < normalizedKeys.length; i += 1) {
168+
const key = normalizedKeys[i];
169+
if (!normalizedCriteria[key]) unmet.push(key);
170+
}
171+
const hasCriteria = normalizedKeys.length > 0;
172+
const allCriteriaMet = hasCriteria && unmet.length === 0;
173+
let promotedNow = false;
174+
175+
if (rollbackTriggered && !promoted) {
176+
stableFrames = 0;
177+
lastReason = 'ROLLBACK_ABORTED_PROMOTION';
178+
metrics.rollbackAborts += 1;
179+
metrics.blockedEvaluations += 1;
180+
} else if (promoted) {
181+
stableFrames = windowFrames;
182+
lastReason = 'ALREADY_PROMOTED';
183+
metrics.stableEvaluations += 1;
184+
} else if (!hasCriteria) {
185+
stableFrames = 0;
186+
lastReason = 'PROMOTION_CRITERIA_MISSING';
187+
metrics.blockedEvaluations += 1;
188+
} else if (!allCriteriaMet) {
189+
stableFrames = 0;
190+
lastReason = 'PROMOTION_CRITERIA_UNMET';
191+
metrics.blockedEvaluations += 1;
192+
} else {
193+
stableFrames += 1;
194+
metrics.stableEvaluations += 1;
195+
lastReason = stableFrames >= windowFrames ? 'PROMOTION_READY' : 'PROMOTION_STABILIZING';
196+
if (stableFrames >= windowFrames) {
197+
promoted = true;
198+
promotedNow = true;
199+
metrics.promotions += 1;
200+
metrics.lastPromotionAtMs = timestampMs;
201+
lastReason = 'PROMOTED';
202+
}
203+
}
204+
205+
const readiness = promoted ? 'authoritative' : (allCriteriaMet ? 'stabilizing' : 'passive');
206+
const evaluation = {
207+
transitionName: String(transitionName || ''),
208+
frame: frame !== undefined && frame !== null ? Number(frame) : null,
209+
timestampMs,
210+
readiness,
211+
promoted,
212+
promotedNow,
213+
rollbackTriggered: Boolean(rollbackTriggered),
214+
stability: {
215+
currentFrames: stableFrames,
216+
requiredFrames: windowFrames
217+
},
218+
criteria: {
219+
values: normalizedCriteria,
220+
unmet,
221+
allMet: allCriteriaMet
222+
},
223+
reason: lastReason,
224+
metrics: getMetrics()
225+
};
226+
lastEvaluation = cloneDeep(evaluation);
227+
228+
if (typeof logger === 'function') {
229+
logger(
230+
`[promotion-gate] readiness=${readiness} promoted=${String(promoted)} ` +
231+
`stable=${stableFrames}/${windowFrames} reason=${lastReason}`
232+
);
233+
}
234+
235+
return evaluation;
236+
}
237+
238+
return {
239+
evaluate,
240+
getMetrics,
241+
getState
242+
};
243+
}
244+
80245
function createWorldGameStateSystem(options = {}) {
81246
const now = typeof options.now === 'function' ? options.now : () => Date.now();
82247
const initialPassiveMode = options.passiveMode !== undefined ? Boolean(options.passiveMode) : true;
@@ -111,7 +276,7 @@ function createWorldGameStateSystem(options = {}) {
111276
? promotionGateConfig.logger
112277
: null;
113278
const promotionGate = promotionGateConfig
114-
? createPromotionGate({
279+
? createInlinePromotionGate({
115280
now,
116281
requiredCriteria: promotionGateConfig.requiredCriteria,
117282
stabilityWindowFrames: promotionGateConfig.stabilityWindowFrames,
@@ -477,26 +642,6 @@ function createWorldGameStateSystem(options = {}) {
477642
return featureGates;
478643
}
479644

480-
function getMode() {
481-
return activeMode;
482-
}
483-
484-
function getPromotionReadiness() {
485-
if (!promotionGate) return null;
486-
const state = promotionGate.getState();
487-
return {
488-
mode: activeMode,
489-
promoted: !isPassiveModeActive(),
490-
stableFrames: state.stableFrames,
491-
requiredStableFrames: state.stabilityWindowFrames,
492-
reason: state.lastReason
493-
};
494-
}
495-
496-
function getPromotionMetrics() {
497-
return promotionGate ? promotionGate.getMetrics() : null;
498-
}
499-
500645
const publicApi = {
501646
getSnapshot,
502647
getReadonlyView,
@@ -505,10 +650,7 @@ function createWorldGameStateSystem(options = {}) {
505650
applyExternalSnapshotPatch,
506651
getTransitionNames,
507652
getSelectorNames,
508-
getFeatureGates,
509-
getMode,
510-
getPromotionReadiness,
511-
getPromotionMetrics
653+
getFeatureGates
512654
};
513655

514656
function getPublicApi() {

0 commit comments

Comments
 (0)