@waica/archetype-isometric 0.8.0 → 0.9.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.
@@ -15,30 +15,51 @@ offline from originals retained outside the repository at
15
15
  `Human-Worker-Red.png`, and `Orc-Grunt.png` (each 768×256, 24×8 cells
16
16
  of 32×32).
17
17
 
18
- Each output character sheet is 4 columns × 5 rows of native 32×32 cells with
19
- one transparent pixel between cells. Output columns are source columns 0, 1,
20
- 2 and 3: idle followed by the three walk poses. Output rows and exact source
21
- cell rectangles are:
18
+ Each output character sheet is 11 columns × 5 rows of native 32×32 cells with
19
+ one transparent pixel between cells (362×164). Output rows are the five
20
+ east-facing rows of the pack; the source row order, verified on the sheets
21
+ themselves (2026-08-29), is `0 s · 1 se · 2 e · 3 ne · 4 n · 5 nw · 6 w · 7 sw`.
22
+ An earlier composition took rows 5/6/7 as `ne/e/se`, which face west, and
23
+ shipped the hero walking the wrong way; the row → facing table below is the
24
+ corrected one.
22
25
 
23
- | Output facing | Source row | Cell rectangles `(left, top, right, bottom)` |
26
+ | Output facing | Source row | Cell rectangle of output column `c` |
24
27
  |---|---:|---|
25
- | `n` | 4 | `(0,128,32,160)`, `(32,128,64,160)`, `(64,128,96,160)`, `(96,128,128,160)` |
26
- | `ne` | 5 | `(0,160,32,192)`, `(32,160,64,192)`, `(64,160,96,192)`, `(96,160,128,192)` |
27
- | `e` | 6 | `(0,192,32,224)`, `(32,192,64,224)`, `(64,192,96,224)`, `(96,192,128,224)` |
28
- | `se` | 7 | `(0,224,32,256)`, `(32,224,64,256)`, `(64,224,96,256)`, `(96,224,128,256)` |
29
- | `s` | 0 | `(0,0,32,32)`, `(32,0,64,32)`, `(64,0,96,32)`, `(96,0,128,32)` |
30
-
31
- The offline composition compared every one of those four poses against the
32
- source mirror pair. Source row 2 (`w`) is the pixel-exact horizontal mirror of
33
- row 6 (`e`), row 3 (`nw`) mirrors row 5 (`ne`), and row 1 (`sw`) mirrors row 7
34
- (`se`) in all three character files. The west rows are therefore intentionally
35
- absent from the committed sheets and resolve through runtime mirroring.
28
+ | `n` | 4 | `(32·src[c], 128, 32·src[c]+32, 160)` |
29
+ | `ne` | 3 | `(32·src[c], 96, 32·src[c]+32, 128)` |
30
+ | `e` | 2 | `(32·src[c], 64, 32·src[c]+32, 96)` |
31
+ | `se` | 1 | `(32·src[c], 32, 32·src[c]+32, 64)` |
32
+ | `s` | 0 | `(32·src[c], 0, 32·src[c]+32, 32)` |
33
+
34
+ Output columns are `0 idle · 1–3 walk · 4–6 attack · 7–8 hurt · 9–10 death`.
35
+ `src[c]` is the source column each output column is cut from; the warrior's
36
+ sword/hurt/death poses sit one column earlier than the other two sheets':
37
+
38
+ | Output file | Character | `src[0..10]` |
39
+ |---|---|---|
40
+ | `waica-iso-hero.png` | `Warrior-Blue.png` | 0, 1, 2, 3, 5, 6, 7, 19, 20, 22, 23 |
41
+ | `waica-iso-villager.png` | `Human-Worker-Red.png` | 0, 1, 2, 3, 6, 7, 8, 20, 21, 22, 23 |
42
+ | `waica-iso-orc.png` | `Orc-Grunt.png` | 0, 1, 2, 3, 6, 7, 8, 20, 21, 22, 23 |
43
+
44
+ The villager sheet carries the sword columns like the others but declares no
45
+ `attack-*` clips.
46
+
47
+ ### Mirror check
48
+
49
+ The west rows are absent from the committed sheets and resolve through
50
+ runtime mirroring (`flipX`). The composition compared every used cell of
51
+ source rows 1/2/3 against the horizontal mirror of rows 7/6/5. They are
52
+ **not pixel-exact**: idle/walk poses differ by 1–5 pixels (a hand or a
53
+ strap), and the sword poses by 30–60 pixels because the sword stays in the
54
+ same hand on both sides. Mirroring the east art is therefore a stylistic
55
+ approximation, not a lossless substitute — acceptable for the demo, and the
56
+ reason the human protocol checks the mirrored directions by eye.
36
57
 
37
58
  | Output file | Character | Output size |
38
59
  |---|---|---:|
39
- | `waica-iso-hero.png` | `Warrior-Blue.png` | 131×164 |
40
- | `waica-iso-villager.png` | `Human-Worker-Red.png` | 131×164 |
41
- | `waica-iso-orc.png` | `Orc-Grunt.png` | 131×164 |
60
+ | `waica-iso-hero.png` | `Warrior-Blue.png` | 362×164 |
61
+ | `waica-iso-villager.png` | `Human-Worker-Red.png` | 362×164 |
62
+ | `waica-iso-orc.png` | `Orc-Grunt.png` | 362×164 |
42
63
 
43
64
  ## hawkbirdtree isometric tileset
44
65
 
Binary file
Binary file
Binary file
@@ -1,3 +1,7 @@
1
1
  import type { DirectionalAnimation } from '@waica/engine';
2
- /** Five authored facings plus three horizontal mirror fallbacks. */
2
+ /**
3
+ * Five authored facings plus three horizontal mirror fallbacks; every pose
4
+ * a character can be asked for (walk, attack, hurt, death) degrades to its
5
+ * idle, still facing the right way.
6
+ */
3
7
  export declare const ISOMETRIC_ANIMATION: DirectionalAnimation;
package/dist/animation.js CHANGED
@@ -1,4 +1,8 @@
1
- /** Five authored facings plus three horizontal mirror fallbacks. */
1
+ /**
2
+ * Five authored facings plus three horizontal mirror fallbacks; every pose
3
+ * a character can be asked for (walk, attack, hurt, death) degrades to its
4
+ * idle, still facing the right way.
5
+ */
2
6
  export const ISOMETRIC_ANIMATION = {
3
7
  directions: ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'],
4
8
  fallbacks: {
@@ -6,5 +10,8 @@ export const ISOMETRIC_ANIMATION = {
6
10
  nw: { dir: 'ne', flip: true },
7
11
  sw: { dir: 'se', flip: true },
8
12
  },
9
- contract: { required: ['idle'], fallbacks: { walk: 'idle' } },
13
+ contract: {
14
+ required: ['idle'],
15
+ fallbacks: { walk: 'idle', attack: 'idle', hurt: 'idle', death: 'idle' },
16
+ },
10
17
  };
package/dist/controls.js CHANGED
@@ -4,6 +4,7 @@ export const ISOMETRIC_BINDINGS = {
4
4
  left: ['ArrowLeft', 'KeyA'],
5
5
  right: ['ArrowRight', 'KeyD'],
6
6
  interact: ['KeyE', 'Space'],
7
+ attack: ['KeyX', 'KeyJ'],
7
8
  };
8
9
  export const ISOMETRIC_ACTION_LABELS = {
9
10
  up: 'Move up',
@@ -11,4 +12,5 @@ export const ISOMETRIC_ACTION_LABELS = {
11
12
  left: 'Move left',
12
13
  right: 'Move right',
13
14
  interact: 'Interact',
15
+ attack: 'Attack',
14
16
  };
package/dist/manifest.js CHANGED
@@ -8,6 +8,7 @@ import { ISOMETRIC_BLANK_SCENE, ISOMETRIC_SCENE } from './scene-default.js';
8
8
  export const ISOMETRIC_ENTITY_ICONS = {
9
9
  IsoMotor: '🧭',
10
10
  Tilemap: '💎',
11
+ MeleeAttack: '⚔️',
11
12
  Interactable: '💬',
12
13
  Collectible: '📦',
13
14
  Hazard: '👹',
package/dist/prefabs.d.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  import type { PrefabJson } from '@waica/engine';
2
+ type ClipMap = Record<string, {
3
+ frames: number[];
4
+ fps: number;
5
+ loop?: boolean;
6
+ }>;
2
7
  export declare const ISOMETRIC_HERO_SPRITE: {
3
8
  texture: string;
4
9
  cols: number;
@@ -9,10 +14,9 @@ export declare const ISOMETRIC_HERO_SPRITE: {
9
14
  width: number;
10
15
  height: number;
11
16
  anchorY: number;
12
- clips: Record<string, {
13
- frames: number[];
14
- fps: number;
15
- }>;
17
+ offsetY: number;
18
+ clips: ClipMap;
16
19
  initialClip: string;
17
20
  };
18
21
  export declare const ISOMETRIC_PREFABS: Record<string, PrefabJson>;
22
+ export {};
package/dist/prefabs.js CHANGED
@@ -1,25 +1,35 @@
1
1
  import { ISO_PLAYER_STATE_GRAPH, NPC_STATE_GRAPH, PATROLLER_STATE_GRAPH, } from '@waica/behaviors';
2
2
  const SHIPPED_DIRECTIONS = ['n', 'ne', 'e', 'se', 's'];
3
- function directionalClips(includePlain) {
3
+ /** One row per shipped facing: idle · walk×3 · attack×3 · hurt×2 · death×2. */
4
+ const SHEET_COLUMNS = 11;
5
+ const SOUTH_ROW = SHIPPED_DIRECTIONS.indexOf('s') * SHEET_COLUMNS;
6
+ /** Transparent texels under the boots in every Puny cell, in render units (9 / 16). */
7
+ const BOOTS_OFFSET = -9 / 16;
8
+ function directionalClips({ plain = false, attacks = false }) {
4
9
  const clips = {};
5
10
  SHIPPED_DIRECTIONS.forEach((direction, row) => {
6
- const first = row * 4;
11
+ const first = row * SHEET_COLUMNS;
7
12
  clips[`idle-${direction}`] = { frames: [first], fps: 2 };
8
13
  clips[`walk-${direction}`] = {
9
14
  frames: [first + 1, first + 2, first + 3, first + 2],
10
15
  fps: 8,
11
16
  };
17
+ if (attacks) {
18
+ clips[`attack-${direction}`] = { frames: [first + 4, first + 5, first + 6], fps: 12, loop: false };
19
+ }
20
+ clips[`hurt-${direction}`] = { frames: [first + 7, first + 8], fps: 8, loop: false };
21
+ clips[`death-${direction}`] = { frames: [first + 9, first + 10], fps: 4, loop: false };
12
22
  });
13
- if (includePlain) {
14
- clips['idle'] = { frames: [16], fps: 2 };
15
- clips['walk'] = { frames: [17, 18, 19, 18], fps: 8 };
23
+ if (plain) {
24
+ clips['idle'] = { frames: [SOUTH_ROW], fps: 2 };
25
+ clips['walk'] = { frames: [SOUTH_ROW + 1, SOUTH_ROW + 2, SOUTH_ROW + 3, SOUTH_ROW + 2], fps: 8 };
16
26
  }
17
27
  return clips;
18
28
  }
19
- function characterSprite(texture, includePlain = false) {
29
+ function characterSprite(texture, options = {}) {
20
30
  return {
21
31
  texture,
22
- cols: 4,
32
+ cols: SHEET_COLUMNS,
23
33
  rows: 5,
24
34
  spacingX: 1,
25
35
  spacingY: 1,
@@ -27,11 +37,16 @@ function characterSprite(texture, includePlain = false) {
27
37
  width: 2,
28
38
  height: 2,
29
39
  anchorY: 0,
30
- clips: directionalClips(includePlain),
31
- initialClip: includePlain ? 'idle' : 'idle-s',
40
+ // The Puny bodies stand 9 texels above their cell's bottom edge; shift the
41
+ // quad down so the boots sit on the entity's position (its footprint),
42
+ // instead of floating half a unit up the screen — over a water diamond
43
+ // the body is actually stopped short of.
44
+ offsetY: BOOTS_OFFSET,
45
+ clips: directionalClips(options),
46
+ initialClip: options.plain ? 'idle' : 'idle-s',
32
47
  };
33
48
  }
34
- export const ISOMETRIC_HERO_SPRITE = characterSprite('waica:iso-hero');
49
+ export const ISOMETRIC_HERO_SPRITE = characterSprite('waica:iso-hero', { attacks: true });
35
50
  const MAP_WIDTH = 16;
36
51
  const MAP_HEIGHT = 16;
37
52
  const GRASS = 0;
@@ -68,8 +83,9 @@ export const ISOMETRIC_PREFABS = {
68
83
  },
69
84
  },
70
85
  { type: 'Hitbox', props: { width: 0.8, height: 0.8 } },
86
+ { type: 'MeleeAttack' },
71
87
  { type: 'Respawnable' },
72
- { type: 'Health', props: { max: 3, invulnerability: 1 } },
88
+ { type: 'Health', props: { max: 3, invulnerability: 1, stat: 'health' } },
73
89
  ],
74
90
  },
75
91
  'characters/villager': {
@@ -78,7 +94,7 @@ export const ISOMETRIC_PREFABS = {
78
94
  components: [
79
95
  {
80
96
  type: 'AnimatedSprite',
81
- props: characterSprite('waica:iso-villager', true),
97
+ props: characterSprite('waica:iso-villager', { plain: true }),
82
98
  },
83
99
  {
84
100
  type: 'StateMachine',
@@ -99,8 +115,10 @@ export const ISOMETRIC_PREFABS = {
99
115
  {
100
116
  type: 'AnimatedSprite',
101
117
  props: {
102
- ...characterSprite('waica:iso-orc', true),
103
- initialClip: 'walk',
118
+ ...characterSprite('waica:iso-orc', { attacks: true }),
119
+ // Patrol reports facing, so the orc resolves directional clips
120
+ // from the first frame: it starts down its rail, screen south-east.
121
+ initialClip: 'walk-se',
104
122
  },
105
123
  },
106
124
  { type: 'Patrol', props: { axis: 'horizontal', distance: 2, speed: 2 } },
@@ -114,6 +132,7 @@ export const ISOMETRIC_PREFABS = {
114
132
  },
115
133
  { type: 'Hitbox', props: { width: 0.8, height: 0.7 } },
116
134
  { type: 'Hazard', props: { stompable: false, contactDamage: 1 } },
135
+ { type: 'Health', props: { max: 2, invulnerability: 0.3 } },
117
136
  ],
118
137
  },
119
138
  'objects/crate': {
@@ -1,5 +1,5 @@
1
1
  import { AnimatedSprite, Hitbox, Solid, Sprite, StateMachine, Tilemap, } from '@waica/engine';
2
- import { Chaser, Collectible, Hazard, Health, Interactable, IsoMotor, Lifetime, Patrol, Respawnable, } from '@waica/behaviors';
2
+ import { Chaser, Collectible, Hazard, Health, Interactable, IsoMotor, Lifetime, MeleeAttack, Patrol, Respawnable, } from '@waica/behaviors';
3
3
  import { ISOMETRIC_ART } from './art.js';
4
4
  import { ISOMETRIC_PREFABS } from './prefabs.js';
5
5
  import { ISOMETRIC_UI } from './ui.js';
@@ -13,6 +13,7 @@ export const ISOMETRIC_REGISTRY_DATA = {
13
13
  Hitbox,
14
14
  StateMachine,
15
15
  IsoMotor,
16
+ MeleeAttack,
16
17
  Interactable,
17
18
  Collectible,
18
19
  Patrol,
@@ -1,8 +1,19 @@
1
+ /**
2
+ * Full-map framing plus a follow feel tuned for the diamond: a tight dead
3
+ * zone and no horizontal lookahead (the engine's step kicks in every time
4
+ * the player crosses walking speed, which lurches on short runs), a little
5
+ * vertical lead, and softer smoothing than the platformer default.
6
+ */
1
7
  function isometricCamera() {
2
8
  return {
3
9
  position: [0, -8],
4
10
  zoom: 12,
5
11
  limits: { minX: -16, maxX: 16, minY: -16, maxY: 0 },
12
+ deadzoneWidth: 0.5,
13
+ deadzoneHeight: 0.5,
14
+ lookahead: 0,
15
+ lookaheadY: 0.5,
16
+ smoothing: 4,
6
17
  };
7
18
  }
8
19
  /** A logical 16x16 meadow projected into a 2:1 diamond at render time. */
@@ -12,7 +23,6 @@ export const ISOMETRIC_SCENE = {
12
23
  camera: {
13
24
  ...isometricCamera(),
14
25
  follow: 'Player',
15
- lookaheadY: 1,
16
26
  },
17
27
  entities: [
18
28
  { name: 'Ground', prefab: 'tiles/ground', position: [0, 0] },
@@ -38,7 +48,7 @@ export const ISOMETRIC_SCENE = {
38
48
  { name: 'Rock-1', prefab: 'objects/rock', position: [5, 11] },
39
49
  { name: 'Rock-2', prefab: 'objects/rock', position: [9, 4] },
40
50
  ],
41
- ui: ['crate-counter'],
51
+ ui: ['crate-counter', 'health'],
42
52
  };
43
53
  /** Blank projects retain the diamond grid and full-map framing. */
44
54
  export const ISOMETRIC_BLANK_SCENE = {
package/dist/ui.js CHANGED
@@ -12,6 +12,19 @@ export const ISOMETRIC_UI = {
12
12
  }
13
13
  </style>
14
14
  <div class="crate-counter">📦 {{points}}</div>
15
+ `,
16
+ health: `<style>
17
+ .health {
18
+ position: absolute;
19
+ top: 12px;
20
+ right: 12px;
21
+ font: 600 20px system-ui, sans-serif;
22
+ color: #ef476f;
23
+ text-shadow: 0 1px 3px #000a;
24
+ user-select: none;
25
+ }
26
+ </style>
27
+ <div class="health">♥ {{health}}</div>
15
28
  `,
16
29
  ...INTERACTABLE_UI,
17
30
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waica/archetype-isometric",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Waica isometric archetype — logical-grid action adventures with screen-relative movement",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -24,7 +24,7 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "@waica/behaviors": "^0.8.0",
28
- "@waica/engine": "^0.8.0"
27
+ "@waica/behaviors": "^0.9.0",
28
+ "@waica/engine": "^0.9.0"
29
29
  }
30
30
  }