Skip to content

Commit a81b181

Browse files
author
DavidQ
committed
Phase 18 core services skeleton
1 parent 92bb483 commit a81b181

14 files changed

Lines changed: 402 additions & 8 deletions

docs/dev/CODEX_COMMANDS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
MODEL: GPT-5.3-codex
22
REASONING: high
3-
COMMAND: Create Phase 18 foundation structure and minimal wiring, package to <project folder>/tmp/BUILD_PR_LEVEL_18_2_PHASE18_FOUNDATION.zip
3+
COMMAND: Implement Phase 18 core services skeleton and wiring, package to <project folder>/tmp/BUILD_PR_LEVEL_18_3_PHASE18_CORE_SERVICES.zip

docs/dev/COMMIT_COMMENT.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Phase 18 foundation structure and minimal wiring
1+
Phase 18 core services skeleton
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
- [ ] folders created
1+
- [ ] services initialize
22
- [ ] no runtime errors
3-
- [ ] index/registry loads
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# BUILD_PR_LEVEL_18_3_PHASE18_CORE_SERVICES
2+
3+
## Purpose
4+
Implement a minimal Phase 18 core-services skeleton and wire it into the existing `1801` foundation sample.
5+
6+
## Source of Truth
7+
- `docs/pr/PLAN_PR_LEVEL_18_3_PHASE18_CORE_SERVICES.md`
8+
- `docs/pr/BUILD_PR_LEVEL_18_2_PHASE18_FOUNDATION.md`
9+
10+
## Exact Build Target
11+
1. Add a Phase 18 shared core-services skeleton under:
12+
- `samples/phase-18/shared/coreServices/`
13+
2. Include:
14+
- one service contract validator
15+
- one service registry with lifecycle hooks (`start`, `update`, `stop`)
16+
- one communication service (channel/event bus wrapper)
17+
- one baseline heartbeat service
18+
- one bootstrap factory that registers baseline services
19+
3. Wire sample `1801` to use the core-services skeleton:
20+
- initialize services in `main.js`
21+
- start/update/stop via scene lifecycle hooks
22+
- render minimal service status in sample panel
23+
24+
## Non-Goals
25+
- no engine core modifications
26+
- no gameplay/system feature implementation
27+
- no additional Phase 18 samples
28+
- no roadmap status updates
29+
30+
## Validation
31+
- targeted runtime test for service registry lifecycle + communication path
32+
- sample `1801` still renders with service status
33+
34+
## Packaging Rule
35+
Package only this PR's created/modified files into:
36+
`tmp/BUILD_PR_LEVEL_18_3_PHASE18_CORE_SERVICES.zip`
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# PLAN_PR_LEVEL_18_3_PHASE18_CORE_SERVICES
2+
3+
Purpose:
4+
Define core services for Phase 18.
5+
6+
Scope:
7+
- service registry
8+
- lifecycle hooks
9+
- communication patterns

samples/phase-18/1801/Phase18FoundationScene.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,38 @@ import { drawFrame, drawPanel } from '/src/engine/debug/index.js';
1111
const theme = new Theme(ThemeTokens);
1212

1313
export default class Phase18FoundationScene extends Scene {
14-
constructor() {
14+
constructor({ coreServices = null } = {}) {
1515
super();
1616
this.elapsed = 0;
17+
this.coreServices = coreServices;
18+
this.lastHeartbeatTick = 0;
19+
this.lastHeartbeatTime = 0;
20+
this.unsubscribeHeartbeat = null;
21+
}
22+
23+
enter(engine) {
24+
if (!this.coreServices) return;
25+
const channel = this.coreServices.get('phase18.channel');
26+
if (channel && typeof channel.subscribe === 'function') {
27+
this.unsubscribeHeartbeat = channel.subscribe('phase18.heartbeat', (payload) => {
28+
this.lastHeartbeatTick = Number(payload?.tick) || 0;
29+
this.lastHeartbeatTime = Number(payload?.t) || 0;
30+
});
31+
}
32+
this.coreServices.start({ engine, scene: this });
1733
}
1834

1935
update(dtSeconds) {
2036
this.elapsed += dtSeconds;
37+
this.coreServices?.update(dtSeconds, { scene: this });
38+
}
39+
40+
exit() {
41+
if (typeof this.unsubscribeHeartbeat === 'function') {
42+
this.unsubscribeHeartbeat();
43+
this.unsubscribeHeartbeat = null;
44+
}
45+
this.coreServices?.stop({ scene: this });
2146
}
2247

2348
render(renderer) {
@@ -42,7 +67,8 @@ export default class Phase18FoundationScene extends Scene {
4267
'Status: initialized',
4368
'Folder: samples/phase-18',
4469
'Entry sample: 1801',
45-
'Next: scoped Level 18 slices',
70+
`Services: ${this.coreServices?.listServiceIds().length ?? 0}`,
71+
`Heartbeat tick: ${this.lastHeartbeatTick} @ ${this.lastHeartbeatTime.toFixed(2)}s`,
4672
]);
4773
}
4874
}

samples/phase-18/1801/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<body>
1515
<main>
1616
<h1>Sample 1801 - Phase 18 Foundation</h1>
17-
<p>Bootstrap scaffold for Phase 18 with minimal wiring and no feature implementation.</p>
17+
<p>Bootstrap scaffold for Phase 18 with minimal core-service lifecycle wiring and no feature logic.</p>
1818
<canvas id="game" width="960" height="540"></canvas>
1919

2020
<section>

samples/phase-18/1801/main.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ main.js
77
import Engine from '/src/engine/core/Engine.js';
88
import { InputService } from '/src/engine/input/index.js';
99
import { Theme, ThemeTokens } from '/src/engine/theme/index.js';
10+
import createPhase18CoreServices from '/samples/phase-18/shared/coreServices/createPhase18CoreServices.js';
1011
import Phase18FoundationScene from './Phase18FoundationScene.js';
1112

1213
const theme = new Theme(ThemeTokens);
@@ -23,5 +24,6 @@ const engine = new Engine({
2324
input,
2425
});
2526

26-
engine.setScene(new Phase18FoundationScene());
27+
const coreServices = createPhase18CoreServices();
28+
engine.setScene(new Phase18FoundationScene({ coreServices }));
2729
engine.start();
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
Toolbox Aid
3+
David Quesenberry
4+
04/16/2026
5+
createPhase18ChannelService.js
6+
*/
7+
import EventBus from '/src/engine/events/EventBus.js';
8+
9+
export default function createPhase18ChannelService() {
10+
const bus = new EventBus();
11+
let running = false;
12+
let publishedCount = 0;
13+
let lastChannel = 'none';
14+
15+
function publish(channel, payload = {}) {
16+
if (!running || typeof channel !== 'string' || channel.length === 0) {
17+
return 0;
18+
}
19+
lastChannel = channel;
20+
publishedCount += 1;
21+
return bus.emit(channel, payload);
22+
}
23+
24+
function subscribe(channel, handler) {
25+
return bus.on(channel, handler);
26+
}
27+
28+
return {
29+
id: 'phase18.channel',
30+
publish,
31+
subscribe,
32+
getSnapshot() {
33+
return {
34+
running,
35+
publishedCount,
36+
lastChannel,
37+
};
38+
},
39+
onStart() {
40+
running = true;
41+
},
42+
onStop() {
43+
running = false;
44+
bus.clear();
45+
},
46+
};
47+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
Toolbox Aid
3+
David Quesenberry
4+
04/16/2026
5+
createPhase18CoreServices.js
6+
*/
7+
import createPhase18ServiceRegistry from './createPhase18ServiceRegistry.js';
8+
import createPhase18ChannelService from './createPhase18ChannelService.js';
9+
import createPhase18HeartbeatService from './createPhase18HeartbeatService.js';
10+
11+
export default function createPhase18CoreServices() {
12+
const registry = createPhase18ServiceRegistry();
13+
registry.register(createPhase18ChannelService());
14+
registry.register(createPhase18HeartbeatService());
15+
return registry;
16+
}

0 commit comments

Comments
 (0)