Skip to content

Commit 990b8fb

Browse files
author
DavidQ
committed
Route game tool links through workspace and stabilize palette/pipeline handoff
- pass game context through games tool links and honor requested tool routing in Workspace Manager - hydrate workspace state from launch context, clear stale shared handoffs on game-return launches, and keep tool host sync stable - add Breakout Classic palette asset and register it in breakout workspace.asset-catalog - add shared palette document contract (schema/version/entries validation + legacy colors[] normalization) - align Palette Browser import/export/copy/preview on the same palette schema payload - auto-select shared palette handoff in Palette Browser (prefer display name, guard stale paletteId, mirror workspace palette when needed) - hydrate shared palette from game asset catalog in platform shell and show tool-row badge as “Palette: …” for Palette Browser - remove redundant “Open in Workspace Manager” link in workspace context and apply workspace text color updates (#3600af) - improve Asset Pipeline fullscreen layout and add top-bar usage guidance; apply launch context to loaded/default pipeline payloads - fix Skin Editor object interaction so row clicks do not toggle object checkboxes (checkbox toggles only on checkbox click) - update Asteroids page title/description text
1 parent 9e31f58 commit 990b8fb

14 files changed

Lines changed: 698 additions & 85 deletions

File tree

games/Asteroids/index.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<html lang="en">
99
<head>
1010
<meta charset="UTF-8" />
11-
<title>Asteroids New - Parallel Lane</title>
11+
<title>Asteroids</title>
1212
<link rel="stylesheet" href="/src/engine/ui/baseLayout.css" />
1313
<link rel="stylesheet" href="/games/shared/styles/vectorBattleFont.css" />
1414
<link rel="stylesheet" href="../../src/engine/theme/main.css" />
@@ -21,8 +21,9 @@
2121
<body class="hub-page-games">
2222
<div id="shared-theme-header"></div>
2323
<main>
24-
<h1>Asteroids New - Parallel Lane</h1>
25-
<p>Parallel Asteroids lane for boot and gameplay validation.</p>
24+
<h1>Asteroids</h1>
25+
<p>Asteroids is a classic arcade game where you pilot a spaceship in a wrap-around field of drifting space rocks. Your goal is to shoot and destroy the asteroids and flying saucers while avoiding collisions.
26+
</p>
2627
<canvas id="game" width="960" height="720"></canvas>
2728
<section>
2829
<h3>Keys</h3>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"schema": "html-js-gaming.palette",
3+
"version": 1,
4+
"name": "Breakout Classic Palette",
5+
"source": "custom",
6+
"entries": [
7+
{
8+
"symbol": "!",
9+
"hex": "#000000",
10+
"name": "Background"
11+
},
12+
{
13+
"symbol": "#",
14+
"hex": "#F8F8F2",
15+
"name": "Wall/Paddle/Ball"
16+
},
17+
{
18+
"symbol": "$",
19+
"hex": "#FF595E",
20+
"name": "Brick Row 1"
21+
},
22+
{
23+
"symbol": "%",
24+
"hex": "#FF924C",
25+
"name": "Brick Row 2"
26+
},
27+
{
28+
"symbol": "&",
29+
"hex": "#FFCA3A",
30+
"name": "Brick Row 3"
31+
},
32+
{
33+
"symbol": "(",
34+
"hex": "#8AC926",
35+
"name": "Brick Row 4"
36+
},
37+
{
38+
"symbol": ")",
39+
"hex": "#1982C4",
40+
"name": "Brick Row 5"
41+
},
42+
{
43+
"symbol": "*",
44+
"hex": "#6A4C93",
45+
"name": "Brick Row 6"
46+
},
47+
{
48+
"symbol": "+",
49+
"hex": "#04040A",
50+
"name": "HUD Text"
51+
},
52+
{
53+
"symbol": ",",
54+
"hex": "#A0A0A0",
55+
"name": "HUD Muted"
56+
}
57+
]
58+
}

games/breakout/assets/workspace.asset-catalog.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
"version": 1,
44
"gameId": "breakout",
55
"assets": {
6+
"palette.breakout.classic": {
7+
"path": "/games/Breakout/assets/palettes/breakout-classic.palette.json",
8+
"kind": "palette",
9+
"source": "workspace-manager"
10+
},
611
"skin.main": {
712
"path": "/games/Breakout/assets/skins/default.json",
813
"kind": "skin",

games/index.render.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ function buildRoundtripLinks(game, toolRegistryMap) {
149149
const workspaceHref = "/tools/Workspace%20Manager/index.html";
150150
const query = new URLSearchParams();
151151
query.set("tool", tool.id);
152+
query.set("game", game.id);
152153
query.set("gameId", game.id);
153154
if (game.title) {
154155
query.set("gameTitle", game.title);

tools/Asset Pipeline Tool/index.html

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,19 @@
1919
</details>
2020
<div class="debug-tool-shell app-shell">
2121
<section class="panel debug-tool-panel">
22-
<div class="debug-tool-actions">
22+
<div class="debug-tool-actions asset-pipeline-actions">
2323
<button type="button" id="runAssetPipelineButton">Run Pipeline</button>
24+
<p class="asset-pipeline-quick-guide">
25+
Paste or load pipeline options JSON in <strong>Pipeline Input</strong>, run the pipeline, then review
26+
normalization/validation results and manifest output in <strong>Pipeline Output</strong>. Use this to verify
27+
IDs, paths, types, and references. This tool emits nested game paths such as
28+
<code>games/sample-1413/assets/sprites/*.json</code> plus a coordinator manifest at
29+
<code>games/sample-1413/assets/tools.manifest.json</code>. Input data comes from your JSON (or loaded sample
30+
preset), then the pipeline normalizes records across sprites/tilemaps/parallax/vectors, validates tool-state
31+
contracts, and reports stage results (`load`, `validate`, `normalize`, `emit`) with `ready`/`invalid` status.
32+
Runtime manifest lookup/loader utilities consume this output for discoverable `runtimeAssetSources`; this is
33+
forward coordination (games/samples to normalized manifest), not reverse sync back into editor project state.
34+
</p>
2435
</div>
2536
<div id="assetPipelineStatus" class="debug-tool-meta">Asset pipeline ready.</div>
2637
</section>

tools/Asset Pipeline Tool/main.js

Lines changed: 132 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,35 @@ const refs = {
99
output: document.getElementById("assetPipelineOutput")
1010
};
1111

12+
function normalizeText(value) {
13+
return typeof value === "string" ? value.trim() : "";
14+
}
15+
16+
function cloneJson(value) {
17+
try {
18+
return JSON.parse(JSON.stringify(value));
19+
} catch {
20+
return null;
21+
}
22+
}
23+
24+
function toSlug(value, fallback = "game") {
25+
const text = normalizeText(value)
26+
.toLowerCase()
27+
.replace(/[^a-z0-9._-]+/g, "-")
28+
.replace(/-+/g, "-")
29+
.replace(/^-|-$/g, "");
30+
return text || fallback;
31+
}
32+
33+
function readLaunchContextFromQuery() {
34+
const searchParams = new URLSearchParams(window.location.search);
35+
return {
36+
gameId: normalizeText(searchParams.get("gameId")),
37+
gameTitle: normalizeText(searchParams.get("gameTitle"))
38+
};
39+
}
40+
1241
function normalizeSamplePresetPath(pathValue) {
1342
if (typeof pathValue !== "string") {
1443
return "";
@@ -30,14 +59,101 @@ function normalizeSamplePresetPath(pathValue) {
3059
}
3160

3261
function buildPresetLoadedStatus(sampleId, samplePresetPath) {
33-
const normalizedSampleId = typeof sampleId === "string" ? sampleId.trim() : "";
62+
const normalizedSampleId = normalizeText(sampleId);
3463
if (normalizedSampleId) {
3564
return `Loaded preset from sample ${normalizedSampleId}.`;
3665
}
37-
const normalizedPath = typeof samplePresetPath === "string" ? samplePresetPath.trim() : "";
66+
const normalizedPath = normalizeText(samplePresetPath);
3867
return normalizedPath ? `Loaded preset from ${normalizedPath}.` : "Loaded preset.";
3968
}
4069

70+
function buildPresetLoadedWithContextStatus(launchGameId, samplePresetPath) {
71+
const safeGameId = normalizeText(launchGameId);
72+
const safePresetPath = normalizeText(samplePresetPath);
73+
if (!safeGameId) {
74+
return "Loaded preset with launch context.";
75+
}
76+
if (safePresetPath.startsWith("/samples/")) {
77+
return `Loaded shared sample preset template. Launch context applied for game ${safeGameId}.`;
78+
}
79+
return `Loaded preset. Launch context applied for game ${safeGameId}.`;
80+
}
81+
82+
function applyLaunchContextToPayload(rawPayload, launchContext = {}) {
83+
const payload = rawPayload && typeof rawPayload === "object" ? cloneJson(rawPayload) : null;
84+
if (!payload || typeof payload !== "object") {
85+
return {
86+
payload: rawPayload,
87+
overridden: false
88+
};
89+
}
90+
91+
const gameId = normalizeText(launchContext.gameId);
92+
if (!gameId) {
93+
return {
94+
payload,
95+
overridden: false
96+
};
97+
}
98+
99+
let overridden = false;
100+
const originalGameId = normalizeText(payload.gameId);
101+
if (normalizeText(payload.gameId) !== gameId) {
102+
payload.gameId = gameId;
103+
overridden = true;
104+
}
105+
106+
if (/^sample-\d{4}$/i.test(originalGameId)) {
107+
const gameSlug = toSlug(gameId, "game");
108+
const rewrite = (value) => normalizeText(value).replace(/^sample-\d{4}/i, gameSlug);
109+
const domains = payload.domainInputs && typeof payload.domainInputs === "object"
110+
? payload.domainInputs
111+
: {};
112+
Object.values(domains).forEach((records) => {
113+
if (!Array.isArray(records)) {
114+
return;
115+
}
116+
records.forEach((record) => {
117+
if (!record || typeof record !== "object") {
118+
return;
119+
}
120+
const nextAssetId = rewrite(record.assetId);
121+
if (nextAssetId && nextAssetId !== record.assetId) {
122+
record.assetId = nextAssetId;
123+
overridden = true;
124+
}
125+
const nextRuntimeFileName = rewrite(record.runtimeFileName);
126+
if (nextRuntimeFileName && nextRuntimeFileName !== record.runtimeFileName) {
127+
record.runtimeFileName = nextRuntimeFileName;
128+
overridden = true;
129+
}
130+
const nextToolDataFileName = rewrite(record.toolDataFileName);
131+
if (nextToolDataFileName && nextToolDataFileName !== record.toolDataFileName) {
132+
record.toolDataFileName = nextToolDataFileName;
133+
overridden = true;
134+
}
135+
});
136+
});
137+
}
138+
139+
if (payload.toolStates && typeof payload.toolStates === "object") {
140+
Object.values(payload.toolStates).forEach((toolState) => {
141+
if (!toolState || typeof toolState !== "object") {
142+
return;
143+
}
144+
if (normalizeText(toolState.projectId) !== gameId) {
145+
toolState.projectId = gameId;
146+
overridden = true;
147+
}
148+
});
149+
}
150+
151+
return {
152+
payload,
153+
overridden
154+
};
155+
}
156+
41157
function setStatus(message) {
42158
if (refs.statusText instanceof HTMLElement) {
43159
refs.statusText.textContent = message;
@@ -124,7 +240,8 @@ async function tryLoadPresetFromQuery() {
124240
if (!samplePresetPath) {
125241
return;
126242
}
127-
const sampleId = String(searchParams.get("sampleId") || "").trim();
243+
const sampleId = normalizeText(searchParams.get("sampleId"));
244+
const launchContext = readLaunchContextFromQuery();
128245
try {
129246
const presetUrl = new URL(samplePresetPath, window.location.href);
130247
const response = await fetch(presetUrl.toString(), { cache: "no-store" });
@@ -136,22 +253,31 @@ async function tryLoadPresetFromQuery() {
136253
if (!pipelinePayload) {
137254
throw new Error("Preset payload did not include pipeline options.");
138255
}
256+
const adapted = applyLaunchContextToPayload(pipelinePayload, launchContext);
139257
if (!(refs.input instanceof HTMLTextAreaElement)) {
140258
throw new Error("Pipeline input is unavailable.");
141259
}
142-
refs.input.value = toPrettyJson(pipelinePayload);
143-
setStatus(buildPresetLoadedStatus(sampleId, samplePresetPath));
260+
refs.input.value = toPrettyJson(adapted.payload);
261+
const loadedStatus = buildPresetLoadedStatus(sampleId, samplePresetPath);
262+
if (adapted.overridden && launchContext.gameId) {
263+
setStatus(buildPresetLoadedWithContextStatus(launchContext.gameId, samplePresetPath));
264+
return;
265+
}
266+
setStatus(loadedStatus);
144267
} catch (error) {
145268
setStatus(`Preset load failed: ${error instanceof Error ? error.message : "unknown error"}`);
146269
}
147270
}
148271

149272
function bootAssetPipelineTool() {
273+
const launchContext = readLaunchContextFromQuery();
150274
if (refs.runButton instanceof HTMLButtonElement) {
151275
refs.runButton.addEventListener("click", runPipeline);
152276
}
153277
if (refs.input instanceof HTMLTextAreaElement && !refs.input.value.trim()) {
154-
refs.input.value = toPrettyJson(createDefaultPayload());
278+
const defaultPayload = createDefaultPayload();
279+
const adapted = applyLaunchContextToPayload(defaultPayload, launchContext);
280+
refs.input.value = toPrettyJson(adapted.payload);
155281
}
156282
void tryLoadPresetFromQuery();
157283
return { runPipeline };

0 commit comments

Comments
 (0)