@waica/archetype-topdown 0.6.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ayrton Marini and Waica contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,28 @@
1
+ # Art attribution
2
+
3
+ All sprites in this directory are composed from Creative Commons Zero (CC0)
4
+ pixel art by Kenney (www.kenney.nl). CC0 requires no attribution — this file
5
+ credits the source anyway and records exactly how each sheet was assembled,
6
+ so the composition is reproducible.
7
+
8
+ Sources (both CC0, downloaded 2026-08-10):
9
+
10
+ - **RPG Urban Pack 1.0** — https://kenney.nl/assets/rpg-urban-pack
11
+ (`Tilemap/tilemap_packed.png`, a 27×18 grid of 16×16 tiles)
12
+ - **Tiny Dungeon 1.0** — https://kenney.nl/assets/tiny-dungeon
13
+ (`Tilemap/tilemap_packed.png`, a 12×11 grid of 16×16 tiles)
14
+
15
+ Tile coordinates below are `(column, row)`, zero-based, into each pack's
16
+ `tilemap_packed.png`.
17
+
18
+ | File | Size | Source | Composition |
19
+ |---|---|---|---|
20
+ | `waica-hero.png` | 50×50 | RPG Urban | Character 1 (rows 0–2). 3×3 grid of 16×16 cells with 1px transparent gutters between them (frame-edge sampling never reaches a neighbour): rows = facing s/n/e (source columns 24/25/26), columns = idle / walk A / walk B (source rows base+0/1/2). West is mirrored from east at runtime. |
21
+ | `waica-npc.png` | 50×50 | RPG Urban | Character 4, the farmer (rows 9–11). Same 3×3 gutter layout as the hero. |
22
+ | `waica-blob.png` | 16×16 | Tiny Dungeon | (0, 9) — the green blob. |
23
+ | `waica-potion.png` | 16×16 | Tiny Dungeon | (7, 9) — the red potion. |
24
+ | `waica-grass.png` | 16×16 | RPG Urban | (1, 1) — plain grass. |
25
+ | `waica-water.png` | 16×16 | RPG Urban | (9, 7) — pond interior. |
26
+ | `waica-path.png` | 16×16 | Tiny Dungeon | (1, 4) — speckled sand. |
27
+ | `waica-fence.png` | 16×16 | Tiny Dungeon | (5, 6) — fence run. |
28
+ | `waica-tree.png` | 16×32 | RPG Urban | (16, 8)–(16, 9) — tall green tree, two tiles stacked. |
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,7 @@
1
+ import type { DirectionalAnimation } from '@waica/engine';
2
+ /**
3
+ * The archetype's directional animation contract: four facings with west
4
+ * mirrored from east art (the stock sheets only draw s/n/e), clips named
5
+ * `<state>-<dir>`, and walk falling back to idle when a sheet lacks it.
6
+ */
7
+ export declare const TOPDOWN_ANIMATION: DirectionalAnimation;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The archetype's directional animation contract: four facings with west
3
+ * mirrored from east art (the stock sheets only draw s/n/e), clips named
4
+ * `<state>-<dir>`, and walk falling back to idle when a sheet lacks it.
5
+ */
6
+ export const TOPDOWN_ANIMATION = {
7
+ directions: ['n', 's', 'e', 'w'],
8
+ fallbacks: { w: { dir: 'e', flip: true } },
9
+ contract: { required: ['idle'], fallbacks: { walk: 'idle' } },
10
+ };
package/dist/art.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { ArchetypeArt } from '@waica/engine';
2
+ /**
3
+ * The archetype's stock art, as pure data (no asset imports: node tooling
4
+ * like scripts/sync-scene.mjs imports this module directly).
5
+ *
6
+ * Demo projects materialize each sheet as a real file at `src/art/<file>`
7
+ * and reference that path; `uri` is the registry key that in-package
8
+ * prefab defaults use.
9
+ */
10
+ export declare const TOPDOWN_ART: ArchetypeArt[];
package/dist/art.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The archetype's stock art, as pure data (no asset imports: node tooling
3
+ * like scripts/sync-scene.mjs imports this module directly).
4
+ *
5
+ * Demo projects materialize each sheet as a real file at `src/art/<file>`
6
+ * and reference that path; `uri` is the registry key that in-package
7
+ * prefab defaults use.
8
+ */
9
+ export const TOPDOWN_ART = [
10
+ { file: 'waica-hero.png', uri: 'waica:hero' },
11
+ { file: 'waica-npc.png', uri: 'waica:npc' },
12
+ { file: 'waica-blob.png', uri: 'waica:blob' },
13
+ { file: 'waica-potion.png', uri: 'waica:potion' },
14
+ { file: 'waica-grass.png', uri: 'waica:grass' },
15
+ { file: 'waica-path.png', uri: 'waica:path' },
16
+ { file: 'waica-water.png', uri: 'waica:water' },
17
+ { file: 'waica-fence.png', uri: 'waica:fence' },
18
+ { file: 'waica-tree.png', uri: 'waica:tree' },
19
+ ];
@@ -0,0 +1,3 @@
1
+ import type { ArchetypeBundle } from '@waica/engine';
2
+ /** Role and state-code baseline installed for a top-down project. */
3
+ export declare const TOPDOWN_BUNDLE: ArchetypeBundle;
package/dist/bundle.js ADDED
@@ -0,0 +1,10 @@
1
+ import { CHASER_ROLE, NPC_ROLE, PATROLLER_ROLE, TOPDOWN_PLAYER_ROLE, } from '@waica/behaviors';
2
+ /** Role and state-code baseline installed for a top-down project. */
3
+ export const TOPDOWN_BUNDLE = {
4
+ roles: {
5
+ player: TOPDOWN_PLAYER_ROLE,
6
+ patroller: PATROLLER_ROLE,
7
+ chaser: CHASER_ROLE,
8
+ npc: NPC_ROLE,
9
+ },
10
+ };
@@ -0,0 +1,5 @@
1
+ import type { InputBindings } from '@waica/engine';
2
+ /** Top-down actions: four directions plus interact, arrows and WASD. */
3
+ export declare const TOPDOWN_BINDINGS: Readonly<InputBindings>;
4
+ /** Friendly labels shown by the editor's controls panel. */
5
+ export declare const TOPDOWN_ACTION_LABELS: Readonly<Record<string, string>>;
@@ -0,0 +1,16 @@
1
+ /** Top-down actions: four directions plus interact, arrows and WASD. */
2
+ export const TOPDOWN_BINDINGS = {
3
+ up: ['ArrowUp', 'KeyW'],
4
+ down: ['ArrowDown', 'KeyS'],
5
+ left: ['ArrowLeft', 'KeyA'],
6
+ right: ['ArrowRight', 'KeyD'],
7
+ interact: ['KeyE', 'Space'],
8
+ };
9
+ /** Friendly labels shown by the editor's controls panel. */
10
+ export const TOPDOWN_ACTION_LABELS = {
11
+ up: 'Move up',
12
+ down: 'Move down',
13
+ left: 'Move left',
14
+ right: 'Move right',
15
+ interact: 'Interact',
16
+ };
@@ -0,0 +1,25 @@
1
+ export { TOPDOWN_ANIMATION } from './animation.js';
2
+ export { TOPDOWN_BUNDLE } from './bundle.js';
3
+ export { TOPDOWN_ACTION_LABELS, TOPDOWN_BINDINGS } from './controls.js';
4
+ export { TOPDOWN_SCENE, TOPDOWN_BLANK_SCENE, HERO_SPRITE } from './scene-default.js';
5
+ export { TOPDOWN_PREFABS } from './prefabs.js';
6
+ export { TOPDOWN_UI } from './ui.js';
7
+ export { TOPDOWN_ART } from './art.js';
8
+ export { TOPDOWN_REGISTRY, TOPDOWN_PALETTE, TOPDOWN_ART_URLS } from './registry.js';
9
+ /** Browser-complete manifest exported by the package root. */
10
+ export declare const ARCHETYPE: {
11
+ id: string;
12
+ label: string;
13
+ scene: import("@waica/engine").SceneJson;
14
+ blankScene: import("@waica/engine").SceneJson;
15
+ palette: import("@waica/engine").EntityTemplate[];
16
+ prefabs: Record<string, import("@waica/engine").PrefabJson>;
17
+ art: import("@waica/engine").ArchetypeArt[];
18
+ entityIcons: Readonly<Record<string, string>>;
19
+ bindings: Readonly<import("@waica/engine").InputBindings>;
20
+ actionLabels: Readonly<Record<string, string>>;
21
+ bundle: import("@waica/engine").ArchetypeBundle;
22
+ animation: import("@waica/engine").DirectionalAnimation<string>;
23
+ registry: import("@waica/engine").SceneRegistry;
24
+ artUrls: Record<string, string>;
25
+ };
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ import { ARCHETYPE as DATA_ARCHETYPE } from './manifest.js';
2
+ import { TOPDOWN_REGISTRY, TOPDOWN_ART_URLS } from './registry.js';
3
+ export { TOPDOWN_ANIMATION } from './animation.js';
4
+ export { TOPDOWN_BUNDLE } from './bundle.js';
5
+ export { TOPDOWN_ACTION_LABELS, TOPDOWN_BINDINGS } from './controls.js';
6
+ export { TOPDOWN_SCENE, TOPDOWN_BLANK_SCENE, HERO_SPRITE } from './scene-default.js';
7
+ export { TOPDOWN_PREFABS } from './prefabs.js';
8
+ export { TOPDOWN_UI } from './ui.js';
9
+ export { TOPDOWN_ART } from './art.js';
10
+ export { TOPDOWN_REGISTRY, TOPDOWN_PALETTE, TOPDOWN_ART_URLS } from './registry.js';
11
+ /** Browser-complete manifest exported by the package root. */
12
+ export const ARCHETYPE = {
13
+ ...DATA_ARCHETYPE,
14
+ registry: TOPDOWN_REGISTRY,
15
+ artUrls: TOPDOWN_ART_URLS,
16
+ };
@@ -0,0 +1,16 @@
1
+ /** Asset-import-free manifest for Node tooling. */
2
+ export declare const ARCHETYPE: {
3
+ id: string;
4
+ label: string;
5
+ scene: import("@waica/engine").SceneJson;
6
+ blankScene: import("@waica/engine").SceneJson;
7
+ registry: import("@waica/engine").SceneRegistry;
8
+ palette: import("@waica/engine").EntityTemplate[];
9
+ prefabs: Record<string, import("@waica/engine").PrefabJson>;
10
+ art: import("@waica/engine").ArchetypeArt[];
11
+ entityIcons: Readonly<Record<string, string>>;
12
+ bindings: Readonly<import("@waica/engine").InputBindings>;
13
+ actionLabels: Readonly<Record<string, string>>;
14
+ bundle: import("@waica/engine").ArchetypeBundle;
15
+ animation: import("@waica/engine").DirectionalAnimation<string>;
16
+ };
@@ -0,0 +1,29 @@
1
+ import { TOPDOWN_ANIMATION } from './animation.js';
2
+ import { TOPDOWN_ART } from './art.js';
3
+ import { TOPDOWN_BUNDLE } from './bundle.js';
4
+ import { TOPDOWN_ACTION_LABELS, TOPDOWN_BINDINGS } from './controls.js';
5
+ import { TOPDOWN_PREFABS } from './prefabs.js';
6
+ import { TOPDOWN_PALETTE, TOPDOWN_REGISTRY_DATA } from './registry-data.js';
7
+ import { TOPDOWN_BLANK_SCENE, TOPDOWN_SCENE } from './scene-default.js';
8
+ const TOPDOWN_ENTITY_ICONS = {
9
+ TopDownMotor: '🧒',
10
+ Interactable: '💬',
11
+ Collectible: '🧪',
12
+ Hazard: '👾',
13
+ };
14
+ /** Asset-import-free manifest for Node tooling. */
15
+ export const ARCHETYPE = {
16
+ id: 'topdown',
17
+ label: 'Top-down',
18
+ scene: TOPDOWN_SCENE,
19
+ blankScene: TOPDOWN_BLANK_SCENE,
20
+ registry: TOPDOWN_REGISTRY_DATA,
21
+ palette: TOPDOWN_PALETTE,
22
+ prefabs: TOPDOWN_PREFABS,
23
+ art: TOPDOWN_ART,
24
+ entityIcons: TOPDOWN_ENTITY_ICONS,
25
+ bindings: TOPDOWN_BINDINGS,
26
+ actionLabels: TOPDOWN_ACTION_LABELS,
27
+ bundle: TOPDOWN_BUNDLE,
28
+ animation: TOPDOWN_ANIMATION,
29
+ };
@@ -0,0 +1,11 @@
1
+ import type { PrefabJson } from '@waica/engine';
2
+ /**
3
+ * The archetype's reusable entity templates, keyed by ref ('tiles/tree').
4
+ * Scenes reference these and override per-entity props; the palette derives
5
+ * its pieces from this catalog.
6
+ *
7
+ * Ground tiles draw on negative layers so the y-sort pass never weaves
8
+ * them between characters: meadow (-2), then decor tiles (-1), then
9
+ * everything that occludes at 0.
10
+ */
11
+ export declare const TOPDOWN_PREFABS: Record<string, PrefabJson>;
@@ -0,0 +1,178 @@
1
+ import { NPC_STATE_GRAPH, TOPDOWN_PLAYER_STATE_GRAPH } from '@waica/behaviors';
2
+ import { HERO_SPRITE } from './scene-default.js';
3
+ /**
4
+ * The archetype's reusable entity templates, keyed by ref ('tiles/tree').
5
+ * Scenes reference these and override per-entity props; the palette derives
6
+ * its pieces from this catalog.
7
+ *
8
+ * Ground tiles draw on negative layers so the y-sort pass never weaves
9
+ * them between characters: meadow (-2), then decor tiles (-1), then
10
+ * everything that occludes at 0.
11
+ */
12
+ export const TOPDOWN_PREFABS = {
13
+ 'characters/player': {
14
+ waicaPrefab: 1,
15
+ type: 'character',
16
+ components: [
17
+ { type: 'AnimatedSprite', props: HERO_SPRITE },
18
+ { type: 'TopDownMotor' },
19
+ {
20
+ type: 'StateMachine',
21
+ props: {
22
+ role: 'player',
23
+ initial: TOPDOWN_PLAYER_STATE_GRAPH.initial,
24
+ states: TOPDOWN_PLAYER_STATE_GRAPH.states,
25
+ },
26
+ },
27
+ { type: 'Hitbox', props: { width: 0.8, height: 0.8 } },
28
+ { type: 'Respawnable' },
29
+ { type: 'Health', props: { max: 3, invulnerability: 1 } },
30
+ ],
31
+ },
32
+ 'characters/villager': {
33
+ waicaPrefab: 1,
34
+ type: 'character',
35
+ components: [
36
+ {
37
+ type: 'AnimatedSprite',
38
+ props: {
39
+ texture: 'waica:npc',
40
+ cols: 3,
41
+ rows: 3,
42
+ spacingX: 1,
43
+ spacingY: 1,
44
+ width: 1.2,
45
+ height: 1.2,
46
+ clips: {
47
+ // The plain 'idle' is what a code-free NPC plays (it has no
48
+ // facing provider); the directional clips are ready for the
49
+ // day a project gives it a motor.
50
+ idle: { frames: [0], fps: 2 },
51
+ 'idle-s': { frames: [0], fps: 2 },
52
+ 'idle-n': { frames: [3], fps: 2 },
53
+ 'idle-e': { frames: [6], fps: 2 },
54
+ 'walk-s': { frames: [1, 0, 2, 0], fps: 8 },
55
+ 'walk-n': { frames: [4, 3, 5, 3], fps: 8 },
56
+ 'walk-e': { frames: [7, 6, 8, 6], fps: 8 },
57
+ },
58
+ initialClip: 'idle',
59
+ },
60
+ },
61
+ {
62
+ type: 'StateMachine',
63
+ props: {
64
+ role: 'npc',
65
+ initial: NPC_STATE_GRAPH.initial,
66
+ states: NPC_STATE_GRAPH.states,
67
+ },
68
+ },
69
+ { type: 'Interactable', props: { line: 'Hello, traveler!', radius: 1.5 } },
70
+ // Villagers block the way like any solid prop.
71
+ { type: 'Solid', props: { width: 0.8, height: 0.5 } },
72
+ ],
73
+ },
74
+ 'characters/blob': {
75
+ waicaPrefab: 1,
76
+ type: 'character',
77
+ components: [
78
+ {
79
+ type: 'AnimatedSprite',
80
+ props: {
81
+ texture: 'waica:blob',
82
+ cols: 1,
83
+ rows: 1,
84
+ width: 1,
85
+ height: 1,
86
+ clips: { idle: { frames: [0], fps: 2 } },
87
+ initialClip: 'idle',
88
+ },
89
+ },
90
+ { type: 'Patrol', props: { distance: 2, speed: 2 } },
91
+ {
92
+ type: 'StateMachine',
93
+ props: {
94
+ role: 'patroller',
95
+ initial: 'walk',
96
+ states: { walk: { clip: 'idle' } },
97
+ },
98
+ },
99
+ { type: 'Hitbox', props: { width: 0.8, height: 0.7 } },
100
+ // No stomping without gravity: any contact hurts.
101
+ { type: 'Hazard', props: { stompable: false, contactDamage: 1 } },
102
+ ],
103
+ },
104
+ 'objects/potion': {
105
+ waicaPrefab: 1,
106
+ type: 'object',
107
+ components: [
108
+ {
109
+ type: 'AnimatedSprite',
110
+ props: {
111
+ texture: 'waica:potion',
112
+ cols: 1,
113
+ rows: 1,
114
+ width: 0.7,
115
+ height: 0.7,
116
+ clips: { idle: { frames: [0], fps: 2 } },
117
+ initialClip: 'idle',
118
+ },
119
+ },
120
+ { type: 'Hitbox', props: { width: 0.5, height: 0.5 } },
121
+ { type: 'Collectible', props: { value: 1 } },
122
+ ],
123
+ },
124
+ 'tiles/meadow': {
125
+ waicaPrefab: 1,
126
+ type: 'tile',
127
+ components: [
128
+ // The ground plane: a flat field the size you stretch it to, in the
129
+ // grass sheet's dominant green so textured tiles blend on top.
130
+ { type: 'Sprite', props: { width: 1, height: 1, color: 0x38cbab, layer: -2 } },
131
+ ],
132
+ },
133
+ 'tiles/grass': {
134
+ waicaPrefab: 1,
135
+ type: 'tile',
136
+ components: [
137
+ { type: 'Sprite', props: { texture: 'waica:grass', width: 1, height: 1, layer: -1 } },
138
+ ],
139
+ },
140
+ 'tiles/path': {
141
+ waicaPrefab: 1,
142
+ type: 'tile',
143
+ components: [
144
+ { type: 'Sprite', props: { texture: 'waica:path', width: 1, height: 1, layer: -1 } },
145
+ ],
146
+ },
147
+ 'tiles/water': {
148
+ waicaPrefab: 1,
149
+ type: 'tile',
150
+ components: [
151
+ { type: 'Sprite', props: { texture: 'waica:water', width: 1, height: 1, layer: -1 } },
152
+ { type: 'Solid', props: { width: 1, height: 1 } },
153
+ ],
154
+ },
155
+ 'tiles/fence': {
156
+ waicaPrefab: 1,
157
+ type: 'tile',
158
+ components: [
159
+ { type: 'Sprite', props: { texture: 'waica:fence', width: 1, height: 1 } },
160
+ // Shorter than the art so the player can overlap the top rail a bit,
161
+ // the way top-down fences read.
162
+ { type: 'Solid', props: { width: 1, height: 0.6 } },
163
+ ],
164
+ },
165
+ 'tiles/tree': {
166
+ waicaPrefab: 1,
167
+ type: 'tile',
168
+ components: [
169
+ // Anchored at the trunk: the 1×2 canopy is drawn upward so the
170
+ // entity Y — the y-sort key — is where the tree meets the ground.
171
+ {
172
+ type: 'Sprite',
173
+ props: { texture: 'waica:tree', width: 1, height: 2, offsetY: 0.5 },
174
+ },
175
+ { type: 'Solid', props: { width: 0.8, height: 0.6 } },
176
+ ],
177
+ },
178
+ };
@@ -0,0 +1,9 @@
1
+ import { type EntityTemplate, type SceneRegistry } from '@waica/engine';
2
+ /**
3
+ * Asset-import-free registry. Browser URL resolution is layered on
4
+ * separately. No DynamicBody or OutOfBounds: gravity physics and kill
5
+ * heights have no meaning seen from above.
6
+ */
7
+ export declare const TOPDOWN_REGISTRY_DATA: SceneRegistry;
8
+ /** The editor palette: pieces you can drag into the viewport, one per prefab. */
9
+ export declare const TOPDOWN_PALETTE: EntityTemplate[];
@@ -0,0 +1,54 @@
1
+ import { AnimatedSprite, Hitbox, Solid, Sprite, StateMachine, } from '@waica/engine';
2
+ import { Chaser, Collectible, Hazard, Health, Interactable, Lifetime, Patrol, Respawnable, TopDownMotor, } from '@waica/behaviors';
3
+ import { TOPDOWN_ART } from './art.js';
4
+ import { TOPDOWN_PREFABS } from './prefabs.js';
5
+ import { TOPDOWN_UI } from './ui.js';
6
+ const PACKAGE_ASSETS = Object.fromEntries(TOPDOWN_ART.map((art) => [art.uri, `assets/${art.file}`]));
7
+ /**
8
+ * Asset-import-free registry. Browser URL resolution is layered on
9
+ * separately. No DynamicBody or OutOfBounds: gravity physics and kill
10
+ * heights have no meaning seen from above.
11
+ */
12
+ export const TOPDOWN_REGISTRY_DATA = {
13
+ components: {
14
+ Sprite,
15
+ AnimatedSprite,
16
+ Solid,
17
+ Hitbox,
18
+ StateMachine,
19
+ TopDownMotor,
20
+ Interactable,
21
+ Collectible,
22
+ Patrol,
23
+ Chaser,
24
+ Hazard,
25
+ Health,
26
+ Respawnable,
27
+ Lifetime,
28
+ },
29
+ resolveAsset: (uri) => PACKAGE_ASSETS[uri] ?? uri,
30
+ prefabs: TOPDOWN_PREFABS,
31
+ ui: TOPDOWN_UI,
32
+ };
33
+ const PALETTE_ICONS = {
34
+ player: '🧒',
35
+ villager: '🧑‍🌾',
36
+ blob: '👾',
37
+ potion: '🧪',
38
+ meadow: '▩',
39
+ grass: '🌿',
40
+ path: '🟫',
41
+ water: '🌊',
42
+ fence: '🚧',
43
+ tree: '🌳',
44
+ };
45
+ /** The editor palette: pieces you can drag into the viewport, one per prefab. */
46
+ export const TOPDOWN_PALETTE = Object.entries(TOPDOWN_PREFABS).map(([key, prefab]) => {
47
+ const base = key.slice(key.indexOf('/') + 1);
48
+ return {
49
+ label: base,
50
+ icon: PALETTE_ICONS[base] ?? '▣',
51
+ category: prefab.type,
52
+ make: () => ({ name: base.charAt(0).toUpperCase() + base.slice(1), prefab: key }),
53
+ };
54
+ });
@@ -0,0 +1,6 @@
1
+ import type { SceneRegistry } from '@waica/engine';
2
+ export { TOPDOWN_PALETTE } from './registry-data.js';
3
+ /** Browser URL per stock-art file; Node resolves these to packed file URLs. */
4
+ export declare const TOPDOWN_ART_URLS: Record<string, string>;
5
+ /** Browser registry with bundled asset resolution layered over the shared registry. */
6
+ export declare const TOPDOWN_REGISTRY: SceneRegistry;
@@ -0,0 +1,21 @@
1
+ import { TOPDOWN_ART } from './art.js';
2
+ import { TOPDOWN_REGISTRY_DATA } from './registry-data.js';
3
+ export { TOPDOWN_PALETTE } from './registry-data.js';
4
+ /** Browser URL per stock-art file; Node resolves these to packed file URLs. */
5
+ export const TOPDOWN_ART_URLS = {
6
+ 'waica-hero.png': new URL('../assets/waica-hero.png', import.meta.url).href,
7
+ 'waica-npc.png': new URL('../assets/waica-npc.png', import.meta.url).href,
8
+ 'waica-blob.png': new URL('../assets/waica-blob.png', import.meta.url).href,
9
+ 'waica-potion.png': new URL('../assets/waica-potion.png', import.meta.url).href,
10
+ 'waica-grass.png': new URL('../assets/waica-grass.png', import.meta.url).href,
11
+ 'waica-path.png': new URL('../assets/waica-path.png', import.meta.url).href,
12
+ 'waica-water.png': new URL('../assets/waica-water.png', import.meta.url).href,
13
+ 'waica-fence.png': new URL('../assets/waica-fence.png', import.meta.url).href,
14
+ 'waica-tree.png': new URL('../assets/waica-tree.png', import.meta.url).href,
15
+ };
16
+ const BUILTIN_ASSETS = Object.fromEntries(TOPDOWN_ART.map((art) => [art.uri, TOPDOWN_ART_URLS[art.file] ?? art.uri]));
17
+ /** Browser registry with bundled asset resolution layered over the shared registry. */
18
+ export const TOPDOWN_REGISTRY = {
19
+ ...TOPDOWN_REGISTRY_DATA,
20
+ resolveAsset: (uri) => BUILTIN_ASSETS[uri] ?? uri,
21
+ };
@@ -0,0 +1,57 @@
1
+ import type { SceneJson } from '@waica/engine';
2
+ /**
3
+ * Clips matching the top-down animation contract, applied to the hero
4
+ * sheet: 3×3 grid, one row per facing (s/n/e), columns idle/walk-A/walk-B.
5
+ * West clips come from mirroring east at runtime (the contract's fallback).
6
+ * Cells sit 1px apart — transparent gutters keep frame-edge sampling
7
+ * (MSAA extrapolation, linear filtering) from bleeding a neighbour in.
8
+ */
9
+ export declare const HERO_SPRITE: {
10
+ texture: string;
11
+ cols: number;
12
+ rows: number;
13
+ spacingX: number;
14
+ spacingY: number;
15
+ width: number;
16
+ height: number;
17
+ clips: {
18
+ 'idle-s': {
19
+ frames: number[];
20
+ fps: number;
21
+ };
22
+ 'idle-n': {
23
+ frames: number[];
24
+ fps: number;
25
+ };
26
+ 'idle-e': {
27
+ frames: number[];
28
+ fps: number;
29
+ };
30
+ 'walk-s': {
31
+ frames: number[];
32
+ fps: number;
33
+ };
34
+ 'walk-n': {
35
+ frames: number[];
36
+ fps: number;
37
+ };
38
+ 'walk-e': {
39
+ frames: number[];
40
+ fps: number;
41
+ };
42
+ };
43
+ initialClip: string;
44
+ };
45
+ /**
46
+ * The top-down archetype's starting scene: a village clearing that
47
+ * exercises everything the genre adds — y-sorted occlusion (walk behind
48
+ * and in front of the trees), the two-axis camera (vertical lookahead),
49
+ * an NPC to interact with, a patrolling enemy and potions to collect.
50
+ */
51
+ export declare const TOPDOWN_SCENE: SceneJson;
52
+ /**
53
+ * The blank-start variant: same framing and y-sorted rendering, zero
54
+ * entities. No `follow` because there is no Player yet — the editor's
55
+ * palette places the first one.
56
+ */
57
+ export declare const TOPDOWN_BLANK_SCENE: SceneJson;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Clips matching the top-down animation contract, applied to the hero
3
+ * sheet: 3×3 grid, one row per facing (s/n/e), columns idle/walk-A/walk-B.
4
+ * West clips come from mirroring east at runtime (the contract's fallback).
5
+ * Cells sit 1px apart — transparent gutters keep frame-edge sampling
6
+ * (MSAA extrapolation, linear filtering) from bleeding a neighbour in.
7
+ */
8
+ export const HERO_SPRITE = {
9
+ texture: 'waica:hero',
10
+ cols: 3,
11
+ rows: 3,
12
+ spacingX: 1,
13
+ spacingY: 1,
14
+ width: 1.2,
15
+ height: 1.2,
16
+ clips: {
17
+ 'idle-s': { frames: [0], fps: 2 },
18
+ 'idle-n': { frames: [3], fps: 2 },
19
+ 'idle-e': { frames: [6], fps: 2 },
20
+ 'walk-s': { frames: [1, 0, 2, 0], fps: 8 },
21
+ 'walk-n': { frames: [4, 3, 5, 3], fps: 8 },
22
+ 'walk-e': { frames: [7, 6, 8, 6], fps: 8 },
23
+ },
24
+ initialClip: 'idle-s',
25
+ };
26
+ /**
27
+ * The top-down archetype's starting scene: a village clearing that
28
+ * exercises everything the genre adds — y-sorted occlusion (walk behind
29
+ * and in front of the trees), the two-axis camera (vertical lookahead),
30
+ * an NPC to interact with, a patrolling enemy and potions to collect.
31
+ */
32
+ export const TOPDOWN_SCENE = {
33
+ waicaScene: 3,
34
+ render: { sort: 'y' },
35
+ camera: {
36
+ position: [0, 0],
37
+ zoom: 12,
38
+ follow: 'Player',
39
+ lookaheadY: 1,
40
+ // Clamped to the meadow: 22×14 world units centered on the origin.
41
+ limits: { minX: -11, maxX: 11, minY: -7, maxY: 7 },
42
+ },
43
+ entities: [
44
+ { name: 'Player', prefab: 'characters/player', position: [0, -2] },
45
+ {
46
+ name: 'Meadow',
47
+ prefab: 'tiles/meadow',
48
+ position: [0, 0],
49
+ overrides: { Sprite: { width: 22, height: 14 } },
50
+ },
51
+ { name: 'Path-1', prefab: 'tiles/path', position: [-2, 0] },
52
+ { name: 'Path-2', prefab: 'tiles/path', position: [-1, 0] },
53
+ { name: 'Path-3', prefab: 'tiles/path', position: [0, 0] },
54
+ { name: 'Path-4', prefab: 'tiles/path', position: [1, 0] },
55
+ { name: 'Path-5', prefab: 'tiles/path', position: [2, 0] },
56
+ { name: 'Path-6', prefab: 'tiles/path', position: [0, -1] },
57
+ { name: 'Path-7', prefab: 'tiles/path', position: [0, 1] },
58
+ { name: 'Grass-1', prefab: 'tiles/grass', position: [-4, 1] },
59
+ { name: 'Grass-2', prefab: 'tiles/grass', position: [1, 2] },
60
+ { name: 'Grass-3', prefab: 'tiles/grass', position: [5, -2] },
61
+ { name: 'Grass-4', prefab: 'tiles/grass', position: [-8, 3] },
62
+ { name: 'Grass-5', prefab: 'tiles/grass', position: [8, -3] },
63
+ { name: 'Grass-6', prefab: 'tiles/grass', position: [-1, -4] },
64
+ { name: 'Water-1', prefab: 'tiles/water', position: [-7, -2] },
65
+ { name: 'Water-2', prefab: 'tiles/water', position: [-6, -2] },
66
+ { name: 'Water-3', prefab: 'tiles/water', position: [-5, -2] },
67
+ { name: 'Water-4', prefab: 'tiles/water', position: [-7, -3] },
68
+ { name: 'Water-5', prefab: 'tiles/water', position: [-6, -3] },
69
+ { name: 'Water-6', prefab: 'tiles/water', position: [-5, -3] },
70
+ // Trees sit in open ground so walking above/below them flips the
71
+ // draw order — the y-sort showcase.
72
+ { name: 'Tree-1', prefab: 'tiles/tree', position: [-6, 3] },
73
+ { name: 'Tree-2', prefab: 'tiles/tree', position: [-2, 3.5] },
74
+ { name: 'Tree-3', prefab: 'tiles/tree', position: [2, 4] },
75
+ { name: 'Tree-4', prefab: 'tiles/tree', position: [6, 3] },
76
+ { name: 'Tree-5', prefab: 'tiles/tree', position: [4, -1] },
77
+ { name: 'Tree-6', prefab: 'tiles/tree', position: [-4, -3] },
78
+ { name: 'Fence-1', prefab: 'tiles/fence', position: [5, -4] },
79
+ { name: 'Fence-2', prefab: 'tiles/fence', position: [6, -4] },
80
+ { name: 'Fence-3', prefab: 'tiles/fence', position: [7, -4] },
81
+ {
82
+ name: 'Villager',
83
+ prefab: 'characters/villager',
84
+ position: [3, 1],
85
+ overrides: { Interactable: { line: 'Lovely day in Waica Village!' } },
86
+ },
87
+ {
88
+ name: 'Blob',
89
+ prefab: 'characters/blob',
90
+ position: [-2, -4],
91
+ overrides: { Patrol: { distance: 2.5, speed: 1.5 } },
92
+ },
93
+ { name: 'Potion-1', prefab: 'objects/potion', position: [-3, 2] },
94
+ { name: 'Potion-2', prefab: 'objects/potion', position: [7, 1] },
95
+ { name: 'Potion-3', prefab: 'objects/potion', position: [-6, 1] },
96
+ { name: 'Potion-4', prefab: 'objects/potion', position: [2, -4] },
97
+ ],
98
+ ui: ['potion-counter'],
99
+ };
100
+ /**
101
+ * The blank-start variant: same framing and y-sorted rendering, zero
102
+ * entities. No `follow` because there is no Player yet — the editor's
103
+ * palette places the first one.
104
+ */
105
+ export const TOPDOWN_BLANK_SCENE = {
106
+ waicaScene: 3,
107
+ render: { sort: 'y' },
108
+ camera: { position: [0, 0], zoom: 12 },
109
+ entities: [],
110
+ };
package/dist/ui.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The archetype's default UI pieces: self-contained HTML fragments
3
+ * (markup + <style>, {{stat}} bindings) keyed by piece name. Projects
4
+ * override them with src/ui/<name>.html files; scenes list the pieces
5
+ * they start with in their "ui" field.
6
+ *
7
+ * TS template literals (not .html files) because this package builds
8
+ * with plain tsc — no bundler to inline raw imports.
9
+ */
10
+ export declare const TOPDOWN_UI: Record<string, string>;
package/dist/ui.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The archetype's default UI pieces: self-contained HTML fragments
3
+ * (markup + <style>, {{stat}} bindings) keyed by piece name. Projects
4
+ * override them with src/ui/<name>.html files; scenes list the pieces
5
+ * they start with in their "ui" field.
6
+ *
7
+ * TS template literals (not .html files) because this package builds
8
+ * with plain tsc — no bundler to inline raw imports.
9
+ */
10
+ export const TOPDOWN_UI = {
11
+ 'potion-counter': `<style>
12
+ .potion-counter {
13
+ position: absolute;
14
+ top: 12px;
15
+ left: 12px;
16
+ font: 600 20px system-ui, sans-serif;
17
+ color: #ff6b6b;
18
+ text-shadow: 0 1px 3px #000a;
19
+ user-select: none;
20
+ }
21
+ </style>
22
+ <div class="potion-counter">🧪 {{points}}</div>
23
+ `,
24
+ 'npc-line': `<style>
25
+ .npc-line {
26
+ position: absolute;
27
+ left: 50%;
28
+ bottom: 24px;
29
+ transform: translateX(-50%);
30
+ max-width: 70%;
31
+ padding: 10px 18px;
32
+ border-radius: 8px;
33
+ background: #1a1a2ecc;
34
+ border: 1px solid #ffffff2e;
35
+ font: 500 18px system-ui, sans-serif;
36
+ color: #f5f5f5;
37
+ text-shadow: 0 1px 2px #000a;
38
+ user-select: none;
39
+ }
40
+ </style>
41
+ <div class="npc-line">{{npcLine}}</div>
42
+ `,
43
+ };
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@waica/archetype-topdown",
3
+ "version": "0.6.1",
4
+ "description": "Waica top-down archetype — opinionated setup for Zelda-like overhead adventures",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/chichex/waica",
10
+ "directory": "packages/archetype-topdown"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "assets"
15
+ ],
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "./manifest": {
22
+ "types": "./dist/manifest.d.ts",
23
+ "default": "./dist/manifest.js"
24
+ }
25
+ },
26
+ "dependencies": {
27
+ "@waica/engine": "^0.6.1",
28
+ "@waica/behaviors": "^0.6.1"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^24.13.3"
32
+ },
33
+ "scripts": {
34
+ "build": "node ../../scripts/clean-dist.mjs && tsc -p tsconfig.build.json",
35
+ "typecheck": "tsc --noEmit"
36
+ }
37
+ }