Skip to content

Commit a87aa04

Browse files
author
DavidQ
committed
Add sample 0305 to Tilemap Studio Samples2Tools lane with shared preset/runtime loading and batch 14 roadmap reports
1 parent a7a764a commit a87aa04

9 files changed

Lines changed: 774 additions & 33 deletions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
BUILD_PR_LEVEL_20_1_PHASE20_TOOL_PRESET_INTEGRATION - Samples2Tools Batch 14 Summary
2+
3+
Scope: phase-01 through phase-19 Phase 3 Tilemap Studio expansion
4+
5+
Tilemap Studio mapped samples: 6
6+
- Phase 02 Sample 0221 | Tilemap System | preset: /samples/phase-02/0221/sample-0221-tile-map-editor.json
7+
- Phase 03 Sample 0305 | Tile Metadata | preset: /samples/phase-03/0305/sample-0305-tile-map-editor.json
8+
- Phase 12 Sample 1208 | Tool Formatted Tiles Parallax | preset: /samples/phase-12/1208/sample-1208-tile-map-editor.json
9+
- Phase 12 Sample 1209 | Tilemap Basic Layout Preset | preset: /samples/phase-12/1209/sample-1209-tile-map-editor.json
10+
- Phase 12 Sample 1210 | Tilemap Objective Layout Preset | preset: /samples/phase-12/1210/sample-1210-tile-map-editor.json
11+
- Phase 12 Sample 1211 | Tilemap Mario Learning Layout Preset | preset: /samples/phase-12/1211/sample-1211-tile-map-editor.json
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
BUILD_PR_LEVEL_20_1_PHASE20_TOOL_PRESET_INTEGRATION - Samples2Tools Batch 14 Validation
2+
3+
[x] Tilemap Studio has 6 mapped samples (phase-01 through phase-19)
4+
[x] All 6 mapped Tilemap Studio preset wrapper files exist on disk
5+
[x] Samples index roundtrip logic includes 0305 tile-map-editor mapping
6+
[x] Sample 0305 runtime loads the same sample-0305-tile-map-editor.json lane used by Tilemap Studio preload
7+
[x] Metadata toolHints include tile-map-editor for mapped sample ids
8+
9+
Validation details
10+
- Added/validated sample id: 0305
11+
- Existing mapped sample ids retained: 0221, 1208, 1209, 1210, 1211
12+
- New files: /samples/phase-03/0305/sample-0305-tile-map-editor.json, /samples/phase-03/0305/sample-0305-tile-map-editor-document.json
13+
- Roundtrip launcher path: samples/index.render.js
14+
- Sample runtime loader path: samples/phase-03/0305/TileMetadataScene.js
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"generatedAt": "2026-04-24T16:28:42.042Z",
3+
"scope": "phase-01-phase-19",
4+
"purpose": "batch-14-tilemap-studio-expansion",
5+
"links": [
6+
{
7+
"sampleId": "0221",
8+
"phase": "02",
9+
"title": "Tilemap System",
10+
"toolId": "tile-map-editor",
11+
"href": "./phase-02/0221/index.html",
12+
"presetPath": "/samples/phase-02/0221/sample-0221-tile-map-editor.json"
13+
},
14+
{
15+
"sampleId": "0305",
16+
"phase": "03",
17+
"title": "Tile Metadata",
18+
"toolId": "tile-map-editor",
19+
"href": "./phase-03/0305/index.html",
20+
"presetPath": "/samples/phase-03/0305/sample-0305-tile-map-editor.json"
21+
},
22+
{
23+
"sampleId": "1208",
24+
"phase": "12",
25+
"title": "Tool Formatted Tiles Parallax",
26+
"toolId": "tile-map-editor",
27+
"href": "./phase-12/1208/index.html",
28+
"presetPath": "/samples/phase-12/1208/sample-1208-tile-map-editor.json"
29+
},
30+
{
31+
"sampleId": "1209",
32+
"phase": "12",
33+
"title": "Tilemap Basic Layout Preset",
34+
"toolId": "tile-map-editor",
35+
"href": "./phase-12/1209/index.html",
36+
"presetPath": "/samples/phase-12/1209/sample-1209-tile-map-editor.json"
37+
},
38+
{
39+
"sampleId": "1210",
40+
"phase": "12",
41+
"title": "Tilemap Objective Layout Preset",
42+
"toolId": "tile-map-editor",
43+
"href": "./phase-12/1210/index.html",
44+
"presetPath": "/samples/phase-12/1210/sample-1210-tile-map-editor.json"
45+
},
46+
{
47+
"sampleId": "1211",
48+
"phase": "12",
49+
"title": "Tilemap Mario Learning Layout Preset",
50+
"toolId": "tile-map-editor",
51+
"href": "./phase-12/1211/index.html",
52+
"presetPath": "/samples/phase-12/1211/sample-1211-tile-map-editor.json"
53+
}
54+
]
55+
}

docs/dev/roadmaps/MASTER_ROADMAP_SAMPLES2TOOLS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,6 @@
154154
- [x] `docs/dev/reports/samples2tools_link_map_<n>.json`
155155

156156
## Current Snapshot (from tools_used.txt)
157-
- [x] Current tagged samples across active tools: `27` (phase-01 through phase-19)
157+
- [x] Current tagged samples across active tools: `28` (phase-01 through phase-19)
158158
- [x] Candidate coverage inventory exists for all active tools
159159
- [.] Convert candidates into curated, validated sample-to-tool links

samples/index.render.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ function shouldUsePresetRoundtrip(sample, toolId) {
5555
}
5656
if (toolId === "tile-map-editor") {
5757
return (sampleId === "0221" && samplePhase === "02")
58+
|| (sampleId === "0305" && samplePhase === "03")
5859
|| (sampleId === "1209" && samplePhase === "12")
5960
|| (sampleId === "1210" && samplePhase === "12")
6061
|| (sampleId === "1211" && samplePhase === "12");

samples/metadata/samples.index.metadata.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2326,6 +2326,9 @@
23262326
"engine/theme/index/ThemeTokens",
23272327
"engine/tilemap/index/renderTilemap",
23282328
"engine/tilemap/index/Tilemap"
2329+
],
2330+
"toolHints": [
2331+
"tile-map-editor"
23292332
]
23302333
},
23312334
{

samples/phase-03/0305/TileMetadataScene.js

Lines changed: 211 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,43 +16,89 @@ import { stepArcadeBody, moveRectWithTilemapCollision } from '/src/engine/system
1616
import { createDemoSpriteSheet } from '../0301/demoSpriteFactory.js';
1717

1818
const theme = new Theme(ThemeTokens);
19+
const TILEMAP_PRESET_PATH = '/samples/phase-03/0305/sample-0305-tile-map-editor.json';
20+
21+
const FALLBACK_TILE_ROWS = [
22+
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
23+
[1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 1],
24+
[1, 0, 0, 2, 2, 0, 0, 0, 0, 3, 3, 0, 0, 1],
25+
[1, 0, 0, 2, 2, 0, 0, 0, 0, 3, 3, 0, 0, 1],
26+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
27+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 1],
28+
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
29+
];
30+
31+
const FALLBACK_TILE_METADATA_DEFINITIONS = Object.freeze({
32+
0: { label: 'floor', color: '#1f2937', solid: false },
33+
1: { label: 'wall', color: '#4338ca', solid: true },
34+
2: { label: 'hazard', color: '#dc2626', solid: false, hazard: true, respawnMessage: 'Hazard tile touched. Reset.' },
35+
3: { label: 'trigger', color: '#059669', solid: false, trigger: 'goal-flag', message: 'Trigger tile activated.' },
36+
4: { label: 'slope', color: '#a855f7', solid: false, slope: 'placeholder-up-right', message: 'Slope metadata placeholder.' }
37+
});
38+
39+
function cloneRows(rows = []) {
40+
return rows.map((row) => Array.isArray(row) ? row.map((value) => Number.parseInt(value, 10) || 0) : []);
41+
}
42+
43+
function buildCollisionRows(rows = [], definitions = {}) {
44+
return rows.map((row) => row.map((tileId) => (definitions[tileId]?.solid ? 1 : 0)));
45+
}
46+
47+
function createFallbackTilemapDocument() {
48+
const rows = cloneRows(FALLBACK_TILE_ROWS);
49+
const definitions = JSON.parse(JSON.stringify(FALLBACK_TILE_METADATA_DEFINITIONS));
50+
return {
51+
schema: 'toolbox.tilemap/1',
52+
version: 1,
53+
map: {
54+
name: 'sample-0305-tile-metadata',
55+
width: rows[0]?.length || 14,
56+
height: rows.length || 7,
57+
tileSize: 48
58+
},
59+
tileset: Object.entries(definitions).map(([id, entry]) => ({
60+
id: Number(id),
61+
name: entry.label || `tile-${id}`,
62+
color: entry.color || '#64748b'
63+
})),
64+
layers: [
65+
{
66+
id: 'ground',
67+
name: 'Ground',
68+
kind: 'tile',
69+
visible: true,
70+
locked: false,
71+
data: rows
72+
},
73+
{
74+
id: 'collision',
75+
name: 'Collision',
76+
kind: 'collision',
77+
visible: true,
78+
locked: false,
79+
data: buildCollisionRows(rows, definitions)
80+
},
81+
{
82+
id: 'data',
83+
name: 'Data',
84+
kind: 'data',
85+
visible: true,
86+
locked: false,
87+
data: rows.map((row) => row.map(() => 0))
88+
}
89+
],
90+
markers: [
91+
{ id: 'spawn-1', type: 'spawn', name: 'player-start', col: 12, row: 5, properties: {} }
92+
],
93+
tileMetadataDefinitions: definitions
94+
};
95+
}
1996

2097
export default class TileMetadataScene extends Scene {
2198
constructor() {
2299
super();
23100
this.screen = { x: 60, y: 240 };
24101
this.loader = new ImageAssetLoader();
25-
this.tilemap = new Tilemap({
26-
tileSize: 48,
27-
tiles: [
28-
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
29-
[1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 1],
30-
[1, 0, 0, 2, 2, 0, 0, 0, 0, 3, 3, 0, 0, 1],
31-
[1, 0, 0, 2, 2, 0, 0, 0, 0, 3, 3, 0, 0, 1],
32-
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
33-
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 1],
34-
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
35-
],
36-
definitions: {
37-
0: { label: 'floor', color: '#1f2937', solid: false },
38-
1: { label: 'wall', color: '#4338ca', solid: true },
39-
2: { label: 'hazard', color: '#dc2626', solid: false, hazard: true, respawnMessage: 'Hazard tile touched. Reset.' },
40-
3: { label: 'trigger', color: '#059669', solid: false, trigger: 'goal-flag', message: 'Trigger tile activated.' },
41-
4: { label: 'slope', color: '#a855f7', solid: false, slope: 'placeholder-up-right', message: 'Slope metadata placeholder.' },
42-
},
43-
palette: { 0: '#1f2937', 1: '#4338ca' },
44-
});
45-
this.world = {
46-
width: this.tilemap.width * this.tilemap.tileSize,
47-
height: this.tilemap.height * this.tilemap.tileSize,
48-
};
49-
this.camera = new Camera2D({
50-
viewportWidth: 860,
51-
viewportHeight: 300,
52-
worldWidth: this.world.width,
53-
worldHeight: this.world.height,
54-
});
55-
56102
this.atlas = new SpriteAtlas({
57103
frames: {
58104
idle_0: { x: 0, y: 0, width: 16, height: 16 },
@@ -61,7 +107,8 @@ export default class TileMetadataScene extends Scene {
61107
},
62108
});
63109
this.asset = { status: 'generated-loaded', image: createDemoSpriteSheet() };
64-
//this.spawn = { x: 48, y: 48 };
110+
this.sampleStatus = 'Loading shared tilemap preset...';
111+
this.sampleError = '';
65112
this.spawn = { x: 582, y: 246 };
66113
this.player = {
67114
x: this.spawn.x,
@@ -87,6 +134,136 @@ export default class TileMetadataScene extends Scene {
87134
});
88135
this.metadataNote = 'Walk onto colored metadata tiles.';
89136
this.flags = { goalTriggered: false };
137+
this.applyTilemapDocument(createFallbackTilemapDocument());
138+
void this.loadTilemapPreset();
139+
}
140+
141+
extractTileMapDocumentFromSamplePreset(rawPreset) {
142+
if (!rawPreset || typeof rawPreset !== 'object') {
143+
return null;
144+
}
145+
const payload = rawPreset.payload;
146+
if (payload && typeof payload === 'object') {
147+
if (payload.tilemapDocument && typeof payload.tilemapDocument === 'object') {
148+
return payload.tilemapDocument;
149+
}
150+
if (payload.tileMapDocument && typeof payload.tileMapDocument === 'object') {
151+
return payload.tileMapDocument;
152+
}
153+
if (payload.tilemap && typeof payload.tilemap === 'object') {
154+
return payload.tilemap;
155+
}
156+
if (payload.tileMap && typeof payload.tileMap === 'object') {
157+
return payload.tileMap;
158+
}
159+
if (typeof payload.tilemapDocumentPath === 'string' && payload.tilemapDocumentPath.trim()) {
160+
return payload.tilemapDocumentPath.trim();
161+
}
162+
if (typeof payload.tileMapDocumentPath === 'string' && payload.tileMapDocumentPath.trim()) {
163+
return payload.tileMapDocumentPath.trim();
164+
}
165+
}
166+
return null;
167+
}
168+
169+
applyTilemapDocument(documentModel) {
170+
const map = documentModel?.map || {};
171+
const layers = Array.isArray(documentModel?.layers) ? documentModel.layers : [];
172+
const tileLayer = layers.find((layer) => layer && layer.kind === 'tile') || layers[0] || null;
173+
const tileRows = cloneRows(tileLayer?.data || []);
174+
if (!Array.isArray(tileRows) || tileRows.length === 0) {
175+
throw new Error('Tilemap document did not include tile rows.');
176+
}
177+
178+
const rawDefinitions = documentModel?.tileMetadataDefinitions
179+
&& typeof documentModel.tileMetadataDefinitions === 'object'
180+
? documentModel.tileMetadataDefinitions
181+
: FALLBACK_TILE_METADATA_DEFINITIONS;
182+
183+
const definitions = {};
184+
const palette = {};
185+
Object.entries(rawDefinitions).forEach(([key, raw]) => {
186+
const tileId = Number.parseInt(key, 10);
187+
if (!Number.isInteger(tileId) || !raw || typeof raw !== 'object') {
188+
return;
189+
}
190+
definitions[tileId] = {
191+
label: typeof raw.label === 'string' ? raw.label : `tile-${tileId}`,
192+
color: typeof raw.color === 'string' ? raw.color : '#64748b',
193+
solid: raw.solid === true,
194+
hazard: raw.hazard === true,
195+
trigger: typeof raw.trigger === 'string' ? raw.trigger : undefined,
196+
message: typeof raw.message === 'string' ? raw.message : undefined,
197+
respawnMessage: typeof raw.respawnMessage === 'string' ? raw.respawnMessage : undefined,
198+
slope: typeof raw.slope === 'string' ? raw.slope : undefined
199+
};
200+
palette[tileId] = definitions[tileId].color;
201+
});
202+
203+
const tileSize = Number(map.tileSize) > 0 ? Number(map.tileSize) : 48;
204+
this.tilemap = new Tilemap({
205+
tileSize,
206+
tiles: tileRows,
207+
definitions,
208+
palette
209+
});
210+
211+
this.world = {
212+
width: this.tilemap.width * this.tilemap.tileSize,
213+
height: this.tilemap.height * this.tilemap.tileSize
214+
};
215+
this.camera = new Camera2D({
216+
viewportWidth: 860,
217+
viewportHeight: 300,
218+
worldWidth: this.world.width,
219+
worldHeight: this.world.height
220+
});
221+
222+
const spawnMarker = Array.isArray(documentModel?.markers)
223+
? documentModel.markers.find((entry) => entry && entry.type === 'spawn')
224+
: null;
225+
const spawnCol = Number.parseInt(spawnMarker?.col, 10);
226+
const spawnRow = Number.parseInt(spawnMarker?.row, 10);
227+
if (Number.isInteger(spawnCol) && Number.isInteger(spawnRow)) {
228+
this.spawn = {
229+
x: spawnCol * this.tilemap.tileSize + 6,
230+
y: spawnRow * this.tilemap.tileSize + 6
231+
};
232+
}
233+
this.resetPlayerToSpawn();
234+
}
235+
236+
async loadTilemapPreset() {
237+
try {
238+
const presetResponse = await fetch(TILEMAP_PRESET_PATH, { cache: 'no-store' });
239+
if (!presetResponse.ok) {
240+
throw new Error(`Preset request failed (${presetResponse.status}).`);
241+
}
242+
const rawPreset = await presetResponse.json();
243+
const extracted = this.extractTileMapDocumentFromSamplePreset(rawPreset);
244+
let documentModel = null;
245+
246+
if (typeof extracted === 'string' && extracted.trim()) {
247+
const documentResponse = await fetch(extracted.trim(), { cache: 'no-store' });
248+
if (!documentResponse.ok) {
249+
throw new Error(`Tilemap document request failed (${documentResponse.status}).`);
250+
}
251+
documentModel = await documentResponse.json();
252+
} else if (extracted && typeof extracted === 'object') {
253+
documentModel = extracted;
254+
}
255+
256+
if (!documentModel || typeof documentModel !== 'object') {
257+
throw new Error('Preset payload did not include a tilemap document.');
258+
}
259+
260+
this.applyTilemapDocument(documentModel);
261+
this.sampleStatus = 'Loaded tilemap preset from sample-0305-tile-map-editor.json';
262+
this.sampleError = '';
263+
} catch (error) {
264+
this.sampleStatus = 'Using fallback in-scene tilemap.';
265+
this.sampleError = error instanceof Error ? error.message : String(error);
266+
}
90267
}
91268

92269
getOverlappedMetadata() {
@@ -169,10 +346,12 @@ export default class TileMetadataScene extends Scene {
169346
drawFrame(renderer, theme, [
170347
'Engine sample 0305',
171348
'Extends the tilemap from solid/not-solid into hazard, trigger, and slope-style metadata',
349+
'This sample and Tilemap Studio load the same sample-0305-tile-map-editor.json source',
172350
'Red tiles reset the actor (to spawn point)',
173351
'Green tiles trigger a flag,',
174352
'Purple tiles prove schema room for slopes',
175353
'Only blue wall tiles are solid blockers',
354+
this.sampleError ? `${this.sampleStatus} (${this.sampleError})` : this.sampleStatus,
176355
this.metadataNote,
177356
]);
178357

0 commit comments

Comments
 (0)