Skip to content

Commit 6aa68ce

Browse files
author
DavidQ
committed
PR_08_03_TOOL_LIVE_PREVIEW_SYNC v2
1 parent 74a2154 commit 6aa68ce

10 files changed

Lines changed: 300 additions & 10 deletions

File tree

docs/dev/CODEX_COMMANDS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ MODEL: GPT-5.4
22
REASONING: medium
33

44
COMMAND:
5-
Implement PR_08_02_TOOL_SAMPLE_ROUNDTRIP.
5+
Implement PR_08_03_TOOL_LIVE_PREVIEW_SYNC.

docs/dev/COMMIT_COMMENT.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
PR_08_02_TOOL_SAMPLE_ROUNDTRIP
1+
PR_08_03_TOOL_LIVE_PREVIEW_SYNC
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
PR: PR_08_03_TOOL_LIVE_PREVIEW_SYNC
2+
3+
Live Sync Channel
4+
- Added shared bridge utility at tools/shared/livePreviewSyncChannel.js.
5+
- Channel name: world-game-tool-live-preview-sync.
6+
- Transport order:
7+
1. BroadcastChannel when available.
8+
2. localStorage storage-event fallback when BroadcastChannel is unavailable.
9+
- Envelope fields: channel, sourceId, eventType, updatedAt, payload.
10+
- Loop prevention: per-source filtering and payload signature de-duplication.
11+
12+
Tool Publishers
13+
- tools/Tilemap Studio/main.js publishes tileMapDocument snapshots on:
14+
- init
15+
- new project
16+
- load project
17+
- document update
18+
- tools/Parallax Scene Studio/main.js publishes parallaxDocument snapshots on:
19+
- init
20+
- load sample
21+
- new project
22+
- load project
23+
- document update
24+
- Both publishers batch same-frame updates with requestAnimationFrame (setTimeout fallback).
25+
26+
Sample Consumer
27+
- samples/phase-12/1208/main.js subscribes to live sync channel.
28+
- samples/phase-12/1208/ToolFormattedTilesParallaxScene.js applies updates through queued async processing.
29+
- Tile updates reuse existing tile import and asset load path.
30+
- Parallax updates reuse existing layer extraction and asset load path.
31+
- Status surface updated with sync success/failure and version counter.
32+
33+
Compatibility / Boundaries
34+
- No engine core API changes.
35+
- No sample navigation/index reshaping.
36+
- Sync is additive and isolated to tool/sample integration surface.
Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
1-
PR_08_02_TOOL_SAMPLE_ROUNDTRIP - checklist
1+
PR: PR_08_03_TOOL_LIVE_PREVIEW_SYNC
22

3-
[x] Tool launcher retains sample link entry points for Sample 1208.
4-
[x] Sample 1208 exposes direct links back to relevant tools.
5-
[x] No tool runtime implementation redesign.
6-
[x] No game/network behavior changes.
7-
[x] Roundtrip path (tool -> sample -> tool) is present.
8-
[x] Smoke validation passed.
3+
Validation Commands
4+
- node --check tools/shared/livePreviewSyncChannel.js
5+
- node --check "tools/Tilemap Studio/main.js"
6+
- node --check "tools/Parallax Scene Studio/main.js"
7+
- node --check samples/phase-12/1208/main.js
8+
- node --check samples/phase-12/1208/ToolFormattedTilesParallaxScene.js
9+
- node tests/runtime/LaunchSmokeAllEntries.test.mjs
10+
11+
Results
12+
- Syntax checks: PASS
13+
- Runtime smoke: PASS
14+
15+
Scope Verification
16+
- Changed implementation files:
17+
- tools/shared/livePreviewSyncChannel.js
18+
- tools/Tilemap Studio/main.js
19+
- tools/Parallax Scene Studio/main.js
20+
- samples/phase-12/1208/main.js
21+
- samples/phase-12/1208/ToolFormattedTilesParallaxScene.js
22+
- Deliverable docs:
23+
- docs/dev/reports/live_sync_model.txt
24+
- docs/dev/reports/validation_checklist.txt
25+
- No engine core API modifications performed.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# PR_08_03_TOOL_LIVE_PREVIEW_SYNC
2+
3+
## Purpose
4+
Enable live preview syncing between tools and running samples.
5+
6+
## Scope
7+
- live sync only
8+
- no redesign
9+
10+
## Tasks
11+
1. Define live update channel between tool and sample.
12+
2. Apply changes in real-time preview.
13+
3. Validate sync stability.
14+
15+
## Deliverables
16+
- docs/dev/reports/live_sync_model.txt
17+
- docs/dev/reports/validation_checklist.txt
18+
19+
## Output
20+
<project folder>/tmp/PR_08_03_TOOL_LIVE_PREVIEW_SYNC.zip

samples/phase-12/1208/ToolFormattedTilesParallaxScene.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export default class ToolFormattedTilesParallaxScene extends Scene {
3939
this.contentStatus = 'Loading tool-formatted tile/parallax content...';
4040
this.contentLoaded = false;
4141
this.contentError = null;
42+
this.liveSyncVersion = 0;
43+
this.liveSyncPending = Promise.resolve();
4244
this.tilesetAssetPath = '';
4345
this.tilesetImage = null;
4446
this.tileFrameById = {};
@@ -415,6 +417,41 @@ export default class ToolFormattedTilesParallaxScene extends Scene {
415417
}
416418
}
417419
}
420+
421+
applyLivePreviewUpdate(update = {}) {
422+
this.liveSyncPending = this.liveSyncPending
423+
.then(() => this.applyLivePreviewUpdateInternal(update))
424+
.catch(() => {});
425+
return this.liveSyncPending;
426+
}
427+
428+
async applyLivePreviewUpdateInternal(update = {}) {
429+
const nextTileDocument = update && typeof update.tileMapDocument === 'object' ? update.tileMapDocument : null;
430+
const nextParallaxDocument = update && typeof update.parallaxDocument === 'object' ? update.parallaxDocument : null;
431+
if (!nextTileDocument && !nextParallaxDocument) {
432+
return;
433+
}
434+
435+
try {
436+
let tileAssetCount = this.tilesetImage ? 1 : 0;
437+
if (nextTileDocument) {
438+
this.applyTileExportData(nextTileDocument);
439+
tileAssetCount = await this.loadTileAssets(extractTileEntries(nextTileDocument));
440+
}
441+
442+
if (nextParallaxDocument) {
443+
this.parallaxLayers = await this.loadParallaxAssets(extractParallaxLayers(nextParallaxDocument));
444+
}
445+
446+
this.liveSyncVersion += 1;
447+
this.contentLoaded = tileAssetCount > 0 && this.parallaxLayers.length > 0;
448+
this.contentError = null;
449+
this.contentStatus = `Live preview synced (${this.liveSyncVersion}).`;
450+
} catch (error) {
451+
this.contentError = error instanceof Error ? error.message : String(error);
452+
this.contentStatus = 'Live preview sync failed.';
453+
}
454+
}
418455
}
419456

420457
function loadImageFromRelativePath(relativePath, baseUrl) {

samples/phase-12/1208/main.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ 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';
1010
import ToolFormattedTilesParallaxScene from './ToolFormattedTilesParallaxScene.js';
11+
import { createLivePreviewSyncBridge } from '/tools/shared/livePreviewSyncChannel.js';
1112

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

26-
engine.setScene(new ToolFormattedTilesParallaxScene());
27+
const scene = new ToolFormattedTilesParallaxScene();
28+
const livePreviewSync = createLivePreviewSyncBridge({ sourceId: 'sample-1208-live-preview' });
29+
livePreviewSync.subscribe((payload) => {
30+
scene.applyLivePreviewUpdate(payload);
31+
});
32+
engine.setScene(scene);
2733
engine.start();

tools/Parallax Scene Studio/main.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { buildProjectPackage, summarizeProjectPackaging } from "../shared/projec
3131
import { buildEditorExperienceLayer, summarizeEditorExperienceLayer } from "../shared/editorExperienceLayer.js";
3232
import { buildDebugVisualizationLayer, summarizeDebugVisualizationLayer } from "../shared/debugVisualizationLayer.js";
3333
import { registerToolBootContract } from "../shared/toolBootContract.js";
34+
import { createLivePreviewSyncBridge } from "../shared/livePreviewSyncChannel.js";
3435

3536
const SAMPLE_DIRECTORY_PATH = "./samples/";
3637
const SAMPLE_MANIFEST_PATH = "./samples/sample-manifest.json";
@@ -390,6 +391,9 @@ class ParallaxEditorApp {
390391
this.lastRuntimeResult = null;
391392
this.editorExperienceResult = null;
392393
this.debugVisualizationResult = null;
394+
this.livePreviewSync = createLivePreviewSyncBridge({ sourceId: "parallax-editor" });
395+
this.livePreviewSyncFrame = 0;
396+
this.pendingLivePreviewReason = "init";
393397
}
394398

395399
invalidateImageCache() {
@@ -402,6 +406,7 @@ class ParallaxEditorApp {
402406
this.attachEvents();
403407
this.syncInputsFromDocument();
404408
this.renderAll();
409+
this.queueLivePreviewSync("init");
405410
this.loadSampleManifest();
406411
}
407412

@@ -539,6 +544,32 @@ class ParallaxEditorApp {
539544

540545
touchDocument() {
541546
this.documentModel.metadata.updatedAt = new Date().toISOString();
547+
this.queueLivePreviewSync("document-update");
548+
}
549+
550+
publishLivePreviewSync(reason = "update") {
551+
this.livePreviewSync.publish(
552+
{
553+
toolId: "parallax-editor",
554+
reason,
555+
parallaxDocument: cloneDeep(this.documentModel)
556+
},
557+
"tool-live-preview-sync"
558+
);
559+
}
560+
561+
queueLivePreviewSync(reason = "update") {
562+
this.pendingLivePreviewReason = reason;
563+
if (this.livePreviewSyncFrame) {
564+
return;
565+
}
566+
const schedule = typeof requestAnimationFrame === "function"
567+
? requestAnimationFrame
568+
: (callback) => setTimeout(callback, 16);
569+
this.livePreviewSyncFrame = schedule(() => {
570+
this.livePreviewSyncFrame = 0;
571+
this.publishLivePreviewSync(this.pendingLivePreviewReason);
572+
});
542573
}
543574

544575
updateStatus(message) {
@@ -933,6 +964,7 @@ class ParallaxEditorApp {
933964
this.cameraY = 0;
934965
this.syncInputsFromDocument();
935966
this.renderAll();
967+
this.queueLivePreviewSync("load-sample");
936968
this.updateStatus(`Loaded sample ${selectedPath}.`);
937969
} catch (error) {
938970
this.updateStatus(`Sample load failed: ${error instanceof Error ? error.message : "unknown error"}`);
@@ -948,6 +980,7 @@ class ParallaxEditorApp {
948980
this.cameraY = 0;
949981
this.syncInputsFromDocument();
950982
this.renderAll();
983+
this.queueLivePreviewSync("new-project");
951984
this.updateStatus("Created new parallax document.");
952985
}
953986

@@ -1151,6 +1184,7 @@ class ParallaxEditorApp {
11511184
this.cameraY = 0;
11521185
this.syncInputsFromDocument();
11531186
this.renderAll();
1187+
this.queueLivePreviewSync("load-project");
11541188
const validation = this.validateProjectAssets();
11551189
if (resolution.resolvedCount > 0) {
11561190
this.updateStatus(`Loaded ${file.name} (${resolution.resolvedCount} layer image refs restored from asset registry, validation: ${summarizeAssetValidation(validation)}).`);

tools/Tilemap Studio/main.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { buildProjectPackage, summarizeProjectPackaging } from "../shared/projec
3131
import { buildEditorExperienceLayer, summarizeEditorExperienceLayer } from "../shared/editorExperienceLayer.js";
3232
import { buildDebugVisualizationLayer, summarizeDebugVisualizationLayer } from "../shared/debugVisualizationLayer.js";
3333
import { registerToolBootContract } from "../shared/toolBootContract.js";
34+
import { createLivePreviewSyncBridge } from "../shared/livePreviewSyncChannel.js";
3435

3536
const DEFAULT_TILESET = [
3637
{ id: 0, name: "Empty", color: "transparent" },
@@ -581,13 +582,17 @@ class TileMapEditorApp {
581582
this.lastRuntimeResult = null;
582583
this.editorExperienceResult = null;
583584
this.debugVisualizationResult = null;
585+
this.livePreviewSync = createLivePreviewSyncBridge({ sourceId: "tile-map-editor" });
586+
this.livePreviewSyncFrame = 0;
587+
this.pendingLivePreviewReason = "init";
584588
}
585589

586590
init(rootDocument) {
587591
this.captureRefs(rootDocument);
588592
this.attachEvents();
589593
this.syncInputsFromDocument();
590594
this.renderAll();
595+
this.queueLivePreviewSync("init");
591596
void this.reloadTilesetImageFromDocument({ quiet: true });
592597
void this.preloadIndividualTileImages({ quiet: true });
593598
this.loadSampleManifest();
@@ -764,6 +769,7 @@ class TileMapEditorApp {
764769
this.tilesetImageCache = new Map();
765770
this.syncInputsFromDocument();
766771
this.renderAll();
772+
this.queueLivePreviewSync("new-project");
767773
this.updateStatus("Created a new map document.");
768774
}
769775

@@ -947,6 +953,7 @@ class TileMapEditorApp {
947953
this.tilesetImageCache = new Map();
948954
this.syncInputsFromDocument();
949955
this.renderAll();
956+
this.queueLivePreviewSync("load-project");
950957
const validation = this.validateProjectAssets();
951958
void this.reloadTilesetImageFromDocument({ quiet: true });
952959
void this.preloadIndividualTileImages({ quiet: true });
@@ -1554,6 +1561,32 @@ class TileMapEditorApp {
15541561

15551562
touchDocument() {
15561563
this.documentModel.metadata.updatedAt = new Date().toISOString();
1564+
this.queueLivePreviewSync("document-update");
1565+
}
1566+
1567+
publishLivePreviewSync(reason = "update") {
1568+
this.livePreviewSync.publish(
1569+
{
1570+
toolId: "tile-map-editor",
1571+
reason,
1572+
tileMapDocument: cloneDeep(this.documentModel)
1573+
},
1574+
"tool-live-preview-sync"
1575+
);
1576+
}
1577+
1578+
queueLivePreviewSync(reason = "update") {
1579+
this.pendingLivePreviewReason = reason;
1580+
if (this.livePreviewSyncFrame) {
1581+
return;
1582+
}
1583+
const schedule = typeof requestAnimationFrame === "function"
1584+
? requestAnimationFrame
1585+
: (callback) => setTimeout(callback, 16);
1586+
this.livePreviewSyncFrame = schedule(() => {
1587+
this.livePreviewSyncFrame = 0;
1588+
this.publishLivePreviewSync(this.pendingLivePreviewReason);
1589+
});
15571590
}
15581591

15591592
syncInputsFromDocument() {

0 commit comments

Comments
 (0)