@waica/cli 0.6.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/editor/assets/{cssMode-D3SZSKEA.js → cssMode-DwPoqXFO.js} +1 -1
- package/dist/editor/assets/{freemarker2-BH1pa3TU.js → freemarker2-gl5y25bH.js} +1 -1
- package/dist/editor/assets/{handlebars-Cf7meNHo.js → handlebars-DHUb8mb3.js} +1 -1
- package/dist/editor/assets/{html-6hqYzl5P.js → html-C8W-psr4.js} +1 -1
- package/dist/editor/assets/{htmlMode-D_IiyUdi.js → htmlMode-BiF5TH2_.js} +1 -1
- package/dist/editor/assets/{index-Cy5pmBhN.css → index-CzarIYLW.css} +1 -1
- package/dist/editor/assets/{index-CJyB1cBF.js → index-DtMwWp8R.js} +6380 -2495
- package/dist/editor/assets/{javascript-CTeTqLj8.js → javascript-D_t2jyWi.js} +1 -1
- package/dist/editor/assets/{jsonMode-t8AC-38H.js → jsonMode-CZ0SltDj.js} +1 -1
- package/dist/editor/assets/{liquid-Dc6NoKas.js → liquid-DUxURZui.js} +1 -1
- package/dist/editor/assets/{mdx-DCPtX-bs.js → mdx-CDIQ7hmo.js} +1 -1
- package/dist/editor/assets/{python-DXAju_oJ.js → python-Cra2N916.js} +1 -1
- package/dist/editor/assets/{razor-DqQldpv6.js → razor-B3qV-mNp.js} +1 -1
- package/dist/editor/assets/{tsMode-B60EhHlZ.js → tsMode-Ci457z4v.js} +1 -1
- package/dist/editor/assets/{typescript-C4LyYAns.js → typescript-BXjh3GLT.js} +1 -1
- package/dist/editor/assets/{xml-B0QcT5_Z.js → xml-DQuEpqk5.js} +1 -1
- package/dist/editor/assets/{yaml-CEpyO2Qc.js → yaml-CQA7WIT-.js} +1 -1
- package/dist/editor/index.html +2 -2
- package/dist/mcp/archetypes.js +6 -2
- package/dist/mcp/create-project.js +12 -6
- package/dist/mcp/known-archetypes.js +24 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/ATTRIBUTION.md +72 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/waica-iso-crate.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/waica-iso-ground.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/waica-iso-hero.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/waica-iso-orc.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/waica-iso-rock.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/waica-iso-tree.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/assets/waica-iso-villager.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/animation.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/animation.js +10 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/art.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/art.js +10 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/bundle.d.ts +2 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/bundle.js +9 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/controls.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/controls.js +14 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/index.d.ts +24 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/index.js +15 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/manifest.d.ts +16 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/manifest.js +29 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/prefabs.d.ts +18 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/prefabs.js +196 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/registry-data.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/registry-data.js +46 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/registry.d.ts +4 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/registry.js +17 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/scene-default.d.ts +5 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/scene-default.js +49 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/ui.d.ts +1 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/dist/ui.js +17 -0
- package/dist/mcp/node_modules/@waica/archetype-isometric/package.json +30 -0
- package/dist/mcp/node_modules/@waica/archetype-platformer/package.json +3 -3
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/ATTRIBUTION.md +28 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-blob.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-fence.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-grass.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-hero.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-npc.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-path.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-potion.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-tree.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/assets/waica-water.png +0 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/animation.d.ts +7 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/animation.js +10 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/art.d.ts +10 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/art.js +19 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/bundle.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/bundle.js +10 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/controls.d.ts +5 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/controls.js +16 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/index.d.ts +25 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/index.js +16 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/manifest.d.ts +16 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/manifest.js +29 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/prefabs.d.ts +11 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/prefabs.js +178 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/registry-data.d.ts +9 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/registry-data.js +55 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/registry.d.ts +6 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/registry.js +21 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/scene-default.d.ts +57 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/scene-default.js +110 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/ui.d.ts +10 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/dist/ui.js +26 -0
- package/dist/mcp/node_modules/@waica/archetype-topdown/package.json +30 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/grid-motor.d.ts +56 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/grid-motor.js +74 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/grid-player-role.d.ts +15 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/grid-player-role.js +71 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/index.d.ts +5 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/index.js +5 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/interactable.d.ts +34 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/interactable.js +72 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/iso-motor.d.ts +14 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/iso-motor.js +36 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/iso-player-states.d.ts +5 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/iso-player-states.js +9 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/platformer-motor.d.ts +4 -2
- package/dist/mcp/node_modules/@waica/behaviors/dist/platformer-motor.js +5 -1
- package/dist/mcp/node_modules/@waica/behaviors/dist/topdown-motor.d.ts +18 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/topdown-motor.js +31 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/topdown-player-states.d.ts +5 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/topdown-player-states.js +14 -0
- package/dist/mcp/node_modules/@waica/behaviors/package.json +2 -2
- package/dist/mcp/node_modules/@waica/engine/dist/animation/directional.d.ts +48 -0
- package/dist/mcp/node_modules/@waica/engine/dist/animation/directional.js +68 -0
- package/dist/mcp/node_modules/@waica/engine/dist/archetype.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/engine/dist/camera.d.ts +19 -0
- package/dist/mcp/node_modules/@waica/engine/dist/camera.js +10 -0
- package/dist/mcp/node_modules/@waica/engine/dist/component.d.ts +2 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/animated-sprite.d.ts +27 -2
- package/dist/mcp/node_modules/@waica/engine/dist/components/animated-sprite.js +50 -5
- package/dist/mcp/node_modules/@waica/engine/dist/components/dynamic-body.js +2 -4
- package/dist/mcp/node_modules/@waica/engine/dist/components/sprite.d.ts +23 -1
- package/dist/mcp/node_modules/@waica/engine/dist/components/sprite.js +49 -8
- package/dist/mcp/node_modules/@waica/engine/dist/components/tilemap.d.ts +113 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/tilemap.js +316 -0
- package/dist/mcp/node_modules/@waica/engine/dist/entity.d.ts +3 -0
- package/dist/mcp/node_modules/@waica/engine/dist/entity.js +13 -1
- package/dist/mcp/node_modules/@waica/engine/dist/game.d.ts +13 -1
- package/dist/mcp/node_modules/@waica/engine/dist/game.js +70 -9
- package/dist/mcp/node_modules/@waica/engine/dist/index.d.ts +16 -3
- package/dist/mcp/node_modules/@waica/engine/dist/index.js +8 -1
- package/dist/mcp/node_modules/@waica/engine/dist/projection.d.ts +14 -0
- package/dist/mcp/node_modules/@waica/engine/dist/projection.js +30 -0
- package/dist/mcp/node_modules/@waica/engine/dist/render-sort.d.ts +31 -0
- package/dist/mcp/node_modules/@waica/engine/dist/render-sort.js +39 -0
- package/dist/mcp/node_modules/@waica/engine/dist/scene-solids.d.ts +16 -0
- package/dist/mcp/node_modules/@waica/engine/dist/scene-solids.js +28 -0
- package/dist/mcp/node_modules/@waica/engine/dist/scene.d.ts +12 -0
- package/dist/mcp/node_modules/@waica/engine/dist/scene.js +1 -0
- package/dist/mcp/node_modules/@waica/engine/dist/solid-axis.js +2 -4
- package/dist/mcp/node_modules/@waica/engine/dist/sprite-placement.d.ts +22 -0
- package/dist/mcp/node_modules/@waica/engine/dist/sprite-placement.js +14 -0
- package/dist/mcp/node_modules/@waica/engine/dist/state/hooks.d.ts +1 -1
- package/dist/mcp/node_modules/@waica/engine/dist/state/hooks.js +3 -1
- package/dist/mcp/node_modules/@waica/engine/dist/state/state-machine.d.ts +18 -0
- package/dist/mcp/node_modules/@waica/engine/dist/state/state-machine.js +57 -1
- package/dist/mcp/node_modules/@waica/engine/dist/tilemap-grid.d.ts +26 -0
- package/dist/mcp/node_modules/@waica/engine/dist/tilemap-grid.js +41 -0
- package/dist/mcp/node_modules/@waica/engine/package.json +1 -1
- package/dist/mcp/package-resolver.js +14 -4
- package/dist/mcp/project-component-loader.js +2 -1
- package/dist/mcp/project-component-runner.js +5 -0
- package/dist/mcp/server.js +18 -1
- package/dist/mcp/template/package.json.tpl +1 -1
- package/dist/mcp/template/src/main.ts +8 -4
- package/dist/mcp/workspace-runtime.js +41 -10
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Y-sort draw ordering: an opt-in render mode (scene JSON `render.sort: 'y'`)
|
|
3
|
+
* for top-down scenes where "lower on screen" means "closer to the camera".
|
|
4
|
+
*/
|
|
5
|
+
export interface YSortEntry {
|
|
6
|
+
/** The sprite's layer — the primary draw-order band, exactly as without y-sort. */
|
|
7
|
+
layer: number;
|
|
8
|
+
/** The owning entity's world Y — the sort key. Sprite offsets don't shift it. */
|
|
9
|
+
y: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The explicit seam a component opts into to participate in y-sort: it
|
|
13
|
+
* exposes its draw-order layer and accepts the per-frame z the pass derives.
|
|
14
|
+
* Both stock sprite classes implement it; a custom renderable can too.
|
|
15
|
+
*/
|
|
16
|
+
export interface YSortParticipant {
|
|
17
|
+
readonly layer: number;
|
|
18
|
+
/** Y-sort pass hook: overrides the layer-derived z for this frame. */
|
|
19
|
+
setSortZ(z: number): void;
|
|
20
|
+
}
|
|
21
|
+
export declare function isYSortParticipant(value: unknown): value is YSortParticipant;
|
|
22
|
+
/**
|
|
23
|
+
* Z per entry under y-sort. Each layer keeps its 0.01 band; within a band,
|
|
24
|
+
* lower Y gets a higher z (renders in front), and exact Y ties keep input
|
|
25
|
+
* order. Offsets stay strictly inside (layer, layer + 1) × 0.01 for integer
|
|
26
|
+
* layers — but a fractional layer (e.g. 0.5) can sit closer than that to the
|
|
27
|
+
* next one present, so each band is capped at the gap to the next distinct
|
|
28
|
+
* layer above it, never wider than 0.01. Integer layers are always >= 1
|
|
29
|
+
* apart, so their band is exactly the old fixed 0.01.
|
|
30
|
+
*/
|
|
31
|
+
export declare function ySortZ(entries: readonly YSortEntry[]): number[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function isYSortParticipant(value) {
|
|
2
|
+
return (typeof value === 'object' &&
|
|
3
|
+
value !== null &&
|
|
4
|
+
typeof value.layer === 'number' &&
|
|
5
|
+
typeof value.setSortZ === 'function');
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Z per entry under y-sort. Each layer keeps its 0.01 band; within a band,
|
|
9
|
+
* lower Y gets a higher z (renders in front), and exact Y ties keep input
|
|
10
|
+
* order. Offsets stay strictly inside (layer, layer + 1) × 0.01 for integer
|
|
11
|
+
* layers — but a fractional layer (e.g. 0.5) can sit closer than that to the
|
|
12
|
+
* next one present, so each band is capped at the gap to the next distinct
|
|
13
|
+
* layer above it, never wider than 0.01. Integer layers are always >= 1
|
|
14
|
+
* apart, so their band is exactly the old fixed 0.01.
|
|
15
|
+
*/
|
|
16
|
+
export function ySortZ(entries) {
|
|
17
|
+
const byLayer = new Map();
|
|
18
|
+
for (const [index, entry] of entries.entries()) {
|
|
19
|
+
const group = byLayer.get(entry.layer);
|
|
20
|
+
if (group)
|
|
21
|
+
group.push(index);
|
|
22
|
+
else
|
|
23
|
+
byLayer.set(entry.layer, [index]);
|
|
24
|
+
}
|
|
25
|
+
const layers = [...byLayer.keys()].sort((a, b) => a - b);
|
|
26
|
+
const z = new Array(entries.length);
|
|
27
|
+
for (const [i, layer] of layers.entries()) {
|
|
28
|
+
const indices = byLayer.get(layer);
|
|
29
|
+
const next = layers[i + 1];
|
|
30
|
+
const width = next === undefined ? 0.01 : Math.min(0.01, (next - layer) * 0.01);
|
|
31
|
+
// Stable sort: back-to-front is descending Y, ties keep input order.
|
|
32
|
+
const ordered = [...indices].sort((a, b) => entries[b].y - entries[a].y);
|
|
33
|
+
const step = width / (ordered.length + 1);
|
|
34
|
+
for (const [rank, index] of ordered.entries()) {
|
|
35
|
+
z[index] = layer * 0.01 + (rank + 1) * step;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return z;
|
|
39
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Solid } from './components/solid.js';
|
|
2
|
+
import type { Entity } from './entity.js';
|
|
3
|
+
import type { Game } from './game.js';
|
|
4
|
+
/** Explicit opt-in brand for components that derive collision Solids. */
|
|
5
|
+
export declare const SOLID_SOURCE_SYMBOL: unique symbol;
|
|
6
|
+
/** Component seam for geometry that derives one or more collision Solids. */
|
|
7
|
+
export interface SolidSource {
|
|
8
|
+
readonly [SOLID_SOURCE_SYMBOL]: true;
|
|
9
|
+
solids(): readonly Solid[];
|
|
10
|
+
}
|
|
11
|
+
export declare function isSolidSource(value: unknown): value is SolidSource;
|
|
12
|
+
/**
|
|
13
|
+
* All static collision geometry in stable entity/component order. Without a
|
|
14
|
+
* SolidSource this is exactly one direct Solid per entity, as before.
|
|
15
|
+
*/
|
|
16
|
+
export declare function sceneSolids(game: Game, except?: Entity): Solid[];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Solid } from './components/solid.js';
|
|
2
|
+
/** Explicit opt-in brand for components that derive collision Solids. */
|
|
3
|
+
export const SOLID_SOURCE_SYMBOL = Symbol('waica.solidSource');
|
|
4
|
+
export function isSolidSource(value) {
|
|
5
|
+
if (value === null || typeof value !== 'object')
|
|
6
|
+
return false;
|
|
7
|
+
const candidate = value;
|
|
8
|
+
return candidate[SOLID_SOURCE_SYMBOL] === true && typeof candidate.solids === 'function';
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* All static collision geometry in stable entity/component order. Without a
|
|
12
|
+
* SolidSource this is exactly one direct Solid per entity, as before.
|
|
13
|
+
*/
|
|
14
|
+
export function sceneSolids(game, except) {
|
|
15
|
+
const result = [];
|
|
16
|
+
for (const entity of game.entities) {
|
|
17
|
+
if (entity === except)
|
|
18
|
+
continue;
|
|
19
|
+
const direct = entity.get(Solid);
|
|
20
|
+
if (direct)
|
|
21
|
+
result.push(direct);
|
|
22
|
+
for (const component of entity.components ?? []) {
|
|
23
|
+
if (isSolidSource(component))
|
|
24
|
+
result.push(...component.solids());
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
@@ -28,10 +28,22 @@ export interface PrefabJson {
|
|
|
28
28
|
type: 'character' | 'object' | 'tile';
|
|
29
29
|
components: SceneComponentJson[];
|
|
30
30
|
}
|
|
31
|
+
/** Scene-wide render options (v3). */
|
|
32
|
+
export interface SceneRenderJson {
|
|
33
|
+
/**
|
|
34
|
+
* 'y' orders same-layer sprites by their render-space Y — lower Y renders
|
|
35
|
+
* in front (top-down depth). Absent: spawn order breaks same-layer ties.
|
|
36
|
+
*/
|
|
37
|
+
sort?: 'y';
|
|
38
|
+
/** Logical positions are projected to the fixed 2:1 render lattice. */
|
|
39
|
+
projection?: 'isometric';
|
|
40
|
+
}
|
|
31
41
|
export interface SceneJson {
|
|
32
42
|
waicaScene: 1 | 2 | 3;
|
|
33
43
|
/** The scene's built-in camera (v3); absent = the host keeps control. */
|
|
34
44
|
camera?: SceneCameraJson;
|
|
45
|
+
/** Draw-order policy (v3); absent = layer bands with spawn-order ties. */
|
|
46
|
+
render?: SceneRenderJson;
|
|
35
47
|
entities: SceneEntityJson[];
|
|
36
48
|
/** UI pieces (src/ui/*.html) mounted visible when the scene loads. */
|
|
37
49
|
ui?: string[];
|
|
@@ -82,6 +82,7 @@ export function spawnFromJson(game, json, registry) {
|
|
|
82
82
|
/** Loads a full scene into the game. */
|
|
83
83
|
export function loadScene(game, scene, registry) {
|
|
84
84
|
game.registry = registry;
|
|
85
|
+
game.setSceneRender(scene.render);
|
|
85
86
|
for (const entityJson of scene.entities)
|
|
86
87
|
spawnFromJson(game, entityJson, registry);
|
|
87
88
|
// After the spawns: with a follow target, the camera starts centered on it.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { collisionBounds, collisionOverlap } from './collision-shape.js';
|
|
2
2
|
import { Solid } from './components/solid.js';
|
|
3
|
+
import { sceneSolids } from './scene-solids.js';
|
|
3
4
|
const CONTACT_TOLERANCE = 1e-7;
|
|
4
5
|
function bodyFor(solid) {
|
|
5
6
|
return {
|
|
@@ -19,10 +20,7 @@ function bodyFor(solid) {
|
|
|
19
20
|
export function resolveSolidAxis({ entity, axis, previous, body, onContact, }) {
|
|
20
21
|
const position = entity.position;
|
|
21
22
|
const target = position[axis];
|
|
22
|
-
const solids = entity.game
|
|
23
|
-
.filter((other) => other !== entity)
|
|
24
|
-
.map((other) => other.get(Solid))
|
|
25
|
-
.filter((solid) => solid !== undefined);
|
|
23
|
+
const solids = sceneSolids(entity.game, entity);
|
|
26
24
|
position[axis] = previous;
|
|
27
25
|
// Preserve the established spawn-inside-wall bail: pre-existing overlaps
|
|
28
26
|
// are ignored for this move rather than becoming arbitrary push-out rules.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface SpritePlacementInput {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
offsetX: number;
|
|
5
|
+
offsetY: number;
|
|
6
|
+
anchorX: number;
|
|
7
|
+
anchorY: number;
|
|
8
|
+
flipX: boolean;
|
|
9
|
+
frameScaleX: number;
|
|
10
|
+
frameScaleY: number;
|
|
11
|
+
}
|
|
12
|
+
export interface SpritePlacement {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
scaleX: number;
|
|
16
|
+
scaleY: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Places a displayed frame inside its full-size sprite box. The declared
|
|
20
|
+
* anchor belongs to that box; smaller animation frames retain its floor.
|
|
21
|
+
*/
|
|
22
|
+
export declare function spritePlacement(input: SpritePlacementInput): SpritePlacement;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Places a displayed frame inside its full-size sprite box. The declared
|
|
3
|
+
* anchor belongs to that box; smaller animation frames retain its floor.
|
|
4
|
+
*/
|
|
5
|
+
export function spritePlacement(input) {
|
|
6
|
+
const boxX = input.offsetX + (0.5 - input.anchorX) * input.width;
|
|
7
|
+
const floorY = input.offsetY - input.anchorY * input.height;
|
|
8
|
+
return {
|
|
9
|
+
x: input.flipX ? -boxX : boxX,
|
|
10
|
+
y: floorY + (input.height * input.frameScaleY) / 2,
|
|
11
|
+
scaleX: input.width * input.frameScaleX * (input.flipX ? -1 : 1),
|
|
12
|
+
scaleY: input.height * input.frameScaleY,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -63,7 +63,7 @@ export interface ArchetypeBundle {
|
|
|
63
63
|
/** Extra named logic sets that are not themselves roles. */
|
|
64
64
|
logicSets?: Readonly<Record<string, StateLogic>>;
|
|
65
65
|
}
|
|
66
|
-
/** Clears role definitions
|
|
66
|
+
/** Clears role definitions, every named logic set and the directional contract. */
|
|
67
67
|
export declare function resetRegistries(): void;
|
|
68
68
|
/**
|
|
69
69
|
* Replaces the active registry contents with one archetype's complete bundle.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { installDirectionalAnimation } from '../animation/directional.js';
|
|
1
2
|
const sets = new Map();
|
|
2
3
|
/**
|
|
3
4
|
* Registers state code under a logic-set name. A role's name is also its
|
|
@@ -8,10 +9,11 @@ export function defineStates(name, states) {
|
|
|
8
9
|
sets.set(name, { ...sets.get(name), ...states });
|
|
9
10
|
}
|
|
10
11
|
const roles = new Map();
|
|
11
|
-
/** Clears role definitions
|
|
12
|
+
/** Clears role definitions, every named logic set and the directional contract. */
|
|
12
13
|
export function resetRegistries() {
|
|
13
14
|
roles.clear();
|
|
14
15
|
sets.clear();
|
|
16
|
+
installDirectionalAnimation(null);
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* Replaces the active registry contents with one archetype's complete bundle.
|
|
@@ -66,6 +66,8 @@ export declare class StateMachine extends Component {
|
|
|
66
66
|
private readonly instanceHooks;
|
|
67
67
|
private readonly signals;
|
|
68
68
|
private readonly warnedClips;
|
|
69
|
+
/** Facing last used to resolve a directional clip — detects a turn mid-state. */
|
|
70
|
+
private lastFacing;
|
|
69
71
|
onReady(): void;
|
|
70
72
|
/** Adds instance-level hooks on top of the logic set — the escape hatch. */
|
|
71
73
|
on(state: string, hooks: StateHooks): void;
|
|
@@ -81,4 +83,20 @@ export declare class StateMachine extends Component {
|
|
|
81
83
|
private runCollision;
|
|
82
84
|
private enter;
|
|
83
85
|
private playClip;
|
|
86
|
+
/**
|
|
87
|
+
* Directional resolution: with an installed contract AND a sibling that
|
|
88
|
+
* reports facing, plays state × facing (mirroring included). Returns false
|
|
89
|
+
* to keep the name-based path — no contract, no facing, no playable clip.
|
|
90
|
+
*/
|
|
91
|
+
private playDirectionalClip;
|
|
92
|
+
private facingProvider;
|
|
93
|
+
/**
|
|
94
|
+
* playDirectionalClip only runs at enter() time (via playClip), so a
|
|
95
|
+
* facing change that doesn't cross a state boundary — turning while
|
|
96
|
+
* still walking, say — never re-resolves the clip on its own. Catch that
|
|
97
|
+
* here every frame: same state, new facing, re-run the resolution.
|
|
98
|
+
* AnimatedSprite.play() no-ops on an unchanged clip name, so this is
|
|
99
|
+
* cheap when facing hasn't moved the resolved clip.
|
|
100
|
+
*/
|
|
101
|
+
private reresolveOnFacingChange;
|
|
84
102
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { installedDirectionalAnimation, isAnimationFacingProvider, resolveDirectionalClip, } from '../animation/directional.js';
|
|
1
2
|
import { Component } from '../component.js';
|
|
2
3
|
import { AnimatedSprite } from '../components/animated-sprite.js';
|
|
3
4
|
import { closestLogicSet, logicSet, registeredLogicSets, } from './hooks.js';
|
|
@@ -47,7 +48,14 @@ export class StateMachine extends Component {
|
|
|
47
48
|
static params = {
|
|
48
49
|
role: { label: 'Role' },
|
|
49
50
|
};
|
|
50
|
-
static transient = [
|
|
51
|
+
static transient = [
|
|
52
|
+
'current',
|
|
53
|
+
'elapsed',
|
|
54
|
+
'instanceHooks',
|
|
55
|
+
'signals',
|
|
56
|
+
'warnedClips',
|
|
57
|
+
'lastFacing',
|
|
58
|
+
];
|
|
51
59
|
/** The character's role — names the logic set providing its state code. */
|
|
52
60
|
role = '';
|
|
53
61
|
/** Starting state; defaults to the first declared state. */
|
|
@@ -60,6 +68,8 @@ export class StateMachine extends Component {
|
|
|
60
68
|
instanceHooks = new Map();
|
|
61
69
|
signals = new Set();
|
|
62
70
|
warnedClips = new Set();
|
|
71
|
+
/** Facing last used to resolve a directional clip — detects a turn mid-state. */
|
|
72
|
+
lastFacing;
|
|
63
73
|
onReady() {
|
|
64
74
|
if (this.role && !logicSet(this.role)) {
|
|
65
75
|
const sets = registeredLogicSets();
|
|
@@ -111,6 +121,7 @@ export class StateMachine extends Component {
|
|
|
111
121
|
this.enter(edge.to);
|
|
112
122
|
}
|
|
113
123
|
this.signals.clear();
|
|
124
|
+
this.reresolveOnFacingChange();
|
|
114
125
|
}
|
|
115
126
|
onCollide(other) {
|
|
116
127
|
// The state that was active when the contact happened owns it: a
|
|
@@ -167,6 +178,8 @@ export class StateMachine extends Component {
|
|
|
167
178
|
if (!sprite)
|
|
168
179
|
return;
|
|
169
180
|
const clip = this.states[state]?.clip ?? state;
|
|
181
|
+
if (this.playDirectionalClip(sprite, clip))
|
|
182
|
+
return;
|
|
170
183
|
if (sprite.clips[clip]) {
|
|
171
184
|
sprite.play(clip);
|
|
172
185
|
}
|
|
@@ -176,4 +189,47 @@ export class StateMachine extends Component {
|
|
|
176
189
|
`keeping "${sprite.current ?? 'none'}"`);
|
|
177
190
|
}
|
|
178
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Directional resolution: with an installed contract AND a sibling that
|
|
194
|
+
* reports facing, plays state × facing (mirroring included). Returns false
|
|
195
|
+
* to keep the name-based path — no contract, no facing, no playable clip.
|
|
196
|
+
*/
|
|
197
|
+
playDirectionalClip(sprite, clip) {
|
|
198
|
+
const animation = installedDirectionalAnimation();
|
|
199
|
+
if (!animation)
|
|
200
|
+
return false;
|
|
201
|
+
const provider = this.facingProvider();
|
|
202
|
+
if (!provider)
|
|
203
|
+
return false;
|
|
204
|
+
const facing = provider.getAnimationFacing();
|
|
205
|
+
this.lastFacing = facing;
|
|
206
|
+
const resolved = resolveDirectionalClip(animation, Object.keys(sprite.clips), clip, facing);
|
|
207
|
+
if (!resolved.clip || !sprite.clips[resolved.clip])
|
|
208
|
+
return false;
|
|
209
|
+
sprite.setFlipX(resolved.flip);
|
|
210
|
+
sprite.play(resolved.clip);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
facingProvider() {
|
|
214
|
+
return this.entity.components.find((c) => isAnimationFacingProvider(c));
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* playDirectionalClip only runs at enter() time (via playClip), so a
|
|
218
|
+
* facing change that doesn't cross a state boundary — turning while
|
|
219
|
+
* still walking, say — never re-resolves the clip on its own. Catch that
|
|
220
|
+
* here every frame: same state, new facing, re-run the resolution.
|
|
221
|
+
* AnimatedSprite.play() no-ops on an unchanged clip name, so this is
|
|
222
|
+
* cheap when facing hasn't moved the resolved clip.
|
|
223
|
+
*/
|
|
224
|
+
reresolveOnFacingChange() {
|
|
225
|
+
if (!this.current || !installedDirectionalAnimation())
|
|
226
|
+
return;
|
|
227
|
+
const sprite = this.entity.get(AnimatedSprite);
|
|
228
|
+
if (!sprite)
|
|
229
|
+
return;
|
|
230
|
+
const provider = this.facingProvider();
|
|
231
|
+
if (!provider || provider.getAnimationFacing() === this.lastFacing)
|
|
232
|
+
return;
|
|
233
|
+
this.playDirectionalClip(sprite, this.states[this.current]?.clip ?? this.current);
|
|
234
|
+
}
|
|
179
235
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface TilemapGridSpec {
|
|
2
|
+
mapWidth: number;
|
|
3
|
+
mapHeight: number;
|
|
4
|
+
cellSize: number;
|
|
5
|
+
originX?: number;
|
|
6
|
+
originY?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface TilemapCell {
|
|
9
|
+
column: number;
|
|
10
|
+
row: number;
|
|
11
|
+
index: number;
|
|
12
|
+
}
|
|
13
|
+
export interface TilemapCellBounds {
|
|
14
|
+
left: number;
|
|
15
|
+
right: number;
|
|
16
|
+
bottom: number;
|
|
17
|
+
top: number;
|
|
18
|
+
centerX: number;
|
|
19
|
+
centerY: number;
|
|
20
|
+
}
|
|
21
|
+
/** Row-major index for a map cell, or null outside a valid map. */
|
|
22
|
+
export declare function cellIndex(mapWidth: number, mapHeight: number, column: number, row: number): number | null;
|
|
23
|
+
/** Cell containing a logical point, relative to the Tilemap entity origin. */
|
|
24
|
+
export declare function cellAt(spec: TilemapGridSpec, logicalX: number, logicalY: number): TilemapCell | null;
|
|
25
|
+
/** Logical bounds and center of one map cell. */
|
|
26
|
+
export declare function cellBounds(spec: TilemapGridSpec, column: number, row: number): TilemapCellBounds | null;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
function dimension(value) {
|
|
2
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
3
|
+
}
|
|
4
|
+
/** Row-major index for a map cell, or null outside a valid map. */
|
|
5
|
+
export function cellIndex(mapWidth, mapHeight, column, row) {
|
|
6
|
+
const width = dimension(mapWidth);
|
|
7
|
+
const height = dimension(mapHeight);
|
|
8
|
+
if (!Number.isInteger(column) || !Number.isInteger(row))
|
|
9
|
+
return null;
|
|
10
|
+
if (column < 0 || row < 0 || column >= width || row >= height)
|
|
11
|
+
return null;
|
|
12
|
+
return row * width + column;
|
|
13
|
+
}
|
|
14
|
+
/** Cell containing a logical point, relative to the Tilemap entity origin. */
|
|
15
|
+
export function cellAt(spec, logicalX, logicalY) {
|
|
16
|
+
const size = spec.cellSize;
|
|
17
|
+
if (!Number.isFinite(size) || size <= 0)
|
|
18
|
+
return null;
|
|
19
|
+
const column = Math.floor((logicalX - (spec.originX ?? 0)) / size);
|
|
20
|
+
const row = Math.floor((logicalY - (spec.originY ?? 0)) / size);
|
|
21
|
+
const index = cellIndex(spec.mapWidth, spec.mapHeight, column, row);
|
|
22
|
+
return index === null ? null : { column, row, index };
|
|
23
|
+
}
|
|
24
|
+
/** Logical bounds and center of one map cell. */
|
|
25
|
+
export function cellBounds(spec, column, row) {
|
|
26
|
+
if (cellIndex(spec.mapWidth, spec.mapHeight, column, row) === null)
|
|
27
|
+
return null;
|
|
28
|
+
const size = spec.cellSize;
|
|
29
|
+
if (!Number.isFinite(size) || size <= 0)
|
|
30
|
+
return null;
|
|
31
|
+
const left = (spec.originX ?? 0) + column * size;
|
|
32
|
+
const bottom = (spec.originY ?? 0) + row * size;
|
|
33
|
+
return {
|
|
34
|
+
left,
|
|
35
|
+
right: left + size,
|
|
36
|
+
bottom,
|
|
37
|
+
top: bottom + size,
|
|
38
|
+
centerX: left + size / 2,
|
|
39
|
+
centerY: bottom + size / 2,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -2,6 +2,7 @@ import { access, readFile, realpath } from 'node:fs/promises';
|
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { KNOWN_ARCHETYPES } from './known-archetypes.js';
|
|
5
6
|
import { workspacePackageRoot } from './workspace-runtime.js';
|
|
6
7
|
/** A declared package that is absent from both the project and MCP bundle. */
|
|
7
8
|
export class PackageUnavailableError extends Error {
|
|
@@ -15,8 +16,7 @@ export class PackageUnavailableError extends Error {
|
|
|
15
16
|
const BUNDLED_SPECIFIERS = new Set([
|
|
16
17
|
'@waica/engine',
|
|
17
18
|
'@waica/behaviors',
|
|
18
|
-
|
|
19
|
-
'@waica/archetype-platformer/manifest',
|
|
19
|
+
...KNOWN_ARCHETYPES.flatMap(({ packageName }) => [packageName, `${packageName}/manifest`]),
|
|
20
20
|
]);
|
|
21
21
|
const requireBundled = createRequire(import.meta.url);
|
|
22
22
|
/** Node resolution anchored at a game project rather than the MCP host process. */
|
|
@@ -26,8 +26,10 @@ export function projectAnchoredRequire(projectPath) {
|
|
|
26
26
|
const BUILT_ENTRY_BY_SPECIFIER = {
|
|
27
27
|
'@waica/engine': 'dist/index.js',
|
|
28
28
|
'@waica/behaviors': 'dist/index.js',
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
...Object.fromEntries(KNOWN_ARCHETYPES.flatMap(({ packageName }) => [
|
|
30
|
+
[packageName, 'dist/index.js'],
|
|
31
|
+
[`${packageName}/manifest`, 'dist/manifest.js'],
|
|
32
|
+
])),
|
|
31
33
|
};
|
|
32
34
|
function specifierFor(packageName, subpath) {
|
|
33
35
|
if (!subpath || subpath === '.')
|
|
@@ -127,6 +129,14 @@ async function loadBundledModule(specifier, packageRoot) {
|
|
|
127
129
|
return import('@waica/archetype-platformer');
|
|
128
130
|
case '@waica/archetype-platformer/manifest':
|
|
129
131
|
return import('@waica/archetype-platformer/manifest');
|
|
132
|
+
case '@waica/archetype-topdown':
|
|
133
|
+
return import('@waica/archetype-topdown');
|
|
134
|
+
case '@waica/archetype-topdown/manifest':
|
|
135
|
+
return import('@waica/archetype-topdown/manifest');
|
|
136
|
+
case '@waica/archetype-isometric':
|
|
137
|
+
return import('@waica/archetype-isometric');
|
|
138
|
+
case '@waica/archetype-isometric/manifest':
|
|
139
|
+
return import('@waica/archetype-isometric/manifest');
|
|
130
140
|
default:
|
|
131
141
|
throw new PackageUnavailableError(specifier);
|
|
132
142
|
}
|
|
@@ -7,6 +7,7 @@ import { tmpdir } from 'node:os';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { promisify } from 'node:util';
|
|
10
|
+
import { KNOWN_ARCHETYPES } from './known-archetypes.js';
|
|
10
11
|
import { directFiles } from './project-path.js';
|
|
11
12
|
export const PROJECT_COMPONENT_PROTOCOL_VERSION = 1;
|
|
12
13
|
export const PROJECT_COMPONENT_DEADLINE_MS = 5_000;
|
|
@@ -16,7 +17,7 @@ const REF_KINDS = new Set(['prefab', 'clip', 'action', 'stat']);
|
|
|
16
17
|
const FALLBACK_PACKAGE_DIRECTORIES = [
|
|
17
18
|
['@waica/engine', 'engine'],
|
|
18
19
|
['@waica/behaviors', 'behaviors'],
|
|
19
|
-
[
|
|
20
|
+
...KNOWN_ARCHETYPES.map(({ packageName, directory }) => [packageName, directory]),
|
|
20
21
|
];
|
|
21
22
|
const execFileAsync = promisify(execFile);
|
|
22
23
|
let sourceFallbackEntries;
|
|
@@ -5,10 +5,15 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
5
5
|
const PROTOCOL_VERSION = 1;
|
|
6
6
|
const { createRequire } = nodeModule;
|
|
7
7
|
const hostRequire = createRequire(import.meta.url);
|
|
8
|
+
// This file runs as a standalone forked child (see runnerFromModule in the
|
|
9
|
+
// loader) and cannot import siblings, so it mirrors known-archetypes.ts by
|
|
10
|
+
// hand: one entry per KNOWN_ARCHETYPES row, plus engine and behaviors.
|
|
8
11
|
const FALLBACK_PACKAGES = new Set([
|
|
9
12
|
'@waica/engine',
|
|
10
13
|
'@waica/behaviors',
|
|
11
14
|
'@waica/archetype-platformer',
|
|
15
|
+
'@waica/archetype-topdown',
|
|
16
|
+
'@waica/archetype-isometric',
|
|
12
17
|
]);
|
|
13
18
|
const FALLBACK_SPECIFIERS = new Set([...FALLBACK_PACKAGES, 'three']);
|
|
14
19
|
const REF_KINDS = new Set(['prefab', 'clip', 'action', 'stat']);
|
package/dist/mcp/server.js
CHANGED
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
5
5
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
6
6
|
import { createProject } from './create-project.js';
|
|
7
|
+
import { DEFAULT_ARCHETYPE_ID, knownArchetype, knownArchetypeIds, } from './known-archetypes.js';
|
|
7
8
|
import { describeArchetype, listComponents, projectSummary, } from './introspection.js';
|
|
8
9
|
import { ABSOLUTE_PATH_MESSAGE, WaicaToolError, assertAbsoluteProjectPath, requireWaicaProject, } from './project-path.js';
|
|
9
10
|
import { scaffoldComponent, scaffoldPrefab, scaffoldRole, scaffoldState, scaffoldUi, } from './scaffolds.js';
|
|
@@ -34,6 +35,12 @@ export const TOOLS = [
|
|
|
34
35
|
default: 'demo',
|
|
35
36
|
description: 'demo includes archetype content; blank creates only the chassis.',
|
|
36
37
|
},
|
|
38
|
+
archetype: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
enum: knownArchetypeIds(),
|
|
41
|
+
default: DEFAULT_ARCHETYPE_ID,
|
|
42
|
+
description: 'Archetype the project is created for.',
|
|
43
|
+
},
|
|
37
44
|
}),
|
|
38
45
|
annotations: { destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
39
46
|
},
|
|
@@ -352,7 +359,17 @@ async function execute(name, args, runtime, signal, componentLoader) {
|
|
|
352
359
|
projectPath,
|
|
353
360
|
});
|
|
354
361
|
}
|
|
355
|
-
|
|
362
|
+
const archetype = args.archetype === undefined
|
|
363
|
+
? DEFAULT_ARCHETYPE_ID
|
|
364
|
+
: requiredString(args, 'archetype', projectPath);
|
|
365
|
+
if (!knownArchetype(archetype)) {
|
|
366
|
+
throw new WaicaToolError({
|
|
367
|
+
code: 'unknown-archetype',
|
|
368
|
+
message: `Unknown archetype "${archetype}"; available: ${knownArchetypeIds().join(', ')}.`,
|
|
369
|
+
projectPath,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
return { ...(await createProject(projectPath, start, archetype)) };
|
|
356
373
|
}
|
|
357
374
|
case 'list_components':
|
|
358
375
|
return listComponents(projectPath);
|
|
@@ -2,11 +2,12 @@ import {
|
|
|
2
2
|
collectModuleComponents,
|
|
3
3
|
Game,
|
|
4
4
|
installArchetype,
|
|
5
|
+
installDirectionalAnimation,
|
|
5
6
|
loadScene,
|
|
6
7
|
mergeRegistryComponents,
|
|
7
8
|
type PrefabJson,
|
|
8
9
|
} from '@waica/engine'
|
|
9
|
-
import {
|
|
10
|
+
import { ARCHETYPE } from '__ARCHETYPE_PACKAGE__'
|
|
10
11
|
import scene from './scenes/main.scene.json'
|
|
11
12
|
import controls from './controls.json'
|
|
12
13
|
import stats from './stats.json'
|
|
@@ -60,14 +61,14 @@ for (const [path, url] of Object.entries(artFiles)) {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
const registryBase = {
|
|
63
|
-
...
|
|
64
|
+
...ARCHETYPE.registry,
|
|
64
65
|
// Keep these two: the spread above carries the archetype's own catalogs,
|
|
65
66
|
// and these replace them with yours. Drop one and the game starts
|
|
66
67
|
// resolving pieces that are nowhere in src/.
|
|
67
68
|
prefabs,
|
|
68
69
|
ui,
|
|
69
70
|
resolveAsset: (uri: string) =>
|
|
70
|
-
artUrls[uri] ??
|
|
71
|
+
artUrls[uri] ?? ARCHETYPE.registry.resolveAsset?.(uri) ?? uri,
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
const canvas = document.querySelector<HTMLCanvasElement>('#game')
|
|
@@ -82,7 +83,10 @@ if (canvas.dataset.waica) {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
async function main(canvas: HTMLCanvasElement): Promise<void> {
|
|
85
|
-
installArchetype(
|
|
86
|
+
installArchetype(ARCHETYPE.bundle)
|
|
87
|
+
// installArchetype resets the directional registry, so the contract goes
|
|
88
|
+
// right after it (archetypes without one install null — a no-op).
|
|
89
|
+
installDirectionalAnimation(ARCHETYPE.animation ?? null)
|
|
86
90
|
const projectModules = await Promise.all(Object.values(projectCode).map((load) => load()))
|
|
87
91
|
const registry = mergeRegistryComponents(
|
|
88
92
|
registryBase,
|