diff --git a/docs/specification-content-catalog.md b/docs/specification-content-catalog.md
index b27af18..b99e130 100644
--- a/docs/specification-content-catalog.md
+++ b/docs/specification-content-catalog.md
@@ -188,6 +188,11 @@ Naming convention:
| `prop` | `cart_broken` | `obj.prop.cart_broken.v1` | `poi` | roadside storytelling |
| `prop` | `sign_wood` | `obj.prop.sign_wood.v1` | `poi` | route marker |
| `prop` | `totem_bone` | `obj.prop.totem_bone.v1` | `poi_dark` | undead area marker |
+| `wall` | `cobble_
` | `obj.wall.cobble_.v1` | `town_edge` | `` ∈ `n,ne,e,se,s,sw,w,nw` — cobble segment/corner for isometric town ring |
+| `wall` | `brick_` | `obj.wall.brick_.v1` | `town_edge` | fired brick wall, same 8-way set |
+| `wall` | `palisade_` | `obj.wall.palisade_.v1` | `town_edge` | wooden palisade/log wall, same 8-way set |
+
+Town plaza sprites (manifest texture keys, not `obj.*`): `building.civic.townhall.v0`, `prop.fountain.plaza.v0`, `prop.market.stall.v0`, `building.tavern.v0`, `building.tavern.v1`, `building.house.v2`.
## 3) Sound Cue Catalog (Gameplay + UI)
diff --git a/frontend/.env.local b/frontend/.env.local
new file mode 100644
index 0000000..bf9ba68
--- /dev/null
+++ b/frontend/.env.local
@@ -0,0 +1 @@
+PIXELLAB_API_TOKEN=b87d979a-8027-405d-8e07-105ac494cc0b
diff --git a/frontend/assets/building/building.civic.townhall.v0.png b/frontend/assets/building/building.civic.townhall.v0.png
new file mode 100644
index 0000000..3535258
Binary files /dev/null and b/frontend/assets/building/building.civic.townhall.v0.png differ
diff --git a/frontend/assets/building/building.house.v2.png b/frontend/assets/building/building.house.v2.png
new file mode 100644
index 0000000..20aa877
Binary files /dev/null and b/frontend/assets/building/building.house.v2.png differ
diff --git a/frontend/assets/building/building.tavern.v0.png b/frontend/assets/building/building.tavern.v0.png
new file mode 100644
index 0000000..520b35c
Binary files /dev/null and b/frontend/assets/building/building.tavern.v0.png differ
diff --git a/frontend/assets/building/building.tavern.v1.png b/frontend/assets/building/building.tavern.v1.png
new file mode 100644
index 0000000..57a206b
Binary files /dev/null and b/frontend/assets/building/building.tavern.v1.png differ
diff --git a/frontend/assets/enemies/enemy.bandit_l10_11_canyon.south.png b/frontend/assets/enemies/enemy.bandit_l10_11_canyon.south.png
new file mode 100644
index 0000000..89049c9
Binary files /dev/null and b/frontend/assets/enemies/enemy.bandit_l10_11_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.bandit_l10_11_swamp.south.png b/frontend/assets/enemies/enemy.bandit_l10_11_swamp.south.png
new file mode 100644
index 0000000..85056de
Binary files /dev/null and b/frontend/assets/enemies/enemy.bandit_l10_11_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.bandit_l12_12_astral.south.png b/frontend/assets/enemies/enemy.bandit_l12_12_astral.south.png
new file mode 100644
index 0000000..597e804
Binary files /dev/null and b/frontend/assets/enemies/enemy.bandit_l12_12_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.bandit_l12_12_volcanic.south.png b/frontend/assets/enemies/enemy.bandit_l12_12_volcanic.south.png
new file mode 100644
index 0000000..2dc4e78
Binary files /dev/null and b/frontend/assets/enemies/enemy.bandit_l12_12_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.bandit_l4_5_forest.south.png b/frontend/assets/enemies/enemy.bandit_l4_5_forest.south.png
new file mode 100644
index 0000000..df1485f
Binary files /dev/null and b/frontend/assets/enemies/enemy.bandit_l4_5_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.bandit_l4_5_meadow.south.png b/frontend/assets/enemies/enemy.bandit_l4_5_meadow.south.png
new file mode 100644
index 0000000..f545d87
Binary files /dev/null and b/frontend/assets/enemies/enemy.bandit_l4_5_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.bandit_l6_7_forest.south.png b/frontend/assets/enemies/enemy.bandit_l6_7_forest.south.png
new file mode 100644
index 0000000..3fbc346
Binary files /dev/null and b/frontend/assets/enemies/enemy.bandit_l6_7_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.bandit_l6_7_ruins.south.png b/frontend/assets/enemies/enemy.bandit_l6_7_ruins.south.png
new file mode 100644
index 0000000..9a8f687
Binary files /dev/null and b/frontend/assets/enemies/enemy.bandit_l6_7_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.bandit_l8_9_canyon.south.png b/frontend/assets/enemies/enemy.bandit_l8_9_canyon.south.png
new file mode 100644
index 0000000..222c173
Binary files /dev/null and b/frontend/assets/enemies/enemy.bandit_l8_9_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.bandit_l8_9_ruins.south.png b/frontend/assets/enemies/enemy.bandit_l8_9_ruins.south.png
new file mode 100644
index 0000000..ac3eacf
Binary files /dev/null and b/frontend/assets/enemies/enemy.bandit_l8_9_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.basilisk_l12_13_forest.south.png b/frontend/assets/enemies/enemy.basilisk_l12_13_forest.south.png
new file mode 100644
index 0000000..08994af
Binary files /dev/null and b/frontend/assets/enemies/enemy.basilisk_l12_13_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.basilisk_l12_13_ruins.south.png b/frontend/assets/enemies/enemy.basilisk_l12_13_ruins.south.png
new file mode 100644
index 0000000..a213a6c
Binary files /dev/null and b/frontend/assets/enemies/enemy.basilisk_l12_13_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.basilisk_l14_15_canyon.south.png b/frontend/assets/enemies/enemy.basilisk_l14_15_canyon.south.png
new file mode 100644
index 0000000..af851bd
Binary files /dev/null and b/frontend/assets/enemies/enemy.basilisk_l14_15_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.basilisk_l14_15_ruins.south.png b/frontend/assets/enemies/enemy.basilisk_l14_15_ruins.south.png
new file mode 100644
index 0000000..c7df76b
Binary files /dev/null and b/frontend/assets/enemies/enemy.basilisk_l14_15_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.basilisk_l16_17_canyon.south.png b/frontend/assets/enemies/enemy.basilisk_l16_17_canyon.south.png
new file mode 100644
index 0000000..6c77d7f
Binary files /dev/null and b/frontend/assets/enemies/enemy.basilisk_l16_17_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.basilisk_l16_17_swamp.south.png b/frontend/assets/enemies/enemy.basilisk_l16_17_swamp.south.png
new file mode 100644
index 0000000..ec3760a
Binary files /dev/null and b/frontend/assets/enemies/enemy.basilisk_l16_17_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.basilisk_l18_19_astral.south.png b/frontend/assets/enemies/enemy.basilisk_l18_19_astral.south.png
new file mode 100644
index 0000000..bfc298a
Binary files /dev/null and b/frontend/assets/enemies/enemy.basilisk_l18_19_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.basilisk_l18_19_volcanic.south.png b/frontend/assets/enemies/enemy.basilisk_l18_19_volcanic.south.png
new file mode 100644
index 0000000..a5c3a96
Binary files /dev/null and b/frontend/assets/enemies/enemy.basilisk_l18_19_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.basilisk_l9_11_forest.south.png b/frontend/assets/enemies/enemy.basilisk_l9_11_forest.south.png
new file mode 100644
index 0000000..39e0daf
Binary files /dev/null and b/frontend/assets/enemies/enemy.basilisk_l9_11_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.basilisk_l9_11_meadow.south.png b/frontend/assets/enemies/enemy.basilisk_l9_11_meadow.south.png
new file mode 100644
index 0000000..87a0dfb
Binary files /dev/null and b/frontend/assets/enemies/enemy.basilisk_l9_11_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.battle_lizard_l11_12_canyon.south.png b/frontend/assets/enemies/enemy.battle_lizard_l11_12_canyon.south.png
new file mode 100644
index 0000000..328da8a
Binary files /dev/null and b/frontend/assets/enemies/enemy.battle_lizard_l11_12_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.battle_lizard_l11_12_ruins.south.png b/frontend/assets/enemies/enemy.battle_lizard_l11_12_ruins.south.png
new file mode 100644
index 0000000..ed7869f
Binary files /dev/null and b/frontend/assets/enemies/enemy.battle_lizard_l11_12_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.battle_lizard_l13_14_canyon.south.png b/frontend/assets/enemies/enemy.battle_lizard_l13_14_canyon.south.png
new file mode 100644
index 0000000..e2d4ec2
Binary files /dev/null and b/frontend/assets/enemies/enemy.battle_lizard_l13_14_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.battle_lizard_l13_14_swamp.south.png b/frontend/assets/enemies/enemy.battle_lizard_l13_14_swamp.south.png
new file mode 100644
index 0000000..54220d4
Binary files /dev/null and b/frontend/assets/enemies/enemy.battle_lizard_l13_14_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.battle_lizard_l15_15_astral.south.png b/frontend/assets/enemies/enemy.battle_lizard_l15_15_astral.south.png
new file mode 100644
index 0000000..ee27c49
Binary files /dev/null and b/frontend/assets/enemies/enemy.battle_lizard_l15_15_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.battle_lizard_l15_15_volcanic.south.png b/frontend/assets/enemies/enemy.battle_lizard_l15_15_volcanic.south.png
new file mode 100644
index 0000000..ee54c0b
Binary files /dev/null and b/frontend/assets/enemies/enemy.battle_lizard_l15_15_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.battle_lizard_l7_8_forest.south.png b/frontend/assets/enemies/enemy.battle_lizard_l7_8_forest.south.png
new file mode 100644
index 0000000..e4e8a57
Binary files /dev/null and b/frontend/assets/enemies/enemy.battle_lizard_l7_8_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.battle_lizard_l7_8_meadow.south.png b/frontend/assets/enemies/enemy.battle_lizard_l7_8_meadow.south.png
new file mode 100644
index 0000000..962f52d
Binary files /dev/null and b/frontend/assets/enemies/enemy.battle_lizard_l7_8_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.battle_lizard_l9_10_forest.south.png b/frontend/assets/enemies/enemy.battle_lizard_l9_10_forest.south.png
new file mode 100644
index 0000000..f075981
Binary files /dev/null and b/frontend/assets/enemies/enemy.battle_lizard_l9_10_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.battle_lizard_l9_10_ruins.south.png b/frontend/assets/enemies/enemy.battle_lizard_l9_10_ruins.south.png
new file mode 100644
index 0000000..2e02773
Binary files /dev/null and b/frontend/assets/enemies/enemy.battle_lizard_l9_10_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.boar_l5_5_canyon.south.png b/frontend/assets/enemies/enemy.boar_l5_5_canyon.south.png
new file mode 100644
index 0000000..87a6e90
Binary files /dev/null and b/frontend/assets/enemies/enemy.boar_l5_5_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.boar_l5_5_swamp.south.png b/frontend/assets/enemies/enemy.boar_l5_5_swamp.south.png
new file mode 100644
index 0000000..5d61d9b
Binary files /dev/null and b/frontend/assets/enemies/enemy.boar_l5_5_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.boar_l6_6_astral.south.png b/frontend/assets/enemies/enemy.boar_l6_6_astral.south.png
new file mode 100644
index 0000000..5660c6f
Binary files /dev/null and b/frontend/assets/enemies/enemy.boar_l6_6_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.boar_l6_6_volcanic.south.png b/frontend/assets/enemies/enemy.boar_l6_6_volcanic.south.png
new file mode 100644
index 0000000..7808fee
Binary files /dev/null and b/frontend/assets/enemies/enemy.boar_l6_6_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.cultist_l11_12_canyon.south.png b/frontend/assets/enemies/enemy.cultist_l11_12_canyon.south.png
new file mode 100644
index 0000000..cb52902
Binary files /dev/null and b/frontend/assets/enemies/enemy.cultist_l11_12_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.cultist_l11_12_ruins.south.png b/frontend/assets/enemies/enemy.cultist_l11_12_ruins.south.png
new file mode 100644
index 0000000..f7071cd
Binary files /dev/null and b/frontend/assets/enemies/enemy.cultist_l11_12_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.cultist_l13_14_canyon.south.png b/frontend/assets/enemies/enemy.cultist_l13_14_canyon.south.png
new file mode 100644
index 0000000..781e7bd
Binary files /dev/null and b/frontend/assets/enemies/enemy.cultist_l13_14_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.cultist_l13_14_swamp.south.png b/frontend/assets/enemies/enemy.cultist_l13_14_swamp.south.png
new file mode 100644
index 0000000..93c1fde
Binary files /dev/null and b/frontend/assets/enemies/enemy.cultist_l13_14_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.cultist_l15_16_astral.south.png b/frontend/assets/enemies/enemy.cultist_l15_16_astral.south.png
new file mode 100644
index 0000000..a9ff2bf
Binary files /dev/null and b/frontend/assets/enemies/enemy.cultist_l15_16_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.cultist_l15_16_volcanic.south.png b/frontend/assets/enemies/enemy.cultist_l15_16_volcanic.south.png
new file mode 100644
index 0000000..fdb6667
Binary files /dev/null and b/frontend/assets/enemies/enemy.cultist_l15_16_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.cultist_l6_8_forest.south.png b/frontend/assets/enemies/enemy.cultist_l6_8_forest.south.png
new file mode 100644
index 0000000..304d4a8
Binary files /dev/null and b/frontend/assets/enemies/enemy.cultist_l6_8_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.cultist_l6_8_meadow.south.png b/frontend/assets/enemies/enemy.cultist_l6_8_meadow.south.png
new file mode 100644
index 0000000..6ca4069
Binary files /dev/null and b/frontend/assets/enemies/enemy.cultist_l6_8_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.cultist_l9_10_forest.south.png b/frontend/assets/enemies/enemy.cultist_l9_10_forest.south.png
new file mode 100644
index 0000000..e7ff206
Binary files /dev/null and b/frontend/assets/enemies/enemy.cultist_l9_10_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.cultist_l9_10_ruins.south.png b/frontend/assets/enemies/enemy.cultist_l9_10_ruins.south.png
new file mode 100644
index 0000000..92aa61a
Binary files /dev/null and b/frontend/assets/enemies/enemy.cultist_l9_10_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.demon_l10_12_forest.south.png b/frontend/assets/enemies/enemy.demon_l10_12_forest.south.png
new file mode 100644
index 0000000..2cd0e45
Binary files /dev/null and b/frontend/assets/enemies/enemy.demon_l10_12_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.demon_l10_12_meadow.south.png b/frontend/assets/enemies/enemy.demon_l10_12_meadow.south.png
new file mode 100644
index 0000000..c45d175
Binary files /dev/null and b/frontend/assets/enemies/enemy.demon_l10_12_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.demon_l13_14_forest.south.png b/frontend/assets/enemies/enemy.demon_l13_14_forest.south.png
new file mode 100644
index 0000000..43dd5b8
Binary files /dev/null and b/frontend/assets/enemies/enemy.demon_l13_14_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.demon_l13_14_ruins.south.png b/frontend/assets/enemies/enemy.demon_l13_14_ruins.south.png
new file mode 100644
index 0000000..5984f9d
Binary files /dev/null and b/frontend/assets/enemies/enemy.demon_l13_14_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.demon_l15_16_canyon.south.png b/frontend/assets/enemies/enemy.demon_l15_16_canyon.south.png
new file mode 100644
index 0000000..b83fcab
Binary files /dev/null and b/frontend/assets/enemies/enemy.demon_l15_16_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.demon_l15_16_ruins.south.png b/frontend/assets/enemies/enemy.demon_l15_16_ruins.south.png
new file mode 100644
index 0000000..14c4c94
Binary files /dev/null and b/frontend/assets/enemies/enemy.demon_l15_16_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.demon_l17_18_canyon.south.png b/frontend/assets/enemies/enemy.demon_l17_18_canyon.south.png
new file mode 100644
index 0000000..942b98c
Binary files /dev/null and b/frontend/assets/enemies/enemy.demon_l17_18_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.demon_l17_18_swamp.south.png b/frontend/assets/enemies/enemy.demon_l17_18_swamp.south.png
new file mode 100644
index 0000000..48af6ff
Binary files /dev/null and b/frontend/assets/enemies/enemy.demon_l17_18_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.demon_l19_20_astral.south.png b/frontend/assets/enemies/enemy.demon_l19_20_astral.south.png
new file mode 100644
index 0000000..46cda36
Binary files /dev/null and b/frontend/assets/enemies/enemy.demon_l19_20_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.demon_l19_20_volcanic.south.png b/frontend/assets/enemies/enemy.demon_l19_20_volcanic.south.png
new file mode 100644
index 0000000..2481de3
Binary files /dev/null and b/frontend/assets/enemies/enemy.demon_l19_20_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.element_l12_14_forest.south.png b/frontend/assets/enemies/enemy.element_l12_14_forest.south.png
new file mode 100644
index 0000000..7f24cef
Binary files /dev/null and b/frontend/assets/enemies/enemy.element_l12_14_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.element_l15_16_ruins.south.png b/frontend/assets/enemies/enemy.element_l15_16_ruins.south.png
new file mode 100644
index 0000000..9abb525
Binary files /dev/null and b/frontend/assets/enemies/enemy.element_l15_16_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.element_l17_18_canyon.south.png b/frontend/assets/enemies/enemy.element_l17_18_canyon.south.png
new file mode 100644
index 0000000..122e0c1
Binary files /dev/null and b/frontend/assets/enemies/enemy.element_l17_18_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.element_l18_20_meadow.south.png b/frontend/assets/enemies/enemy.element_l18_20_meadow.south.png
new file mode 100644
index 0000000..19bbe76
Binary files /dev/null and b/frontend/assets/enemies/enemy.element_l18_20_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.element_l19_20_swamp.south.png b/frontend/assets/enemies/enemy.element_l19_20_swamp.south.png
new file mode 100644
index 0000000..8d85b65
Binary files /dev/null and b/frontend/assets/enemies/enemy.element_l19_20_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.element_l21_22_astral.south.png b/frontend/assets/enemies/enemy.element_l21_22_astral.south.png
new file mode 100644
index 0000000..3f33b33
Binary files /dev/null and b/frontend/assets/enemies/enemy.element_l21_22_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.element_l21_22_forest.south.png b/frontend/assets/enemies/enemy.element_l21_22_forest.south.png
new file mode 100644
index 0000000..d6cd6e8
Binary files /dev/null and b/frontend/assets/enemies/enemy.element_l21_22_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.element_l23_24_ruins.south.png b/frontend/assets/enemies/enemy.element_l23_24_ruins.south.png
new file mode 100644
index 0000000..ee0b588
Binary files /dev/null and b/frontend/assets/enemies/enemy.element_l23_24_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.element_l25_26_canyon.south.png b/frontend/assets/enemies/enemy.element_l25_26_canyon.south.png
new file mode 100644
index 0000000..8eff8e3
Binary files /dev/null and b/frontend/assets/enemies/enemy.element_l25_26_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.element_l27_28_volcanic.south.png b/frontend/assets/enemies/enemy.element_l27_28_volcanic.south.png
new file mode 100644
index 0000000..ba3b787
Binary files /dev/null and b/frontend/assets/enemies/enemy.element_l27_28_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.forest_warden_l20_22_forest.south.png b/frontend/assets/enemies/enemy.forest_warden_l20_22_forest.south.png
new file mode 100644
index 0000000..9fd5a55
Binary files /dev/null and b/frontend/assets/enemies/enemy.forest_warden_l20_22_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.forest_warden_l20_22_meadow.south.png b/frontend/assets/enemies/enemy.forest_warden_l20_22_meadow.south.png
new file mode 100644
index 0000000..48a4a69
Binary files /dev/null and b/frontend/assets/enemies/enemy.forest_warden_l20_22_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.forest_warden_l23_24_forest.south.png b/frontend/assets/enemies/enemy.forest_warden_l23_24_forest.south.png
new file mode 100644
index 0000000..8926be5
Binary files /dev/null and b/frontend/assets/enemies/enemy.forest_warden_l23_24_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.forest_warden_l23_24_ruins.south.png b/frontend/assets/enemies/enemy.forest_warden_l23_24_ruins.south.png
new file mode 100644
index 0000000..c693d0f
Binary files /dev/null and b/frontend/assets/enemies/enemy.forest_warden_l23_24_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.forest_warden_l25_26_canyon.south.png b/frontend/assets/enemies/enemy.forest_warden_l25_26_canyon.south.png
new file mode 100644
index 0000000..cf7545d
Binary files /dev/null and b/frontend/assets/enemies/enemy.forest_warden_l25_26_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.forest_warden_l25_26_ruins.south.png b/frontend/assets/enemies/enemy.forest_warden_l25_26_ruins.south.png
new file mode 100644
index 0000000..4eb5be5
Binary files /dev/null and b/frontend/assets/enemies/enemy.forest_warden_l25_26_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.forest_warden_l27_28_canyon.south.png b/frontend/assets/enemies/enemy.forest_warden_l27_28_canyon.south.png
new file mode 100644
index 0000000..6a43738
Binary files /dev/null and b/frontend/assets/enemies/enemy.forest_warden_l27_28_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.forest_warden_l27_28_swamp.south.png b/frontend/assets/enemies/enemy.forest_warden_l27_28_swamp.south.png
new file mode 100644
index 0000000..f502523
Binary files /dev/null and b/frontend/assets/enemies/enemy.forest_warden_l27_28_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.forest_warden_l29_30_astral.south.png b/frontend/assets/enemies/enemy.forest_warden_l29_30_astral.south.png
new file mode 100644
index 0000000..31d90ce
Binary files /dev/null and b/frontend/assets/enemies/enemy.forest_warden_l29_30_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.forest_warden_l29_30_volcanic.south.png b/frontend/assets/enemies/enemy.forest_warden_l29_30_volcanic.south.png
new file mode 100644
index 0000000..7a37a1b
Binary files /dev/null and b/frontend/assets/enemies/enemy.forest_warden_l29_30_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.golem_l11_12_forest.south.png b/frontend/assets/enemies/enemy.golem_l11_12_forest.south.png
new file mode 100644
index 0000000..addc9d2
Binary files /dev/null and b/frontend/assets/enemies/enemy.golem_l11_12_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.golem_l11_12_ruins.south.png b/frontend/assets/enemies/enemy.golem_l11_12_ruins.south.png
new file mode 100644
index 0000000..dcc4a9e
Binary files /dev/null and b/frontend/assets/enemies/enemy.golem_l11_12_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.golem_l13_14_canyon.south.png b/frontend/assets/enemies/enemy.golem_l13_14_canyon.south.png
new file mode 100644
index 0000000..c0f7e29
Binary files /dev/null and b/frontend/assets/enemies/enemy.golem_l13_14_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.golem_l13_14_ruins.south.png b/frontend/assets/enemies/enemy.golem_l13_14_ruins.south.png
new file mode 100644
index 0000000..4fc3bf2
Binary files /dev/null and b/frontend/assets/enemies/enemy.golem_l13_14_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.golem_l15_16_canyon.south.png b/frontend/assets/enemies/enemy.golem_l15_16_canyon.south.png
new file mode 100644
index 0000000..0259eae
Binary files /dev/null and b/frontend/assets/enemies/enemy.golem_l15_16_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.golem_l15_16_swamp.south.png b/frontend/assets/enemies/enemy.golem_l15_16_swamp.south.png
new file mode 100644
index 0000000..ba9a2bc
Binary files /dev/null and b/frontend/assets/enemies/enemy.golem_l15_16_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.golem_l17_18_astral.south.png b/frontend/assets/enemies/enemy.golem_l17_18_astral.south.png
new file mode 100644
index 0000000..c5b9266
Binary files /dev/null and b/frontend/assets/enemies/enemy.golem_l17_18_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.golem_l17_18_volcanic.south.png b/frontend/assets/enemies/enemy.golem_l17_18_volcanic.south.png
new file mode 100644
index 0000000..f737e39
Binary files /dev/null and b/frontend/assets/enemies/enemy.golem_l17_18_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.golem_l8_10_forest.south.png b/frontend/assets/enemies/enemy.golem_l8_10_forest.south.png
new file mode 100644
index 0000000..1b120ba
Binary files /dev/null and b/frontend/assets/enemies/enemy.golem_l8_10_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.golem_l8_10_meadow.south.png b/frontend/assets/enemies/enemy.golem_l8_10_meadow.south.png
new file mode 100644
index 0000000..c77e780
Binary files /dev/null and b/frontend/assets/enemies/enemy.golem_l8_10_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.harpy_l10_11_canyon.south.png b/frontend/assets/enemies/enemy.harpy_l10_11_canyon.south.png
new file mode 100644
index 0000000..6a6dc23
Binary files /dev/null and b/frontend/assets/enemies/enemy.harpy_l10_11_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.harpy_l10_11_ruins.south.png b/frontend/assets/enemies/enemy.harpy_l10_11_ruins.south.png
new file mode 100644
index 0000000..ee005ae
Binary files /dev/null and b/frontend/assets/enemies/enemy.harpy_l10_11_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.harpy_l12_13_canyon.south.png b/frontend/assets/enemies/enemy.harpy_l12_13_canyon.south.png
new file mode 100644
index 0000000..99aa79f
Binary files /dev/null and b/frontend/assets/enemies/enemy.harpy_l12_13_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.harpy_l12_13_swamp.south.png b/frontend/assets/enemies/enemy.harpy_l12_13_swamp.south.png
new file mode 100644
index 0000000..2841861
Binary files /dev/null and b/frontend/assets/enemies/enemy.harpy_l12_13_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.harpy_l14_15_astral.south.png b/frontend/assets/enemies/enemy.harpy_l14_15_astral.south.png
new file mode 100644
index 0000000..684b5fd
Binary files /dev/null and b/frontend/assets/enemies/enemy.harpy_l14_15_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.harpy_l14_15_volcanic.south.png b/frontend/assets/enemies/enemy.harpy_l14_15_volcanic.south.png
new file mode 100644
index 0000000..1b1be8d
Binary files /dev/null and b/frontend/assets/enemies/enemy.harpy_l14_15_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.harpy_l6_7_forest.south.png b/frontend/assets/enemies/enemy.harpy_l6_7_forest.south.png
new file mode 100644
index 0000000..6c07297
Binary files /dev/null and b/frontend/assets/enemies/enemy.harpy_l6_7_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.harpy_l6_7_meadow.south.png b/frontend/assets/enemies/enemy.harpy_l6_7_meadow.south.png
new file mode 100644
index 0000000..f10b8b9
Binary files /dev/null and b/frontend/assets/enemies/enemy.harpy_l6_7_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.harpy_l8_9_forest.south.png b/frontend/assets/enemies/enemy.harpy_l8_9_forest.south.png
new file mode 100644
index 0000000..704aee0
Binary files /dev/null and b/frontend/assets/enemies/enemy.harpy_l8_9_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.harpy_l8_9_ruins.south.png b/frontend/assets/enemies/enemy.harpy_l8_9_ruins.south.png
new file mode 100644
index 0000000..5b7a091
Binary files /dev/null and b/frontend/assets/enemies/enemy.harpy_l8_9_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.manticore_l14_16_forest.south.png b/frontend/assets/enemies/enemy.manticore_l14_16_forest.south.png
new file mode 100644
index 0000000..f9ee4f4
Binary files /dev/null and b/frontend/assets/enemies/enemy.manticore_l14_16_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.manticore_l14_16_meadow.south.png b/frontend/assets/enemies/enemy.manticore_l14_16_meadow.south.png
new file mode 100644
index 0000000..840f012
Binary files /dev/null and b/frontend/assets/enemies/enemy.manticore_l14_16_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.manticore_l17_19_forest.south.png b/frontend/assets/enemies/enemy.manticore_l17_19_forest.south.png
new file mode 100644
index 0000000..467e7cb
Binary files /dev/null and b/frontend/assets/enemies/enemy.manticore_l17_19_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.manticore_l17_19_ruins.south.png b/frontend/assets/enemies/enemy.manticore_l17_19_ruins.south.png
new file mode 100644
index 0000000..7a04333
Binary files /dev/null and b/frontend/assets/enemies/enemy.manticore_l17_19_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.manticore_l20_22_canyon.south.png b/frontend/assets/enemies/enemy.manticore_l20_22_canyon.south.png
new file mode 100644
index 0000000..6017274
Binary files /dev/null and b/frontend/assets/enemies/enemy.manticore_l20_22_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.manticore_l20_22_ruins.south.png b/frontend/assets/enemies/enemy.manticore_l20_22_ruins.south.png
new file mode 100644
index 0000000..710830e
Binary files /dev/null and b/frontend/assets/enemies/enemy.manticore_l20_22_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.manticore_l23_24_canyon.south.png b/frontend/assets/enemies/enemy.manticore_l23_24_canyon.south.png
new file mode 100644
index 0000000..4c72396
Binary files /dev/null and b/frontend/assets/enemies/enemy.manticore_l23_24_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.manticore_l23_24_swamp.south.png b/frontend/assets/enemies/enemy.manticore_l23_24_swamp.south.png
new file mode 100644
index 0000000..f4919fa
Binary files /dev/null and b/frontend/assets/enemies/enemy.manticore_l23_24_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.manticore_l25_26_astral.south.png b/frontend/assets/enemies/enemy.manticore_l25_26_astral.south.png
new file mode 100644
index 0000000..147064b
Binary files /dev/null and b/frontend/assets/enemies/enemy.manticore_l25_26_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.manticore_l25_26_volcanic.south.png b/frontend/assets/enemies/enemy.manticore_l25_26_volcanic.south.png
new file mode 100644
index 0000000..df1ac48
Binary files /dev/null and b/frontend/assets/enemies/enemy.manticore_l25_26_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.orc_l11_11_canyon.south.png b/frontend/assets/enemies/enemy.orc_l11_11_canyon.south.png
new file mode 100644
index 0000000..dd63a72
Binary files /dev/null and b/frontend/assets/enemies/enemy.orc_l11_11_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.orc_l11_11_swamp.south.png b/frontend/assets/enemies/enemy.orc_l11_11_swamp.south.png
new file mode 100644
index 0000000..8a528d9
Binary files /dev/null and b/frontend/assets/enemies/enemy.orc_l11_11_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.orc_l12_12_astral.south.png b/frontend/assets/enemies/enemy.orc_l12_12_astral.south.png
new file mode 100644
index 0000000..b485aee
Binary files /dev/null and b/frontend/assets/enemies/enemy.orc_l12_12_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.orc_l12_12_volcanic.south.png b/frontend/assets/enemies/enemy.orc_l12_12_volcanic.south.png
new file mode 100644
index 0000000..44942e4
Binary files /dev/null and b/frontend/assets/enemies/enemy.orc_l12_12_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.orc_l5_6_forest.south.png b/frontend/assets/enemies/enemy.orc_l5_6_forest.south.png
new file mode 100644
index 0000000..fc790ed
Binary files /dev/null and b/frontend/assets/enemies/enemy.orc_l5_6_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.orc_l5_6_meadow.south.png b/frontend/assets/enemies/enemy.orc_l5_6_meadow.south.png
new file mode 100644
index 0000000..8234eb3
Binary files /dev/null and b/frontend/assets/enemies/enemy.orc_l5_6_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.orc_l7_8_forest.south.png b/frontend/assets/enemies/enemy.orc_l7_8_forest.south.png
new file mode 100644
index 0000000..87ab389
Binary files /dev/null and b/frontend/assets/enemies/enemy.orc_l7_8_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.orc_l7_8_ruins.south.png b/frontend/assets/enemies/enemy.orc_l7_8_ruins.south.png
new file mode 100644
index 0000000..e10d245
Binary files /dev/null and b/frontend/assets/enemies/enemy.orc_l7_8_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.orc_l9_10_canyon.south.png b/frontend/assets/enemies/enemy.orc_l9_10_canyon.south.png
new file mode 100644
index 0000000..592d090
Binary files /dev/null and b/frontend/assets/enemies/enemy.orc_l9_10_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.orc_l9_10_ruins.south.png b/frontend/assets/enemies/enemy.orc_l9_10_ruins.south.png
new file mode 100644
index 0000000..2a861c5
Binary files /dev/null and b/frontend/assets/enemies/enemy.orc_l9_10_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.shade_l10_12_forest.south.png b/frontend/assets/enemies/enemy.shade_l10_12_forest.south.png
new file mode 100644
index 0000000..c32dacd
Binary files /dev/null and b/frontend/assets/enemies/enemy.shade_l10_12_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.shade_l10_12_meadow.south.png b/frontend/assets/enemies/enemy.shade_l10_12_meadow.south.png
new file mode 100644
index 0000000..f372c9c
Binary files /dev/null and b/frontend/assets/enemies/enemy.shade_l10_12_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.shade_l13_15_forest.south.png b/frontend/assets/enemies/enemy.shade_l13_15_forest.south.png
new file mode 100644
index 0000000..428cfff
Binary files /dev/null and b/frontend/assets/enemies/enemy.shade_l13_15_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.shade_l13_15_ruins.south.png b/frontend/assets/enemies/enemy.shade_l13_15_ruins.south.png
new file mode 100644
index 0000000..e743c0a
Binary files /dev/null and b/frontend/assets/enemies/enemy.shade_l13_15_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.shade_l16_18_canyon.south.png b/frontend/assets/enemies/enemy.shade_l16_18_canyon.south.png
new file mode 100644
index 0000000..9d77349
Binary files /dev/null and b/frontend/assets/enemies/enemy.shade_l16_18_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.shade_l16_18_ruins.south.png b/frontend/assets/enemies/enemy.shade_l16_18_ruins.south.png
new file mode 100644
index 0000000..36c566f
Binary files /dev/null and b/frontend/assets/enemies/enemy.shade_l16_18_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.shade_l19_20_canyon.south.png b/frontend/assets/enemies/enemy.shade_l19_20_canyon.south.png
new file mode 100644
index 0000000..9f6e921
Binary files /dev/null and b/frontend/assets/enemies/enemy.shade_l19_20_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.shade_l19_20_swamp.south.png b/frontend/assets/enemies/enemy.shade_l19_20_swamp.south.png
new file mode 100644
index 0000000..c9b29bf
Binary files /dev/null and b/frontend/assets/enemies/enemy.shade_l19_20_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.shade_l21_22_astral.south.png b/frontend/assets/enemies/enemy.shade_l21_22_astral.south.png
new file mode 100644
index 0000000..25e71b9
Binary files /dev/null and b/frontend/assets/enemies/enemy.shade_l21_22_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.shade_l21_22_volcanic.south.png b/frontend/assets/enemies/enemy.shade_l21_22_volcanic.south.png
new file mode 100644
index 0000000..b100ad2
Binary files /dev/null and b/frontend/assets/enemies/enemy.shade_l21_22_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_king_l15_17_forest.south.png b/frontend/assets/enemies/enemy.skeleton_king_l15_17_forest.south.png
new file mode 100644
index 0000000..7612fad
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_king_l15_17_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_king_l15_17_meadow.south.png b/frontend/assets/enemies/enemy.skeleton_king_l15_17_meadow.south.png
new file mode 100644
index 0000000..9ffa762
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_king_l15_17_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_king_l18_19_forest.south.png b/frontend/assets/enemies/enemy.skeleton_king_l18_19_forest.south.png
new file mode 100644
index 0000000..2e0e91e
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_king_l18_19_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_king_l18_19_ruins.south.png b/frontend/assets/enemies/enemy.skeleton_king_l18_19_ruins.south.png
new file mode 100644
index 0000000..a8ce9d0
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_king_l18_19_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_king_l20_21_canyon.south.png b/frontend/assets/enemies/enemy.skeleton_king_l20_21_canyon.south.png
new file mode 100644
index 0000000..6e3c0c5
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_king_l20_21_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_king_l20_21_ruins.south.png b/frontend/assets/enemies/enemy.skeleton_king_l20_21_ruins.south.png
new file mode 100644
index 0000000..ac5d0dd
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_king_l20_21_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_king_l22_23_canyon.south.png b/frontend/assets/enemies/enemy.skeleton_king_l22_23_canyon.south.png
new file mode 100644
index 0000000..02da912
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_king_l22_23_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_king_l22_23_swamp.south.png b/frontend/assets/enemies/enemy.skeleton_king_l22_23_swamp.south.png
new file mode 100644
index 0000000..c44fbf4
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_king_l22_23_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_king_l24_25_astral.south.png b/frontend/assets/enemies/enemy.skeleton_king_l24_25_astral.south.png
new file mode 100644
index 0000000..125b639
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_king_l24_25_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_king_l24_25_volcanic.south.png b/frontend/assets/enemies/enemy.skeleton_king_l24_25_volcanic.south.png
new file mode 100644
index 0000000..245a1d2
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_king_l24_25_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_l10_11_canyon.south.png b/frontend/assets/enemies/enemy.skeleton_l10_11_canyon.south.png
new file mode 100644
index 0000000..af12a31
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_l10_11_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_l10_11_ruins.south.png b/frontend/assets/enemies/enemy.skeleton_l10_11_ruins.south.png
new file mode 100644
index 0000000..3cf7eaf
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_l10_11_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_l12_13_canyon.south.png b/frontend/assets/enemies/enemy.skeleton_l12_13_canyon.south.png
new file mode 100644
index 0000000..095e908
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_l12_13_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_l12_13_swamp.south.png b/frontend/assets/enemies/enemy.skeleton_l12_13_swamp.south.png
new file mode 100644
index 0000000..a2b31d3
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_l12_13_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_l14_14_astral.south.png b/frontend/assets/enemies/enemy.skeleton_l14_14_astral.south.png
new file mode 100644
index 0000000..3174e7b
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_l14_14_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_l14_14_volcanic.south.png b/frontend/assets/enemies/enemy.skeleton_l14_14_volcanic.south.png
new file mode 100644
index 0000000..9a43940
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_l14_14_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_l6_7_forest.south.png b/frontend/assets/enemies/enemy.skeleton_l6_7_forest.south.png
new file mode 100644
index 0000000..12697fd
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_l6_7_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_l6_7_meadow.south.png b/frontend/assets/enemies/enemy.skeleton_l6_7_meadow.south.png
new file mode 100644
index 0000000..4b7cd76
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_l6_7_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_l8_9_forest.south.png b/frontend/assets/enemies/enemy.skeleton_l8_9_forest.south.png
new file mode 100644
index 0000000..3fa046a
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_l8_9_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.skeleton_l8_9_ruins.south.png b/frontend/assets/enemies/enemy.skeleton_l8_9_ruins.south.png
new file mode 100644
index 0000000..2dd4ebd
Binary files /dev/null and b/frontend/assets/enemies/enemy.skeleton_l8_9_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.spider_l4_5_forest.south.png b/frontend/assets/enemies/enemy.spider_l4_5_forest.south.png
new file mode 100644
index 0000000..960365c
Binary files /dev/null and b/frontend/assets/enemies/enemy.spider_l4_5_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.spider_l4_5_meadow.south.png b/frontend/assets/enemies/enemy.spider_l4_5_meadow.south.png
new file mode 100644
index 0000000..e0b7c3c
Binary files /dev/null and b/frontend/assets/enemies/enemy.spider_l4_5_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.spider_l6_6_forest.south.png b/frontend/assets/enemies/enemy.spider_l6_6_forest.south.png
new file mode 100644
index 0000000..0493166
Binary files /dev/null and b/frontend/assets/enemies/enemy.spider_l6_6_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.spider_l6_6_ruins.south.png b/frontend/assets/enemies/enemy.spider_l6_6_ruins.south.png
new file mode 100644
index 0000000..e55674b
Binary files /dev/null and b/frontend/assets/enemies/enemy.spider_l6_6_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.spider_l7_7_canyon.south.png b/frontend/assets/enemies/enemy.spider_l7_7_canyon.south.png
new file mode 100644
index 0000000..bcb0c9d
Binary files /dev/null and b/frontend/assets/enemies/enemy.spider_l7_7_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.spider_l7_7_ruins.south.png b/frontend/assets/enemies/enemy.spider_l7_7_ruins.south.png
new file mode 100644
index 0000000..18b0698
Binary files /dev/null and b/frontend/assets/enemies/enemy.spider_l7_7_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.spider_l8_8_canyon.south.png b/frontend/assets/enemies/enemy.spider_l8_8_canyon.south.png
new file mode 100644
index 0000000..c3f7a00
Binary files /dev/null and b/frontend/assets/enemies/enemy.spider_l8_8_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.spider_l8_8_swamp.south.png b/frontend/assets/enemies/enemy.spider_l8_8_swamp.south.png
new file mode 100644
index 0000000..e4b76df
Binary files /dev/null and b/frontend/assets/enemies/enemy.spider_l8_8_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.spider_l9_9_astral.south.png b/frontend/assets/enemies/enemy.spider_l9_9_astral.south.png
new file mode 100644
index 0000000..2ca8442
Binary files /dev/null and b/frontend/assets/enemies/enemy.spider_l9_9_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.spider_l9_9_volcanic.south.png b/frontend/assets/enemies/enemy.spider_l9_9_volcanic.south.png
new file mode 100644
index 0000000..c546e37
Binary files /dev/null and b/frontend/assets/enemies/enemy.spider_l9_9_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.titan_l25_27_forest.south.png b/frontend/assets/enemies/enemy.titan_l25_27_forest.south.png
new file mode 100644
index 0000000..46306b2
Binary files /dev/null and b/frontend/assets/enemies/enemy.titan_l25_27_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.titan_l25_27_meadow.south.png b/frontend/assets/enemies/enemy.titan_l25_27_meadow.south.png
new file mode 100644
index 0000000..226a893
Binary files /dev/null and b/frontend/assets/enemies/enemy.titan_l25_27_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.titan_l28_29_forest.south.png b/frontend/assets/enemies/enemy.titan_l28_29_forest.south.png
new file mode 100644
index 0000000..f2c54a5
Binary files /dev/null and b/frontend/assets/enemies/enemy.titan_l28_29_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.titan_l28_29_ruins.south.png b/frontend/assets/enemies/enemy.titan_l28_29_ruins.south.png
new file mode 100644
index 0000000..20c3943
Binary files /dev/null and b/frontend/assets/enemies/enemy.titan_l28_29_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.titan_l30_31_canyon.south.png b/frontend/assets/enemies/enemy.titan_l30_31_canyon.south.png
new file mode 100644
index 0000000..5dfaf55
Binary files /dev/null and b/frontend/assets/enemies/enemy.titan_l30_31_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.titan_l30_31_ruins.south.png b/frontend/assets/enemies/enemy.titan_l30_31_ruins.south.png
new file mode 100644
index 0000000..229ba14
Binary files /dev/null and b/frontend/assets/enemies/enemy.titan_l30_31_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.titan_l32_33_canyon.south.png b/frontend/assets/enemies/enemy.titan_l32_33_canyon.south.png
new file mode 100644
index 0000000..6fb32be
Binary files /dev/null and b/frontend/assets/enemies/enemy.titan_l32_33_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.titan_l32_33_swamp.south.png b/frontend/assets/enemies/enemy.titan_l32_33_swamp.south.png
new file mode 100644
index 0000000..d8ff8fe
Binary files /dev/null and b/frontend/assets/enemies/enemy.titan_l32_33_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.titan_l34_35_astral.south.png b/frontend/assets/enemies/enemy.titan_l34_35_astral.south.png
new file mode 100644
index 0000000..cfa6130
Binary files /dev/null and b/frontend/assets/enemies/enemy.titan_l34_35_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.titan_l34_35_volcanic.south.png b/frontend/assets/enemies/enemy.titan_l34_35_volcanic.south.png
new file mode 100644
index 0000000..078b7b3
Binary files /dev/null and b/frontend/assets/enemies/enemy.titan_l34_35_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.treant_l18_20_forest.south.png b/frontend/assets/enemies/enemy.treant_l18_20_forest.south.png
new file mode 100644
index 0000000..3ca4eaf
Binary files /dev/null and b/frontend/assets/enemies/enemy.treant_l18_20_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.treant_l18_20_meadow.south.png b/frontend/assets/enemies/enemy.treant_l18_20_meadow.south.png
new file mode 100644
index 0000000..1be780b
Binary files /dev/null and b/frontend/assets/enemies/enemy.treant_l18_20_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.treant_l21_23_forest.south.png b/frontend/assets/enemies/enemy.treant_l21_23_forest.south.png
new file mode 100644
index 0000000..93aeb7f
Binary files /dev/null and b/frontend/assets/enemies/enemy.treant_l21_23_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.treant_l21_23_ruins.south.png b/frontend/assets/enemies/enemy.treant_l21_23_ruins.south.png
new file mode 100644
index 0000000..9fcb55d
Binary files /dev/null and b/frontend/assets/enemies/enemy.treant_l21_23_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.treant_l24_26_canyon.south.png b/frontend/assets/enemies/enemy.treant_l24_26_canyon.south.png
new file mode 100644
index 0000000..95dff80
Binary files /dev/null and b/frontend/assets/enemies/enemy.treant_l24_26_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.treant_l24_26_ruins.south.png b/frontend/assets/enemies/enemy.treant_l24_26_ruins.south.png
new file mode 100644
index 0000000..febdd47
Binary files /dev/null and b/frontend/assets/enemies/enemy.treant_l24_26_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.treant_l27_28_canyon.south.png b/frontend/assets/enemies/enemy.treant_l27_28_canyon.south.png
new file mode 100644
index 0000000..792964a
Binary files /dev/null and b/frontend/assets/enemies/enemy.treant_l27_28_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.treant_l27_28_swamp.south.png b/frontend/assets/enemies/enemy.treant_l27_28_swamp.south.png
new file mode 100644
index 0000000..1fafd42
Binary files /dev/null and b/frontend/assets/enemies/enemy.treant_l27_28_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.treant_l29_30_astral.south.png b/frontend/assets/enemies/enemy.treant_l29_30_astral.south.png
new file mode 100644
index 0000000..a705131
Binary files /dev/null and b/frontend/assets/enemies/enemy.treant_l29_30_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.treant_l29_30_volcanic.south.png b/frontend/assets/enemies/enemy.treant_l29_30_volcanic.south.png
new file mode 100644
index 0000000..991adb3
Binary files /dev/null and b/frontend/assets/enemies/enemy.treant_l29_30_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.wraith_l11_12_canyon.south.png b/frontend/assets/enemies/enemy.wraith_l11_12_canyon.south.png
new file mode 100644
index 0000000..726def7
Binary files /dev/null and b/frontend/assets/enemies/enemy.wraith_l11_12_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.wraith_l11_12_swamp.south.png b/frontend/assets/enemies/enemy.wraith_l11_12_swamp.south.png
new file mode 100644
index 0000000..93503d4
Binary files /dev/null and b/frontend/assets/enemies/enemy.wraith_l11_12_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.wraith_l13_14_astral.south.png b/frontend/assets/enemies/enemy.wraith_l13_14_astral.south.png
new file mode 100644
index 0000000..500e6e6
Binary files /dev/null and b/frontend/assets/enemies/enemy.wraith_l13_14_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.wraith_l13_14_volcanic.south.png b/frontend/assets/enemies/enemy.wraith_l13_14_volcanic.south.png
new file mode 100644
index 0000000..2cab778
Binary files /dev/null and b/frontend/assets/enemies/enemy.wraith_l13_14_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.wraith_l5_6_forest.south.png b/frontend/assets/enemies/enemy.wraith_l5_6_forest.south.png
new file mode 100644
index 0000000..8c5c10e
Binary files /dev/null and b/frontend/assets/enemies/enemy.wraith_l5_6_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.wraith_l5_6_meadow.south.png b/frontend/assets/enemies/enemy.wraith_l5_6_meadow.south.png
new file mode 100644
index 0000000..c37fed0
Binary files /dev/null and b/frontend/assets/enemies/enemy.wraith_l5_6_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.wraith_l7_8_forest.south.png b/frontend/assets/enemies/enemy.wraith_l7_8_forest.south.png
new file mode 100644
index 0000000..b631385
Binary files /dev/null and b/frontend/assets/enemies/enemy.wraith_l7_8_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.wraith_l7_8_ruins.south.png b/frontend/assets/enemies/enemy.wraith_l7_8_ruins.south.png
new file mode 100644
index 0000000..5624886
Binary files /dev/null and b/frontend/assets/enemies/enemy.wraith_l7_8_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.wraith_l9_10_canyon.south.png b/frontend/assets/enemies/enemy.wraith_l9_10_canyon.south.png
new file mode 100644
index 0000000..a16db96
Binary files /dev/null and b/frontend/assets/enemies/enemy.wraith_l9_10_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.wraith_l9_10_ruins.south.png b/frontend/assets/enemies/enemy.wraith_l9_10_ruins.south.png
new file mode 100644
index 0000000..9da9665
Binary files /dev/null and b/frontend/assets/enemies/enemy.wraith_l9_10_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.wyvern_l12_14_forest.south.png b/frontend/assets/enemies/enemy.wyvern_l12_14_forest.south.png
new file mode 100644
index 0000000..4fe40c5
Binary files /dev/null and b/frontend/assets/enemies/enemy.wyvern_l12_14_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.wyvern_l12_14_meadow.south.png b/frontend/assets/enemies/enemy.wyvern_l12_14_meadow.south.png
new file mode 100644
index 0000000..8f94cfc
Binary files /dev/null and b/frontend/assets/enemies/enemy.wyvern_l12_14_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.wyvern_l15_17_forest.south.png b/frontend/assets/enemies/enemy.wyvern_l15_17_forest.south.png
new file mode 100644
index 0000000..8f9defc
Binary files /dev/null and b/frontend/assets/enemies/enemy.wyvern_l15_17_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.wyvern_l15_17_ruins.south.png b/frontend/assets/enemies/enemy.wyvern_l15_17_ruins.south.png
new file mode 100644
index 0000000..58923fe
Binary files /dev/null and b/frontend/assets/enemies/enemy.wyvern_l15_17_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.wyvern_l18_20_canyon.south.png b/frontend/assets/enemies/enemy.wyvern_l18_20_canyon.south.png
new file mode 100644
index 0000000..1c50984
Binary files /dev/null and b/frontend/assets/enemies/enemy.wyvern_l18_20_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.wyvern_l18_20_ruins.south.png b/frontend/assets/enemies/enemy.wyvern_l18_20_ruins.south.png
new file mode 100644
index 0000000..2de0970
Binary files /dev/null and b/frontend/assets/enemies/enemy.wyvern_l18_20_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.wyvern_l21_22_canyon.south.png b/frontend/assets/enemies/enemy.wyvern_l21_22_canyon.south.png
new file mode 100644
index 0000000..6e6bb6f
Binary files /dev/null and b/frontend/assets/enemies/enemy.wyvern_l21_22_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.wyvern_l21_22_swamp.south.png b/frontend/assets/enemies/enemy.wyvern_l21_22_swamp.south.png
new file mode 100644
index 0000000..7cf8e38
Binary files /dev/null and b/frontend/assets/enemies/enemy.wyvern_l21_22_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.wyvern_l23_24_astral.south.png b/frontend/assets/enemies/enemy.wyvern_l23_24_astral.south.png
new file mode 100644
index 0000000..1af0b0b
Binary files /dev/null and b/frontend/assets/enemies/enemy.wyvern_l23_24_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.wyvern_l23_24_volcanic.south.png b/frontend/assets/enemies/enemy.wyvern_l23_24_volcanic.south.png
new file mode 100644
index 0000000..b47d5e2
Binary files /dev/null and b/frontend/assets/enemies/enemy.wyvern_l23_24_volcanic.south.png differ
diff --git a/frontend/assets/enemies/enemy.zombie_l3_4_forest.south.png b/frontend/assets/enemies/enemy.zombie_l3_4_forest.south.png
new file mode 100644
index 0000000..f10a83f
Binary files /dev/null and b/frontend/assets/enemies/enemy.zombie_l3_4_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.zombie_l3_4_meadow.south.png b/frontend/assets/enemies/enemy.zombie_l3_4_meadow.south.png
new file mode 100644
index 0000000..cc9d36e
Binary files /dev/null and b/frontend/assets/enemies/enemy.zombie_l3_4_meadow.south.png differ
diff --git a/frontend/assets/enemies/enemy.zombie_l5_5_forest.south.png b/frontend/assets/enemies/enemy.zombie_l5_5_forest.south.png
new file mode 100644
index 0000000..5d026cd
Binary files /dev/null and b/frontend/assets/enemies/enemy.zombie_l5_5_forest.south.png differ
diff --git a/frontend/assets/enemies/enemy.zombie_l5_5_ruins.south.png b/frontend/assets/enemies/enemy.zombie_l5_5_ruins.south.png
new file mode 100644
index 0000000..230e4aa
Binary files /dev/null and b/frontend/assets/enemies/enemy.zombie_l5_5_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.zombie_l6_6_canyon.south.png b/frontend/assets/enemies/enemy.zombie_l6_6_canyon.south.png
new file mode 100644
index 0000000..b67d564
Binary files /dev/null and b/frontend/assets/enemies/enemy.zombie_l6_6_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.zombie_l6_6_ruins.south.png b/frontend/assets/enemies/enemy.zombie_l6_6_ruins.south.png
new file mode 100644
index 0000000..b8e2387
Binary files /dev/null and b/frontend/assets/enemies/enemy.zombie_l6_6_ruins.south.png differ
diff --git a/frontend/assets/enemies/enemy.zombie_l7_7_canyon.south.png b/frontend/assets/enemies/enemy.zombie_l7_7_canyon.south.png
new file mode 100644
index 0000000..ec35cfc
Binary files /dev/null and b/frontend/assets/enemies/enemy.zombie_l7_7_canyon.south.png differ
diff --git a/frontend/assets/enemies/enemy.zombie_l7_7_swamp.south.png b/frontend/assets/enemies/enemy.zombie_l7_7_swamp.south.png
new file mode 100644
index 0000000..f8a2725
Binary files /dev/null and b/frontend/assets/enemies/enemy.zombie_l7_7_swamp.south.png differ
diff --git a/frontend/assets/enemies/enemy.zombie_l8_8_astral.south.png b/frontend/assets/enemies/enemy.zombie_l8_8_astral.south.png
new file mode 100644
index 0000000..d29613d
Binary files /dev/null and b/frontend/assets/enemies/enemy.zombie_l8_8_astral.south.png differ
diff --git a/frontend/assets/enemies/enemy.zombie_l8_8_volcanic.south.png b/frontend/assets/enemies/enemy.zombie_l8_8_volcanic.south.png
new file mode 100644
index 0000000..7e39d88
Binary files /dev/null and b/frontend/assets/enemies/enemy.zombie_l8_8_volcanic.south.png differ
diff --git a/frontend/assets/obj/obj.wall.cobble_e.v1.png b/frontend/assets/obj/obj.wall.cobble_e.v1.png
new file mode 100644
index 0000000..d7ce28d
Binary files /dev/null and b/frontend/assets/obj/obj.wall.cobble_e.v1.png differ
diff --git a/frontend/assets/obj/obj.wall.cobble_n.v1.png b/frontend/assets/obj/obj.wall.cobble_n.v1.png
new file mode 100644
index 0000000..1b0aa54
Binary files /dev/null and b/frontend/assets/obj/obj.wall.cobble_n.v1.png differ
diff --git a/frontend/assets/obj/obj.wall.cobble_ne.v1.png b/frontend/assets/obj/obj.wall.cobble_ne.v1.png
new file mode 100644
index 0000000..b23d0ad
Binary files /dev/null and b/frontend/assets/obj/obj.wall.cobble_ne.v1.png differ
diff --git a/frontend/assets/prop/prop.fountain.plaza.v0.png b/frontend/assets/prop/prop.fountain.plaza.v0.png
new file mode 100644
index 0000000..79e11d4
Binary files /dev/null and b/frontend/assets/prop/prop.fountain.plaza.v0.png differ
diff --git a/frontend/assets/prop/prop.market.stall.v0.png b/frontend/assets/prop/prop.market.stall.v0.png
new file mode 100644
index 0000000..2ee559b
Binary files /dev/null and b/frontend/assets/prop/prop.market.stall.v0.png differ
diff --git a/frontend/package.json b/frontend/package.json
index ca901e3..508d713 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -8,7 +8,7 @@
"build": "tsc -b && vite build",
"preview": "vite preview",
"lint": "eslint src --ext .ts,.tsx --max-warnings 0",
- "gen:enemy-south:pixellab": "node scripts/pixellab-enemy-south-batch.mjs"
+ "gen:enemy-south:pixellab": "node ../scripts/pixellab-fill-missing-south-one-shot.mjs --delay-between-creates-sec=10 --post-queue-wait-sec=110 --concurrency=10"
},
"dependencies": {
"pixi.js": "^8.6.6",
diff --git a/frontend/public/assets/game/manifest.json b/frontend/public/assets/game/manifest.json
index 9f5427f..a1f3b35 100644
--- a/frontend/public/assets/game/manifest.json
+++ b/frontend/public/assets/game/manifest.json
@@ -1,5 +1,5 @@
{
- "version": 36,
+ "version": 82,
"assetsRoot": "frontend/assets",
"note": "file paths relative to frontend/assets. Rest camp: prop.camp_tent/fire/bag.v0 (wild rest). Other props + heroes + NPC.",
"textures": {
@@ -293,6 +293,36 @@
"kind": "map_object",
"pixellabObjectId": "84f736b6-12dd-4d94-93e5-d65dad6b2c42"
},
+ "building.house.v2": {
+ "file": "building/building.house.v2.png",
+ "kind": "map_object",
+ "pixellabObjectId": "7f3d48e7-2246-4f00-86e3-1fcbd76e40fe"
+ },
+ "building.tavern.v0": {
+ "file": "building/building.tavern.v0.png",
+ "kind": "map_object",
+ "pixellabObjectId": "270d54a9-f589-4a0d-ab65-02979de68fed"
+ },
+ "building.tavern.v1": {
+ "file": "building/building.tavern.v1.png",
+ "kind": "map_object",
+ "pixellabObjectId": "4c04957a-bee8-4606-9969-9e23fd52984f"
+ },
+ "building.civic.townhall.v0": {
+ "file": "building/building.civic.townhall.v0.png",
+ "kind": "map_object",
+ "pixellabObjectId": "f759c92a-0d07-40fc-9604-a0d4b6f6ea98"
+ },
+ "prop.fountain.plaza.v0": {
+ "file": "prop/prop.fountain.plaza.v0.png",
+ "kind": "map_object",
+ "pixellabObjectId": "1b1eddc0-d238-48d6-8fb9-6ed2fd4cf581"
+ },
+ "prop.market.stall.v0": {
+ "file": "prop/prop.market.stall.v0.png",
+ "kind": "map_object",
+ "pixellabObjectId": "cca3bea4-0caa-49fd-ba70-75c5a2f36cce"
+ },
"enemy.wolf": {
"file": "enemies/enemy.wolf.png",
"kind": "map_object",
@@ -803,102 +833,122 @@
"enemy.orc_l5_6_meadow.south": {
"file": "enemies/enemy.orc_l5_6_meadow.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "b67f475b-6cbc-4797-9cbe-cecf9d13b3d0"
},
"enemy.orc_l5_6_forest.south": {
"file": "enemies/enemy.orc_l5_6_forest.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "971e9081-fd8f-4baf-9af1-c1e497b24bd3"
},
"enemy.orc_l7_8_forest.south": {
"file": "enemies/enemy.orc_l7_8_forest.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "a00b39c7-f595-4ce5-9e6e-32255e891ea8"
},
"enemy.orc_l7_8_ruins.south": {
"file": "enemies/enemy.orc_l7_8_ruins.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "9bbbf755-1207-45ea-aaf8-4d234e3df161"
},
"enemy.orc_l9_10_ruins.south": {
"file": "enemies/enemy.orc_l9_10_ruins.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "9cb7d0f4-a1e0-4ffc-b042-89fd9f893e86"
},
"enemy.orc_l9_10_canyon.south": {
"file": "enemies/enemy.orc_l9_10_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "4d51531f-a408-452d-bffa-e85977803ed1"
},
"enemy.orc_l11_11_canyon.south": {
"file": "enemies/enemy.orc_l11_11_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "b5c70457-a980-40d8-aec3-04fbf9b4c997"
},
"enemy.orc_l11_11_swamp.south": {
"file": "enemies/enemy.orc_l11_11_swamp.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "e9a02115-4e0e-4e1f-9bdf-4b84e44cb1c3"
},
"enemy.orc_l12_12_volcanic.south": {
"file": "enemies/enemy.orc_l12_12_volcanic.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "ad4400eb-5668-4eb1-810b-233cfcfa892d"
},
"enemy.orc_l12_12_astral.south": {
"file": "enemies/enemy.orc_l12_12_astral.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "f5f0e841-45e1-40d2-aee5-ef4c245f3302"
},
"enemy.skeleton_l6_7_meadow.south": {
"file": "enemies/enemy.skeleton_l6_7_meadow.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "8fc3dc27-76be-4ba2-8b92-b93a16e376a3"
},
"enemy.skeleton_l6_7_forest.south": {
"file": "enemies/enemy.skeleton_l6_7_forest.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "11939a86-ae18-488e-85a5-4bc8ed209065"
},
"enemy.skeleton_l8_9_forest.south": {
"file": "enemies/enemy.skeleton_l8_9_forest.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "ee8c4632-419f-47f6-9885-963e5e290e99"
},
"enemy.skeleton_l8_9_ruins.south": {
"file": "enemies/enemy.skeleton_l8_9_ruins.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "d07a0a85-df81-4afb-a365-cc1605bc4cc0"
},
"enemy.skeleton_l10_11_ruins.south": {
"file": "enemies/enemy.skeleton_l10_11_ruins.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "13a0b778-3221-4ee5-a0f9-0bdf2ddc9028"
},
"enemy.skeleton_l10_11_canyon.south": {
"file": "enemies/enemy.skeleton_l10_11_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "eab47bfb-2674-490a-b526-004323c2d40d"
},
"enemy.skeleton_l12_13_canyon.south": {
"file": "enemies/enemy.skeleton_l12_13_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "82352e7d-b422-4136-88d3-ddbf1f353f31"
},
"enemy.skeleton_l12_13_swamp.south": {
"file": "enemies/enemy.skeleton_l12_13_swamp.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "61b35aed-b370-427c-8498-a3bbc029d327"
},
"enemy.skeleton_l14_14_volcanic.south": {
"file": "enemies/enemy.skeleton_l14_14_volcanic.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "20d4b822-dbdf-4fbd-91da-9bd210ceb0cb"
},
"enemy.skeleton_l14_14_astral.south": {
"file": "enemies/enemy.skeleton_l14_14_astral.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "ed536f83-2e26-4c0d-b8b2-b35147e08d56"
},
"enemy.battle_lizard_l7_8_meadow.south": {
"file": "enemies/enemy.battle_lizard_l7_8_meadow.south.png",
@@ -1083,52 +1133,62 @@
"enemy.skeleton_king_l15_17_meadow.south": {
"file": "enemies/enemy.skeleton_king_l15_17_meadow.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "454f2319-1a29-4a34-95ac-75644be8e650"
},
"enemy.skeleton_king_l15_17_forest.south": {
"file": "enemies/enemy.skeleton_king_l15_17_forest.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "8db28b02-31b5-4d54-a34b-81f38522b465"
},
"enemy.skeleton_king_l18_19_forest.south": {
"file": "enemies/enemy.skeleton_king_l18_19_forest.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "fb455528-045e-49f0-9c60-b023f5e40cf9"
},
"enemy.skeleton_king_l18_19_ruins.south": {
"file": "enemies/enemy.skeleton_king_l18_19_ruins.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "e0aa8fd3-af5b-4db8-a697-59e5afc0f874"
},
"enemy.skeleton_king_l20_21_ruins.south": {
"file": "enemies/enemy.skeleton_king_l20_21_ruins.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "d2b0c94f-9f4b-4600-b46b-e636847934b6"
},
"enemy.skeleton_king_l20_21_canyon.south": {
"file": "enemies/enemy.skeleton_king_l20_21_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "6d56d508-3d38-4b3d-9166-9de7cbe806fa"
},
"enemy.skeleton_king_l22_23_canyon.south": {
"file": "enemies/enemy.skeleton_king_l22_23_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "67d25f77-b5fd-4e0f-9122-b9090486a8cc"
},
"enemy.skeleton_king_l22_23_swamp.south": {
"file": "enemies/enemy.skeleton_king_l22_23_swamp.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "db1e82e0-92d3-4952-b5da-a067faf5f482"
},
"enemy.skeleton_king_l24_25_volcanic.south": {
"file": "enemies/enemy.skeleton_king_l24_25_volcanic.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "ba5b3c64-a890-418c-b5d9-7cbb6d390e30"
},
"enemy.skeleton_king_l24_25_astral.south": {
"file": "enemies/enemy.skeleton_king_l24_25_astral.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "b25f2506-82ed-4043-b332-5399f773e0a2"
},
"enemy.forest_warden_l20_22_meadow.south": {
"file": "enemies/enemy.forest_warden_l20_22_meadow.south.png",
@@ -1313,52 +1373,62 @@
"enemy.wraith_l5_6_meadow.south": {
"file": "enemies/enemy.wraith_l5_6_meadow.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "f5a1b06a-2d42-4f99-af6b-1fa3908bdbba"
},
"enemy.wraith_l5_6_forest.south": {
"file": "enemies/enemy.wraith_l5_6_forest.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "d48aefc7-1b6c-474c-bede-5406d55a1e6a"
},
"enemy.wraith_l7_8_forest.south": {
"file": "enemies/enemy.wraith_l7_8_forest.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "84e899b0-333b-412d-ac11-ae5d1feee7a9"
},
"enemy.wraith_l7_8_ruins.south": {
"file": "enemies/enemy.wraith_l7_8_ruins.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "cd0f0091-949b-486d-a485-914a4fcc9681"
},
"enemy.wraith_l9_10_ruins.south": {
"file": "enemies/enemy.wraith_l9_10_ruins.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "dff7f23c-aed9-4389-a6f7-d1859e4e7b79"
},
"enemy.wraith_l9_10_canyon.south": {
"file": "enemies/enemy.wraith_l9_10_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "a50aa266-b41d-4d5d-96ac-855a9df57366"
},
"enemy.wraith_l11_12_canyon.south": {
"file": "enemies/enemy.wraith_l11_12_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "bf93d8f9-6529-4c5c-bf8f-ed8b9384644e"
},
"enemy.wraith_l11_12_swamp.south": {
"file": "enemies/enemy.wraith_l11_12_swamp.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "bc392fcf-12ba-4145-a58a-097884fd88c1"
},
"enemy.wraith_l13_14_volcanic.south": {
"file": "enemies/enemy.wraith_l13_14_volcanic.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "6bd297e8-22c9-43c6-8fca-503142d2e0bf"
},
"enemy.wraith_l13_14_astral.south": {
"file": "enemies/enemy.wraith_l13_14_astral.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "c6b423e9-680d-461c-9635-68e11ab789d0"
},
"enemy.bandit_l4_5_meadow.south": {
"file": "enemies/enemy.bandit_l4_5_meadow.south.png",
@@ -1483,52 +1553,62 @@
"enemy.treant_l18_20_meadow.south": {
"file": "enemies/enemy.treant_l18_20_meadow.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "b308070a-5139-4129-ae50-df69b8c03fa4"
},
"enemy.treant_l18_20_forest.south": {
"file": "enemies/enemy.treant_l18_20_forest.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "9ac4c6d7-44a3-41b2-9186-b1ad304b853f"
},
"enemy.treant_l21_23_forest.south": {
"file": "enemies/enemy.treant_l21_23_forest.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "9bcc26e5-2c16-4049-9208-5b10b1f99328"
},
"enemy.treant_l21_23_ruins.south": {
"file": "enemies/enemy.treant_l21_23_ruins.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "15eac363-fbbc-48fb-9b70-9e910251102c"
},
"enemy.treant_l24_26_ruins.south": {
"file": "enemies/enemy.treant_l24_26_ruins.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "492548d4-b3c2-438a-80fc-811c8166c83e"
},
"enemy.treant_l24_26_canyon.south": {
"file": "enemies/enemy.treant_l24_26_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "20722805-7ac5-4d3f-93d2-5d2225e08a1b"
},
"enemy.treant_l27_28_canyon.south": {
"file": "enemies/enemy.treant_l27_28_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "61a64581-c257-4aa5-8261-c57116852897"
},
"enemy.treant_l27_28_swamp.south": {
"file": "enemies/enemy.treant_l27_28_swamp.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "ec51a64c-3218-49ba-9243-3b85c163ff9b"
},
"enemy.treant_l29_30_volcanic.south": {
"file": "enemies/enemy.treant_l29_30_volcanic.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "e89b7ef1-af39-415d-9ac8-2fc50cf85938"
},
"enemy.treant_l29_30_astral.south": {
"file": "enemies/enemy.treant_l29_30_astral.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "6f238b2e-588b-4edd-84a9-c9efae04e81d"
},
"enemy.basilisk_l9_11_meadow.south": {
"file": "enemies/enemy.basilisk_l9_11_meadow.south.png",
@@ -1593,52 +1673,62 @@
"enemy.wyvern_l12_14_meadow.south": {
"file": "enemies/enemy.wyvern_l12_14_meadow.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "c46f6ff2-b627-4869-8ce7-55de07ac22a0"
},
"enemy.wyvern_l12_14_forest.south": {
"file": "enemies/enemy.wyvern_l12_14_forest.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "a0ea253d-d2fe-4639-8d24-b15b2e92b00f"
},
"enemy.wyvern_l15_17_forest.south": {
"file": "enemies/enemy.wyvern_l15_17_forest.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "0ba2e3fe-600c-468a-a849-9f9c909192a8"
},
"enemy.wyvern_l15_17_ruins.south": {
"file": "enemies/enemy.wyvern_l15_17_ruins.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "00024d15-2ee7-4d29-b37e-08b6cdc581d9"
},
"enemy.wyvern_l18_20_ruins.south": {
"file": "enemies/enemy.wyvern_l18_20_ruins.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "f9f75ce9-9fa4-4ebb-8bb6-2b6ce6e71dbd"
},
"enemy.wyvern_l18_20_canyon.south": {
"file": "enemies/enemy.wyvern_l18_20_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "0981e890-6b50-4b57-95bf-3ee13cbfed89"
},
"enemy.wyvern_l21_22_canyon.south": {
"file": "enemies/enemy.wyvern_l21_22_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "935269c5-cc37-45c5-b6c3-095e9bf4d5cb"
},
"enemy.wyvern_l21_22_swamp.south": {
"file": "enemies/enemy.wyvern_l21_22_swamp.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "dadf2135-4a49-4d9a-ae5c-a0ea1afa73a2"
},
"enemy.wyvern_l23_24_volcanic.south": {
"file": "enemies/enemy.wyvern_l23_24_volcanic.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "c5cdbdb2-1599-4d25-8810-0b6992f637b1"
},
"enemy.wyvern_l23_24_astral.south": {
"file": "enemies/enemy.wyvern_l23_24_astral.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "f446d3f3-ebe3-45e2-9396-ce37e0a4b453"
},
"enemy.harpy_l6_7_meadow.south": {
"file": "enemies/enemy.harpy_l6_7_meadow.south.png",
@@ -1727,82 +1817,98 @@
"enemy.manticore_l20_22_ruins.south": {
"file": "enemies/enemy.manticore_l20_22_ruins.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "29ae1396-2790-41f8-b700-893279862c92"
},
"enemy.manticore_l20_22_canyon.south": {
"file": "enemies/enemy.manticore_l20_22_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "5e09a97a-5af4-4e9c-8603-f26013782e60"
},
"enemy.manticore_l23_24_canyon.south": {
"file": "enemies/enemy.manticore_l23_24_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "da3e608f-1b6e-4ee4-9314-79faec5a2f8c"
},
"enemy.manticore_l23_24_swamp.south": {
"file": "enemies/enemy.manticore_l23_24_swamp.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "e79212e1-a02a-44ca-86ad-6db71654e610"
},
"enemy.manticore_l25_26_volcanic.south": {
"file": "enemies/enemy.manticore_l25_26_volcanic.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "147a970c-67c2-4c89-8230-2684c236055b"
},
"enemy.manticore_l25_26_astral.south": {
"file": "enemies/enemy.manticore_l25_26_astral.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "1fe8ae19-5236-4924-9132-9474b27d7077"
},
"enemy.shade_l10_12_meadow.south": {
"file": "enemies/enemy.shade_l10_12_meadow.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "55cd16f4-16f4-4be1-8db3-16932b6f7592"
},
"enemy.shade_l10_12_forest.south": {
"file": "enemies/enemy.shade_l10_12_forest.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "8c8792d4-5dd3-41a8-97a5-64b500fce5fa"
},
"enemy.shade_l13_15_forest.south": {
"file": "enemies/enemy.shade_l13_15_forest.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "60197732-5382-4460-aed0-ae48588236df"
},
"enemy.shade_l13_15_ruins.south": {
"file": "enemies/enemy.shade_l13_15_ruins.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "d6322a1d-053c-45d4-b08b-247c12bb95af"
},
"enemy.shade_l16_18_ruins.south": {
"file": "enemies/enemy.shade_l16_18_ruins.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "96de793f-1bfd-4886-aaee-9f42a57b0303"
},
"enemy.shade_l16_18_canyon.south": {
"file": "enemies/enemy.shade_l16_18_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "b1f5b523-4ae4-4498-8771-f15e3c6ba11a"
},
"enemy.shade_l19_20_canyon.south": {
"file": "enemies/enemy.shade_l19_20_canyon.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "d7a33c79-2216-491f-950f-80655ef6135c"
},
"enemy.shade_l19_20_swamp.south": {
"file": "enemies/enemy.shade_l19_20_swamp.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "395addb8-1d91-4c54-a213-c9b08ce50aae"
},
"enemy.shade_l21_22_volcanic.south": {
"file": "enemies/enemy.shade_l21_22_volcanic.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "c053841f-6fe2-4466-bf8c-b4fcdd6d97ca"
},
"enemy.shade_l21_22_astral.south": {
"file": "enemies/enemy.shade_l21_22_astral.south.png",
"kind": "map_object",
- "rotation": "south"
+ "rotation": "south",
+ "pixellabObjectId": "c71f839d-a7bd-4685-ba9b-1e9c2837973e"
}
}
}
diff --git a/frontend/src/game/assets/spriteMapping.ts b/frontend/src/game/assets/spriteMapping.ts
index cbc7ba5..bde10bf 100644
--- a/frontend/src/game/assets/spriteMapping.ts
+++ b/frontend/src/game/assets/spriteMapping.ts
@@ -45,15 +45,24 @@ const NPC_TEXTURE_BY_TYPE: Record = {
quest_giver: 'npc.quest_giver',
};
+/** Town hall / civic building offset from plaza (not a `town_buildings` row). */
+export const TOWN_HALL_TEXTURE_KEY = 'building.civic.townhall.v0';
+/** Plaza center feature: DB uses `decoration.well` at (0,0) for the central water feature. */
+export const PLAZA_FOUNTAIN_TEXTURE_KEY = 'prop.fountain.plaza.v0';
+export const MARKET_STALL_TEXTURE_KEY = 'prop.market.stall.v0';
+
const BUILDING_TEXTURE_BY_TYPE: Record = {
- 'house.quest_giver': 'building.house.v1',
- 'house.merchant': 'building.house.v1',
+ 'house.quest_giver': 'building.house.v0',
+ 'house.merchant': 'building.tavern.v0',
'house.armorer': 'building.house.v1',
- 'house.weapon_smith': 'building.house.v1',
- 'house.jeweler': 'building.house.v1',
+ 'house.weapon_smith': 'building.tavern.v1',
+ 'house.jeweler': 'building.house.v2',
'house.bounty_hunter': 'building.house.v1',
- 'house.elder': 'building.house.v1',
- 'house.healer': 'building.house.v1',
+ 'house.elder': 'building.house.v0',
+ 'house.healer': 'building.house.v2',
+ /** Town square feature — rendered as fountain art (see PLAZA_FOUNTAIN_TEXTURE_KEY). */
+ 'decoration.well': PLAZA_FOUNTAIN_TEXTURE_KEY,
+ 'decoration.stall': MARKET_STALL_TEXTURE_KEY,
};
export function terrainToTextureKey(terrain: string): string {
@@ -104,6 +113,7 @@ export function getRequiredSpriteKeys(): string[] {
...objectKeys,
...npcKeys,
...buildingKeys,
+ TOWN_HALL_TEXTURE_KEY,
...HERO_MODEL_VARIANTS.flatMap((v) => [`hero.player.v${v}.south`, `hero.player.v${v}.north`]),
CAMP_TENT_TEXTURE_KEY,
CAMP_FIRE_TEXTURE_KEY,
diff --git a/frontend/src/game/renderer.ts b/frontend/src/game/renderer.ts
index de26b1e..633dd2b 100644
--- a/frontend/src/game/renderer.ts
+++ b/frontend/src/game/renderer.ts
@@ -8,11 +8,14 @@ import { GameSpriteRegistry } from './assets/gameSpriteRegistry';
import {
buildingTypeToTextureKey,
heroTextureKey,
+ MARKET_STALL_TEXTURE_KEY,
npcTypeToTextureKey,
objectToTextureKey,
+ PLAZA_FOUNTAIN_TEXTURE_KEY,
restCampTextureKeys,
enemySouthTextureKey,
terrainToTextureKey,
+ TOWN_HALL_TEXTURE_KEY,
} from './assets/spriteMapping';
/**
@@ -1321,6 +1324,38 @@ export class GameRenderer {
gfx.fill({ color: 0x6a5a3a, alpha: 0.7 });
}
+ /**
+ * Building-layer sprite (anchor bottom-center). Returns true if the texture was placed.
+ */
+ private _placeBuildingLayerSprite(
+ poolKey: string,
+ textureKey: string,
+ bx: number,
+ by: number,
+ targetWidth: number,
+ usedBuildingSprites: Set,
+ ): boolean {
+ if (!this._spritesReady) return false;
+ const spriteTexture = this._spriteRegistry.getTexture(textureKey);
+ if (!spriteTexture) return false;
+ usedBuildingSprites.add(poolKey);
+ const entry = this._ensureSprite(
+ this._buildingSpritePool,
+ poolKey,
+ textureKey,
+ spriteTexture,
+ this._buildingSpriteLayer,
+ );
+ entry.sprite.x = bx;
+ entry.sprite.y = by;
+ const texW = entry.sprite.texture.width || targetWidth;
+ const scaleFactor = texW > 0 ? targetWidth / texW : 1;
+ entry.sprite.scale.set(scaleFactor);
+ entry.sprite.zIndex = by;
+ entry.sprite.visible = true;
+ return true;
+ }
+
/** Draw a market stall structure for town variety. */
private _drawTownStall(gfx: Graphics, cx: number, cy: number, s: number): void {
// Counter / table
@@ -1375,6 +1410,12 @@ export class GameRenderer {
const w = isNpcHouse ? Math.max(baseW * houseSizeBoost, 54 * scale) : baseW;
const h = isNpcHouse ? Math.max(baseH * houseSizeBoost, 42 * scale) : baseH;
const rh = isNpcHouse ? 32 * scale * 1.65 : 32 * scale;
+ const spriteDrawW =
+ bt === 'decoration.well'
+ ? Math.max(w, 56 * scale)
+ : bt === 'decoration.stall'
+ ? Math.max(w, 44 * scale)
+ : w;
const spriteKey = this._spritesReady ? buildingTypeToTextureKey(bt) : null;
const spriteTexture = spriteKey ? this._spriteRegistry.getTexture(spriteKey) : null;
const hasUsableSprite = spriteKey !== null && spriteTexture !== null;
@@ -1390,8 +1431,8 @@ export class GameRenderer {
);
entry.sprite.x = bx;
entry.sprite.y = by;
- const texW = entry.sprite.texture.width || w;
- const scaleFactor = texW > 0 ? w / texW : 1;
+ const texW = entry.sprite.texture.width || spriteDrawW;
+ const scaleFactor = texW > 0 ? spriteDrawW / texW : 1;
entry.sprite.scale.set(scaleFactor);
entry.sprite.zIndex = by;
entry.sprite.visible = true;
@@ -1427,22 +1468,29 @@ export class GameRenderer {
}
}
+ const twIcon = spriteTexture?.width ?? 0;
+ const thIcon = spriteTexture?.height ?? 0;
+ const iconYBase =
+ hasUsableSprite && twIcon > 0
+ ? by - ((thIcon * spriteDrawW) / twIcon) * 0.9
+ : by - h - rh * 0.45;
+
if (bt === 'house.quest_giver') {
- this._drawBuildingIcon(iconGfx, bx, by - h - rh * 0.5, '!', 0xffd700, scale);
+ this._drawBuildingIcon(iconGfx, bx, iconYBase - rh * 0.05, '!', 0xffd700, scale);
} else if (bt === 'house.merchant') {
- this._drawBuildingIcon(iconGfx, bx, by - h - rh * 0.3, '$', 0x88dd88, scale);
+ this._drawBuildingIcon(iconGfx, bx, iconYBase + rh * 0.12, '$', 0x88dd88, scale);
} else if (bt === 'house.armorer') {
- this._drawBuildingIcon(iconGfx, bx, by - h - rh * 0.4, 'A', 0xaaccff, scale);
+ this._drawBuildingIcon(iconGfx, bx, iconYBase, 'A', 0xaaccff, scale);
} else if (bt === 'house.weapon_smith') {
- this._drawBuildingIcon(iconGfx, bx, by - h - rh * 0.35, 'W', 0xffaa66, scale);
+ this._drawBuildingIcon(iconGfx, bx, iconYBase + rh * 0.04, 'W', 0xffaa66, scale);
} else if (bt === 'house.jeweler') {
- this._drawBuildingIcon(iconGfx, bx, by - h - rh * 0.45, 'J', 0xdd88ff, scale);
+ this._drawBuildingIcon(iconGfx, bx, iconYBase - rh * 0.06, 'J', 0xdd88ff, scale);
} else if (bt === 'house.bounty_hunter') {
- this._drawBuildingIcon(iconGfx, bx, by - h - rh * 0.5, 'B', 0xffcc44, scale);
+ this._drawBuildingIcon(iconGfx, bx, iconYBase - rh * 0.05, 'B', 0xffcc44, scale);
} else if (bt === 'house.elder') {
- this._drawBuildingIcon(iconGfx, bx, by - h - rh * 0.5, 'E', 0xeeddaa, scale);
+ this._drawBuildingIcon(iconGfx, bx, iconYBase - rh * 0.05, 'E', 0xeeddaa, scale);
} else if (bt === 'house.healer') {
- this._drawBuildingIcon(iconGfx, bx, by - h - rh * 0.5, '+', 0xff6666, scale);
+ this._drawBuildingIcon(iconGfx, bx, iconYBase - rh * 0.05, '+', 0xff6666, scale);
}
}
}
@@ -1554,6 +1602,7 @@ export class GameRenderer {
private _drawProceduralBuildings(
gfx: Graphics, tx: number, ty: number, s: number,
spread: number, size: string, townSeed: number,
+ usedBuildingSprites: Set, townId: number,
): void {
const houseCount = size === 'XS' ? 5 : size === 'S' ? 7 : size === 'M' ? 10 : 14;
@@ -1596,12 +1645,22 @@ export class GameRenderer {
for (let si = 0; si < stallCount; si++) {
const stallAngle = (si + 0.5) * Math.PI + (townSeed & 0xf) * 0.1;
const stallDist = spread * 0.42;
- this._drawTownStall(
- gfx,
- tx + Math.cos(stallAngle) * stallDist,
- ty + Math.sin(stallAngle) * stallDist * 0.5,
- s * 0.9,
- );
+ const sx = tx + Math.cos(stallAngle) * stallDist;
+ const sy = ty + Math.sin(stallAngle) * stallDist * 0.5;
+ const stallW = 44 * s * 0.9;
+ const poolKey = `town:${townId}:proc_stall:${si}`;
+ if (
+ !this._placeBuildingLayerSprite(
+ poolKey,
+ MARKET_STALL_TEXTURE_KEY,
+ sx,
+ sy,
+ stallW,
+ usedBuildingSprites,
+ )
+ ) {
+ this._drawTownStall(gfx, sx, sy, s * 0.9);
+ }
}
}
@@ -1681,18 +1740,11 @@ export class GameRenderer {
}
}
- // --- Ground plane: tan/brown dirt ellipse ---
+ // --- Ground plane: tan/brown dirt ellipse (no extra transparent radius rings) ---
const groundW = borderRadius * 0.85;
const groundH = groundW * 0.5;
gfx.ellipse(tx, ty, groundW, groundH);
gfx.fill({ color: 0x8a7454, alpha: 0.35 });
- // Inner lighter patch
- gfx.ellipse(tx, ty, groundW * 0.6, groundH * 0.6);
- gfx.fill({ color: 0x9a8462, alpha: 0.2 });
-
- // Glow circle behind town
- gfx.circle(tx, ty, borderRadius * 0.6);
- gfx.fill({ color: 0xdaa520, alpha: 0.04 });
// --- Central plaza (paving); well/fountain + civic sit on or beside it ---
this._drawTownPlaza(gfx, tx, ty, groundW, groundH);
@@ -1706,14 +1758,40 @@ export class GameRenderer {
// --- Buildings: server-driven if available, fallback procedural ---
if (town.buildings && town.buildings.length > 0) {
this._drawServerBuildings(gfx, iconGfx, usedBuildingSprites, town.buildings, tx, ty, s);
- this._drawCivicBuilding(gfx, civicScreen.x, civicScreen.y, s);
} else {
- this._drawProceduralBuildings(gfx, tx, ty, s, spread, town.size, townSeed);
- if ((townSeed & 1) === 0) {
- this._drawTownFountain(gfx, tx, ty, s);
- } else {
- this._drawTownWell(gfx, tx, ty, s);
+ this._drawProceduralBuildings(
+ gfx, tx, ty, s, spread, town.size, townSeed, usedBuildingSprites, town.id,
+ );
+ const plazaKey = `town:${town.id}:plaza:center`;
+ if (
+ !this._placeBuildingLayerSprite(
+ plazaKey,
+ PLAZA_FOUNTAIN_TEXTURE_KEY,
+ tx,
+ ty,
+ 56 * s,
+ usedBuildingSprites,
+ )
+ ) {
+ if ((townSeed & 1) === 0) {
+ this._drawTownFountain(gfx, tx, ty, s);
+ } else {
+ this._drawTownWell(gfx, tx, ty, s);
+ }
}
+ }
+
+ const civicKey = `town:${town.id}:civic`;
+ if (
+ !this._placeBuildingLayerSprite(
+ civicKey,
+ TOWN_HALL_TEXTURE_KEY,
+ civicScreen.x,
+ civicScreen.y,
+ 52 * s,
+ usedBuildingSprites,
+ )
+ ) {
this._drawCivicBuilding(gfx, civicScreen.x, civicScreen.y, s);
}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..c3b4b6c
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,6 @@
+{
+ "name": "AutoHero",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {}
+}
diff --git a/scripts/pixellab-commit-enemy-south-batch.mjs b/scripts/pixellab-commit-enemy-south-batch.mjs
new file mode 100644
index 0000000..d719c08
--- /dev/null
+++ b/scripts/pixellab-commit-enemy-south-batch.mjs
@@ -0,0 +1,53 @@
+/**
+ * After PixelLab MCP returns object IDs, save PNGs and set manifest pixellabObjectId.
+ *
+ * Usage (pairs: slug without enemy. prefix):
+ * node scripts/pixellab-commit-enemy-south-batch.mjs boar_l5_5_canyon boar_l5_5_swamp ...
+ */
+
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const ROOT = path.join(__dirname, '..');
+const MANIFEST = path.join(ROOT, 'frontend/public/assets/game/manifest.json');
+
+async function download(url, dest) {
+ const res = await fetch(url);
+ if (!res.ok) throw new Error(`GET ${url} ${res.status}`);
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
+ fs.writeFileSync(dest, Buffer.from(await res.arrayBuffer()));
+}
+
+async function main() {
+ const args = process.argv.slice(2);
+ if (args.length < 2 || args.length % 2 !== 0) {
+ console.error('Need even number of args: slug objectId ...');
+ process.exit(1);
+ }
+ const manifest = JSON.parse(fs.readFileSync(MANIFEST, 'utf8'));
+ for (let i = 0; i < args.length; i += 2) {
+ const slug = args[i];
+ const objectId = args[i + 1];
+ const key = `enemy.${slug}.south`;
+ const file = `enemies/enemy.${slug}.south.png`;
+ const entry = manifest.textures[key];
+ if (!entry) throw new Error(`No manifest key ${key}`);
+ const url = `https://api.pixellab.ai/mcp/map-objects/${objectId}/download`;
+ const dest = path.join(ROOT, 'frontend/assets', file);
+ await download(url, dest);
+ entry.pixellabObjectId = objectId;
+ entry.file = file;
+ entry.kind = 'map_object';
+ entry.rotation = 'south';
+ console.log('OK', key);
+ }
+ manifest.version = (manifest.version ?? 0) + 1;
+ fs.writeFileSync(MANIFEST, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
+}
+
+main().catch((e) => {
+ console.error(e);
+ process.exit(1);
+});
diff --git a/scripts/pixellab-commit-enemy-south-pairs-file.mjs b/scripts/pixellab-commit-enemy-south-pairs-file.mjs
new file mode 100644
index 0000000..23864aa
--- /dev/null
+++ b/scripts/pixellab-commit-enemy-south-pairs-file.mjs
@@ -0,0 +1,67 @@
+/**
+ * Commit many enemy south sprites from a JSON file (one manifest version bump).
+ *
+ * File format: [ { "slug": "golem_l8_10_forest", "objectId": "uuid" }, ... ]
+ * slug = enemies.type without enemy. prefix or .south suffix
+ *
+ * node scripts/pixellab-commit-enemy-south-pairs-file.mjs path/to/pairs.json
+ */
+
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const ROOT = path.join(__dirname, '..');
+const MANIFEST = path.join(ROOT, 'frontend/public/assets/game/manifest.json');
+
+async function download(url, dest) {
+ const res = await fetch(url);
+ if (!res.ok) throw new Error(`GET ${url} ${res.status}`);
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
+ fs.writeFileSync(dest, Buffer.from(await res.arrayBuffer()));
+}
+
+async function main() {
+ const file = process.argv[2];
+ if (!file) {
+ console.error('Usage: node scripts/pixellab-commit-enemy-south-pairs-file.mjs ');
+ process.exit(1);
+ }
+ const pairs = JSON.parse(fs.readFileSync(file, 'utf8'));
+ if (!Array.isArray(pairs) || pairs.length === 0) {
+ console.error('Expected non-empty JSON array');
+ process.exit(1);
+ }
+ if (pairs.length > 120) {
+ console.error('Max 120 pairs per run');
+ process.exit(1);
+ }
+
+ const manifest = JSON.parse(fs.readFileSync(MANIFEST, 'utf8'));
+ for (const p of pairs) {
+ const slug = p.slug;
+ const objectId = p.objectId;
+ if (!slug || !objectId) throw new Error('Each entry needs slug and objectId');
+ const key = `enemy.${slug}.south`;
+ const fileRel = `enemies/enemy.${slug}.south.png`;
+ const entry = manifest.textures[key];
+ if (!entry) throw new Error(`No manifest key ${key}`);
+ const url = `https://api.pixellab.ai/mcp/map-objects/${objectId}/download`;
+ const dest = path.join(ROOT, 'frontend/assets', fileRel);
+ await download(url, dest);
+ entry.pixellabObjectId = objectId;
+ entry.file = fileRel;
+ entry.kind = 'map_object';
+ entry.rotation = 'south';
+ console.log('OK', key);
+ }
+ manifest.version = (manifest.version ?? 0) + 1;
+ fs.writeFileSync(MANIFEST, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
+ console.log('Wrote manifest version', manifest.version, 'count', pairs.length);
+}
+
+main().catch((e) => {
+ console.error(e);
+ process.exit(1);
+});
diff --git a/scripts/pixellab-download-map-objects.mjs b/scripts/pixellab-download-map-objects.mjs
new file mode 100644
index 0000000..8663812
--- /dev/null
+++ b/scripts/pixellab-download-map-objects.mjs
@@ -0,0 +1,34 @@
+/**
+ * Download PixelLab map objects by id into frontend/assets paths.
+ * Usage: node scripts/pixellab-download-map-objects.mjs rel/path.png ...
+ */
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const ROOT = path.join(__dirname, '..');
+
+async function main() {
+ const args = process.argv.slice(2);
+ if (args.length < 2 || args.length % 2 !== 0) {
+ console.error('Usage: rel/path.png ...');
+ process.exit(1);
+ }
+ for (let i = 0; i < args.length; i += 2) {
+ const rel = args[i];
+ const id = args[i + 1];
+ const url = `https://api.pixellab.ai/mcp/map-objects/${id}/download`;
+ const dest = path.join(ROOT, 'frontend/assets', rel);
+ const res = await fetch(url);
+ if (!res.ok) throw new Error(`${rel} GET ${res.status}`);
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
+ fs.writeFileSync(dest, Buffer.from(await res.arrayBuffer()));
+ console.log('OK', rel);
+ }
+}
+
+main().catch((e) => {
+ console.error(e);
+ process.exit(1);
+});
diff --git a/scripts/pixellab-enemy-south-v2.mjs b/scripts/pixellab-enemy-south-v2.mjs
new file mode 100644
index 0000000..e322703
--- /dev/null
+++ b/scripts/pixellab-enemy-south-v2.mjs
@@ -0,0 +1,197 @@
+/**
+ * Regenerate enemy..south sprites via PixelLab API v2 (POST /map-objects).
+ * Uses local archetype PNG as init_image for style continuity.
+ *
+ * Requires: PIXELLAB_API_TOKEN in the environment (https://api.pixellab.ai/mcp).
+ *
+ * Usage:
+ * node scripts/pixellab-enemy-south-v2.mjs --limit=5
+ * node scripts/pixellab-enemy-south-v2.mjs --slug=boar_l5_5_canyon
+ */
+
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const ROOT = path.join(__dirname, '..');
+const SQL = path.join(ROOT, 'backend/migrations/000006b_enemy_data.sql');
+const MANIFEST = path.join(ROOT, 'frontend/public/assets/game/manifest.json');
+
+const BASE = 'https://api.pixellab.ai/v2';
+
+const ARCHETYPE_REF = {
+ wolf: 'enemy.wolf',
+ boar: 'enemy.boar',
+ zombie: 'enemy.zombie',
+ spider: 'enemy.spider',
+ orc: 'enemy.orc',
+ skeleton: 'enemy.skeleton_archer',
+ battle_lizard: 'enemy.battle_lizard',
+ element: 'enemy.water_element',
+ demon: 'enemy.fire_demon',
+ skeleton_king: 'enemy.skeleton_king',
+ forest_warden: 'enemy.forest_warden',
+ titan: 'enemy.lightning_titan',
+ bandit: 'enemy.orc',
+ cultist: 'enemy.skeleton_archer',
+ golem: 'enemy.ice_guardian',
+ wraith: 'enemy.zombie',
+ treant: 'enemy.forest_warden',
+ basilisk: 'enemy.battle_lizard',
+ wyvern: 'enemy.battle_lizard',
+ harpy: 'enemy.spider',
+ manticore: 'enemy.battle_lizard',
+ shade: 'enemy.zombie',
+};
+
+const BIOME_HINT = {
+ meadow: 'sunlit meadow greens and wildflowers',
+ forest: 'deep forest shadows, moss and bark tones',
+ ruins: 'grey stone dust, cracked masonry palette',
+ canyon: 'red rock, dust, harsh sun',
+ swamp: 'murky teal, bog mist, rotting wood',
+ volcanic: 'ember glow, black rock, lava highlights',
+ astral: 'cosmic violet-blue, star specks, ethereal glow',
+};
+
+function parseArgs() {
+ const a = process.argv.slice(2);
+ let limit = Infinity;
+ let slug = null;
+ for (const x of a) {
+ if (x.startsWith('--limit=')) limit = parseInt(x.slice(8), 10);
+ if (x.startsWith('--slug=')) slug = x.slice(7);
+ }
+ return { limit, slug };
+}
+
+function parseEnemyRows(sqlText) {
+ const re =
+ /VALUES\s*\(\d+,\s*'([^']+)',\s*'([^']+)',\s*'([^']+)',\s*'([^']*)'/g;
+ const rows = [];
+ let m;
+ while ((m = re.exec(sqlText))) {
+ rows.push({ type: m[1], archetype: m[2], biome: m[3], name: m[4] });
+ }
+ return rows;
+}
+
+function buildPrompt(row) {
+ const b = BIOME_HINT[row.biome] ?? row.biome;
+ return (
+ `Pixel RPG combat sprite: ${row.name}. ` +
+ `${row.archetype.replace(/_/g, ' ')} monster, ${b}. ` +
+ `Low top-down view, facing south toward camera, full body, transparent background, ` +
+ `game-ready, single black outline, medium shading — match init image proportions and style.`
+ );
+}
+
+async function pollObject(token, objectId) {
+ const url = `${BASE}/objects/${objectId}`;
+ for (let i = 0; i < 120; i++) {
+ const res = await fetch(url, {
+ headers: { Authorization: `Bearer ${token}` },
+ });
+ const j = await res.json().catch(() => ({}));
+ const data = j.data ?? j;
+ const status = data?.status ?? data?.generation_status;
+ if (status === 'completed' || data?.image_url || data?.download_url) {
+ return data;
+ }
+ if (status === 'failed' || data?.error) {
+ throw new Error(JSON.stringify(data?.error ?? data));
+ }
+ await new Promise((r) => setTimeout(r, 3000));
+ }
+ throw new Error('Timeout waiting for object');
+}
+
+async function downloadMcpObject(objectId, destPath) {
+ const url = `https://api.pixellab.ai/mcp/map-objects/${objectId}/download`;
+ const res = await fetch(url);
+ if (!res.ok) throw new Error(`download ${res.status}`);
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
+ fs.writeFileSync(destPath, Buffer.from(await res.arrayBuffer()));
+}
+
+async function main() {
+ const token = process.env.PIXELLAB_API_TOKEN;
+ if (!token) {
+ console.error('Set PIXELLAB_API_TOKEN');
+ process.exit(1);
+ }
+
+ const { limit, slug } = parseArgs();
+ const sql = fs.readFileSync(SQL, 'utf8');
+ let rows = parseEnemyRows(sql);
+ if (slug) rows = rows.filter((r) => r.type === slug);
+ rows = rows.slice(0, limit);
+
+ const manifest = JSON.parse(fs.readFileSync(MANIFEST, 'utf8'));
+ const textures = manifest.textures;
+ let updated = 0;
+
+ for (const row of rows) {
+ const refKey = ARCHETYPE_REF[row.archetype];
+ const refFile = textures[refKey].file;
+ const refPath = path.join(ROOT, 'frontend/assets', refFile);
+ const b64 = fs.readFileSync(refPath).toString('base64');
+
+ const body = {
+ description: buildPrompt(row),
+ image_size: { width: 96, height: 96 },
+ view: 'low top-down',
+ outline: 'single color outline',
+ shading: 'medium shading',
+ detail: 'medium detail',
+ init_image: { type: 'base64', base64: b64 },
+ init_image_strength: 420,
+ text_guidance_scale: 9,
+ };
+
+ const cr = await fetch(`${BASE}/map-objects`, {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${token}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(body),
+ });
+ const cj = await cr.json().catch(() => ({}));
+ if (!cr.ok) {
+ console.error('Create failed', row.type, cj);
+ continue;
+ }
+ const objectId = cj.data?.object_id ?? cj.object_id ?? cj.data?.id;
+ if (!objectId) {
+ console.error('No object_id', row.type, cj);
+ continue;
+ }
+
+ await pollObject(token, objectId);
+ const southFile = `enemies/enemy.${row.type}.south.png`;
+ const southAbs = path.join(ROOT, 'frontend/assets', southFile);
+ await downloadMcpObject(objectId, southAbs);
+
+ const texKey = `enemy.${row.type}.south`;
+ textures[texKey] = {
+ file: southFile,
+ kind: 'map_object',
+ rotation: 'south',
+ pixellabObjectId: objectId,
+ };
+ updated++;
+ console.log('OK', row.type, objectId);
+ }
+
+ if (updated > 0) {
+ manifest.version = (manifest.version ?? 0) + 1;
+ fs.writeFileSync(MANIFEST, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
+ }
+}
+
+main().catch((e) => {
+ console.error(e);
+ process.exit(1);
+});
diff --git a/scripts/pixellab-fill-missing-south-one-shot.mjs b/scripts/pixellab-fill-missing-south-one-shot.mjs
new file mode 100644
index 0000000..45424db
--- /dev/null
+++ b/scripts/pixellab-fill-missing-south-one-shot.mjs
@@ -0,0 +1,334 @@
+/**
+ * One-shot: fill every manifest enemy..south missing pixellabObjectId (PixelLab API v2).
+ * Requires PIXELLAB_API_TOKEN.
+ *
+ * node scripts/pixellab-fill-missing-south-one-shot.mjs
+ * node scripts/pixellab-fill-missing-south-one-shot.mjs --concurrency=4
+ *
+ * Wave mode (stagger creates, then shared wait, then parallel poll/download):
+ * node scripts/pixellab-fill-missing-south-one-shot.mjs --delay-between-creates-sec=10 --post-queue-wait-sec=110 --concurrency=10
+ */
+
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const ROOT = path.join(__dirname, '..');
+const SQL = path.join(ROOT, 'backend/migrations/000006b_enemy_data.sql');
+const MANIFEST = path.join(ROOT, 'frontend/public/assets/game/manifest.json');
+const BASE = 'https://api.pixellab.ai/v2';
+
+const ARCHETYPE_REF = {
+ wolf: 'enemy.wolf',
+ boar: 'enemy.boar',
+ zombie: 'enemy.zombie',
+ spider: 'enemy.spider',
+ orc: 'enemy.orc',
+ skeleton: 'enemy.skeleton_archer',
+ battle_lizard: 'enemy.battle_lizard',
+ element: 'enemy.water_element',
+ demon: 'enemy.fire_demon',
+ skeleton_king: 'enemy.skeleton_king',
+ forest_warden: 'enemy.forest_warden',
+ titan: 'enemy.lightning_titan',
+ bandit: 'enemy.orc',
+ cultist: 'enemy.skeleton_archer',
+ golem: 'enemy.ice_guardian',
+ wraith: 'enemy.zombie',
+ treant: 'enemy.forest_warden',
+ basilisk: 'enemy.battle_lizard',
+ wyvern: 'enemy.battle_lizard',
+ harpy: 'enemy.spider',
+ manticore: 'enemy.battle_lizard',
+ shade: 'enemy.zombie',
+};
+
+const BIOME_HINT = {
+ meadow: 'sunlit meadow greens and wildflowers',
+ forest: 'deep forest shadows, moss and bark tones',
+ ruins: 'grey stone dust, cracked masonry palette',
+ canyon: 'red rock, dust, harsh sun',
+ swamp: 'murky teal, bog mist, rotting wood',
+ volcanic: 'ember glow, black rock, lava highlights',
+ astral: 'cosmic violet-blue, star specks, ethereal glow',
+};
+
+function parseArgs() {
+ let concurrency = 6;
+ let delayBetweenCreatesSec = 0;
+ let postQueueWaitSec = 0;
+ for (const x of process.argv.slice(2)) {
+ if (x.startsWith('--delay-between-creates-sec=')) {
+ delayBetweenCreatesSec = Math.max(0, parseInt(x.slice(28), 10) || 0);
+ }
+ if (x.startsWith('--post-queue-wait-sec=')) {
+ postQueueWaitSec = Math.max(0, parseInt(x.slice(22), 10) || 0);
+ }
+ }
+ return { concurrency, delayBetweenCreatesSec, postQueueWaitSec };
+}
+
+function sleep(ms) {
+ return new Promise((r) => setTimeout(r, ms));
+}
+
+function parseEnemyRows(sqlText) {
+ const re =
+ /VALUES\s*\(\d+,\s*'([^']+)',\s*'([^']+)',\s*'([^']+)',\s*'([^']*)'/g;
+ const map = new Map();
+ let m;
+ while ((m = re.exec(sqlText))) {
+ map.set(m[1], { type: m[1], archetype: m[2], biome: m[3], name: m[4] });
+ }
+ return map;
+}
+
+function buildPrompt(row) {
+ const b = BIOME_HINT[row.biome] ?? row.biome;
+ return (
+ `Pixel RPG combat sprite: ${row.name}. ` +
+ `${row.archetype.replace(/_/g, ' ')} monster, ${b}. ` +
+ `Low top-down view, facing south toward camera, full body, transparent background, ` +
+ `game-ready, single black outline, medium shading — match init image proportions and style.`
+ );
+}
+
+async function pollObject(token, objectId) {
+ const url = `${BASE}/objects/${objectId}`;
+ for (let i = 0; i < 120; i++) {
+ const res = await fetch(url, {
+ headers: { Authorization: `Bearer ${token}` },
+ });
+ const j = await res.json().catch(() => ({}));
+ const data = j.data ?? j;
+ const status = data?.status ?? data?.generation_status;
+ if (status === 'completed' || data?.image_url || data?.download_url) {
+ return data;
+ }
+ if (status === 'failed' || data?.error) {
+ throw new Error(JSON.stringify(data?.error ?? data));
+ }
+ await sleep(3000);
+ }
+ throw new Error('Timeout waiting for object');
+}
+
+async function downloadMcpObject(objectId, destPath) {
+ const url = `https://api.pixellab.ai/mcp/map-objects/${objectId}/download`;
+ const res = await fetch(url);
+ if (!res.ok) throw new Error(`download ${res.status}`);
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
+ fs.writeFileSync(destPath, Buffer.from(await res.arrayBuffer()));
+}
+
+async function createMapObject(token, body, retries = 4) {
+ for (let a = 0; a < retries; a++) {
+ const cr = await fetch(`${BASE}/map-objects`, {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${token}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(body),
+ });
+ const cj = await cr.json().catch(() => ({}));
+ if (cr.status === 429 && a < retries - 1) {
+ await sleep(15000 * (a + 1));
+ continue;
+ }
+ if (!cr.ok) {
+ throw new Error(`create ${cr.status} ${JSON.stringify(cj)}`);
+ }
+ const objectId = cj.data?.object_id ?? cj.object_id ?? cj.data?.id;
+ if (!objectId) throw new Error(`no object_id ${JSON.stringify(cj)}`);
+ return objectId;
+ }
+}
+
+async function runPool(concurrency, items, fn) {
+ let ix = 0;
+ const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
+ while (true) {
+ const j = ix++;
+ if (j >= items.length) break;
+ await fn(items[j], j);
+ }
+ });
+ await Promise.all(workers);
+}
+
+/** @returns {{ slug: string, body: object } | { error: string }} */
+function prepareCreateBody(texKey, textures, byType) {
+ const slug = texKey.slice('enemy.'.length, -'.south'.length);
+ const row = byType.get(slug);
+ if (!row) return { error: `No SQL row ${slug}` };
+ const refKey = ARCHETYPE_REF[row.archetype];
+ if (!refKey || !textures[refKey]?.file) {
+ return { error: `No archetype ref ${slug} ${row.archetype} ${refKey}` };
+ }
+ const refPath = path.join(ROOT, 'frontend/assets', textures[refKey].file);
+ if (!fs.existsSync(refPath)) {
+ return { error: `Missing ref file ${refPath}` };
+ }
+ const b64 = fs.readFileSync(refPath).toString('base64');
+ const body = {
+ description: buildPrompt(row),
+ image_size: { width: 96, height: 96 },
+ view: 'low top-down',
+ outline: 'single color outline',
+ shading: 'medium shading',
+ detail: 'medium detail',
+ init_image: { type: 'base64', base64: b64 },
+ init_image_strength: 420,
+ text_guidance_scale: 9,
+ };
+ return { slug, body };
+}
+
+async function finalizeSouthEntry(token, textures, texKey, slug, objectId) {
+ await pollObject(token, objectId);
+ const southFile = `enemies/enemy.${slug}.south.png`;
+ const southAbs = path.join(ROOT, 'frontend/assets', southFile);
+ await downloadMcpObject(objectId, southAbs);
+ const entry = textures[texKey];
+ entry.file = southFile;
+ entry.kind = 'map_object';
+ entry.rotation = 'south';
+ entry.pixellabObjectId = objectId;
+}
+
+function loadPixellabTokenFromEnvFiles() {
+ if (process.env.PIXELLAB_API_TOKEN) return;
+ const candidates = [
+ path.join(ROOT, '.env'),
+ path.join(ROOT, 'frontend', '.env.local'),
+ ];
+ for (const p of candidates) {
+ if (!fs.existsSync(p)) continue;
+ const text = fs.readFileSync(p, 'utf8');
+ for (const line of text.split(/\r?\n/)) {
+ const t = line.trim();
+ if (!t || t.startsWith('#')) continue;
+ const m = t.match(/^PIXELLAB_API_TOKEN\s*=\s*(.*)$/);
+ if (!m) continue;
+ let v = m[1].trim();
+ if (
+ (v.startsWith('"') && v.endsWith('"')) ||
+ (v.startsWith("'") && v.endsWith("'"))
+ ) {
+ v = v.slice(1, -1);
+ }
+ process.env.PIXELLAB_API_TOKEN = v;
+ return;
+ }
+ }
+}
+
+async function main() {
+ loadPixellabTokenFromEnvFiles();
+ const token = process.env.PIXELLAB_API_TOKEN;
+ if (!token) {
+ console.error(
+ 'Set PIXELLAB_API_TOKEN in the environment or in repo root .env / frontend/.env.local',
+ );
+ process.exit(1);
+ }
+
+ const { concurrency, delayBetweenCreatesSec, postQueueWaitSec } = parseArgs();
+ const sql = fs.readFileSync(SQL, 'utf8');
+ const byType = parseEnemyRows(sql);
+
+ const manifest = JSON.parse(fs.readFileSync(MANIFEST, 'utf8'));
+ const { textures } = manifest;
+
+ const missing = Object.keys(textures)
+ .filter((k) => k.startsWith('enemy.') && k.endsWith('.south') && !textures[k].pixellabObjectId)
+ .sort();
+
+ if (missing.length === 0) {
+ console.log('Nothing missing.');
+ return;
+ }
+
+ console.log(
+ 'Missing count:',
+ missing.length,
+ 'concurrency:',
+ concurrency,
+ delayBetweenCreatesSec > 0
+ ? `wave: delayBetweenCreatesSec=${delayBetweenCreatesSec} postQueueWaitSec=${postQueueWaitSec}`
+ : '',
+ );
+
+ let ok = 0;
+ let fail = 0;
+
+ if (delayBetweenCreatesSec > 0) {
+ const queued = [];
+ for (let i = 0; i < missing.length; i++) {
+ const texKey = missing[i];
+ const prep = prepareCreateBody(texKey, textures, byType);
+ if ('error' in prep) {
+ console.error('SKIP', texKey, prep.error);
+ fail++;
+ continue;
+ }
+ try {
+ const objectId = await createMapObject(token, prep.body);
+ queued.push({ texKey, slug: prep.slug, objectId });
+ console.log('Queued', prep.slug, objectId, `(${queued.length}/${missing.length})`);
+ } catch (e) {
+ console.error('FAIL create', prep.slug, e.message);
+ fail++;
+ }
+ if (i < missing.length - 1 && delayBetweenCreatesSec > 0) {
+ await sleep(delayBetweenCreatesSec * 1000);
+ }
+ }
+ if (postQueueWaitSec > 0) {
+ console.log(`Post-queue wait ${postQueueWaitSec}s…`);
+ await sleep(postQueueWaitSec * 1000);
+ }
+ await runPool(concurrency, queued, async (job) => {
+ try {
+ await finalizeSouthEntry(token, textures, job.texKey, job.slug, job.objectId);
+ ok++;
+ console.log('OK', job.slug, job.objectId);
+ } catch (e) {
+ console.error('FAIL', job.slug, e.message);
+ fail++;
+ }
+ });
+ } else {
+ await runPool(concurrency, missing, async (texKey) => {
+ const prep = prepareCreateBody(texKey, textures, byType);
+ if ('error' in prep) {
+ console.error('SKIP', texKey, prep.error);
+ fail++;
+ return;
+ }
+ try {
+ const objectId = await createMapObject(token, prep.body);
+ await finalizeSouthEntry(token, textures, texKey, prep.slug, objectId);
+ ok++;
+ console.log('OK', prep.slug, objectId);
+ } catch (e) {
+ console.error('FAIL', prep.slug, e.message);
+ fail++;
+ }
+ });
+ }
+
+ if (ok > 0) {
+ manifest.version = (manifest.version ?? 0) + 1;
+ fs.writeFileSync(MANIFEST, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
+ }
+
+ console.log('Done. OK', ok, 'FAIL', fail, 'manifest.version', manifest.version);
+}
+
+main().catch((e) => {
+ console.error(e);
+ process.exit(1);
+});
diff --git a/scripts/sync-enemy-south-sprites.mjs b/scripts/sync-enemy-south-sprites.mjs
new file mode 100644
index 0000000..5461ce1
--- /dev/null
+++ b/scripts/sync-enemy-south-sprites.mjs
@@ -0,0 +1,129 @@
+/**
+ * Ensures one south-facing enemy PNG per DB template (enemies.type).
+ * 1) Downloads archetype reference sprites from PixelLab MCP URLs (manifest pixellabObjectId).
+ * 2) Copies the right archetype PNG to enemies/enemy..south.png for every row in 000006b_enemy_data.sql.
+ * 3) Merges manifest.json entries for all enemy..south keys.
+ *
+ * For per-slug PixelLab generations (init_image + unique prompt), use pixellab-enemy-south-v2.mjs with PIXELLAB_API_TOKEN.
+ */
+
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const ROOT = path.join(__dirname, '..');
+const SQL = path.join(ROOT, 'backend/migrations/000006b_enemy_data.sql');
+const MANIFEST = path.join(ROOT, 'frontend/public/assets/game/manifest.json');
+const ASSETS_ENEMIES = path.join(ROOT, 'frontend/assets/enemies');
+
+const ARCHETYPE_REF = {
+ wolf: 'enemy.wolf',
+ boar: 'enemy.boar',
+ zombie: 'enemy.zombie',
+ spider: 'enemy.spider',
+ orc: 'enemy.orc',
+ skeleton: 'enemy.skeleton_archer',
+ battle_lizard: 'enemy.battle_lizard',
+ element: 'enemy.water_element',
+ demon: 'enemy.fire_demon',
+ skeleton_king: 'enemy.skeleton_king',
+ forest_warden: 'enemy.forest_warden',
+ titan: 'enemy.lightning_titan',
+ bandit: 'enemy.orc',
+ cultist: 'enemy.skeleton_archer',
+ golem: 'enemy.ice_guardian',
+ wraith: 'enemy.zombie',
+ treant: 'enemy.forest_warden',
+ basilisk: 'enemy.battle_lizard',
+ wyvern: 'enemy.battle_lizard',
+ harpy: 'enemy.spider',
+ manticore: 'enemy.battle_lizard',
+ shade: 'enemy.zombie',
+};
+
+function parseEnemyRows(sqlText) {
+ const re =
+ /VALUES\s*\(\d+,\s*'([^']+)',\s*'([^']+)',\s*'([^']+)',\s*'([^']*)'/g;
+ const rows = [];
+ let m;
+ while ((m = re.exec(sqlText))) {
+ rows.push({ type: m[1], archetype: m[2], biome: m[3], name: m[4] });
+ }
+ return rows;
+}
+
+async function downloadFile(url, destPath) {
+ const res = await fetch(url, { redirect: 'follow' });
+ if (!res.ok) throw new Error(`GET ${url} -> ${res.status}`);
+ const buf = Buffer.from(await res.arrayBuffer());
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
+ fs.writeFileSync(destPath, buf);
+}
+
+async function main() {
+ const sql = fs.readFileSync(SQL, 'utf8');
+ const rows = parseEnemyRows(sql);
+ if (rows.length !== 220) {
+ throw new Error(`Expected 220 enemy rows, got ${rows.length}`);
+ }
+
+ const manifest = JSON.parse(fs.readFileSync(MANIFEST, 'utf8'));
+ const textures = manifest.textures;
+
+ const refKeys = [...new Set(Object.values(ARCHETYPE_REF))];
+ for (const refKey of refKeys) {
+ const entry = textures[refKey];
+ if (!entry?.pixellabObjectId || !entry?.file) {
+ throw new Error(`Manifest missing reference ${refKey}`);
+ }
+ const dest = path.join(ROOT, 'frontend/assets', entry.file);
+ if (fs.existsSync(dest) && fs.statSync(dest).size > 0) continue;
+ const url = `https://api.pixellab.ai/mcp/map-objects/${entry.pixellabObjectId}/download`;
+ console.log('Download', refKey, '->', entry.file);
+ await downloadFile(url, dest);
+ }
+
+ fs.mkdirSync(ASSETS_ENEMIES, { recursive: true });
+
+ for (const row of rows) {
+ const refManifestKey = ARCHETYPE_REF[row.archetype];
+ if (!refManifestKey) {
+ throw new Error(`No ARCHETYPE_REF for archetype ${row.archetype}`);
+ }
+ const refEntry = textures[refManifestKey];
+ const refAbs = path.join(ROOT, 'frontend/assets', refEntry.file);
+ if (!fs.existsSync(refAbs)) throw new Error(`Missing ref file ${refAbs}`);
+
+ const southFile = `enemies/enemy.${row.type}.south.png`;
+ const southAbs = path.join(ROOT, 'frontend/assets', southFile);
+ const texKey = `enemy.${row.type}.south`;
+ const existing = textures[texKey];
+
+ if (existing?.pixellabObjectId) {
+ const url = `https://api.pixellab.ai/mcp/map-objects/${existing.pixellabObjectId}/download`;
+ await downloadFile(url, southAbs);
+ } else {
+ fs.copyFileSync(refAbs, southAbs);
+ }
+
+ textures[texKey] = {
+ file: southFile,
+ kind: 'map_object',
+ rotation: 'south',
+ ...(existing?.pixellabObjectId
+ ? { pixellabObjectId: existing.pixellabObjectId }
+ : {}),
+ };
+ }
+
+ manifest.version = (manifest.version ?? 0) + 1;
+
+ fs.writeFileSync(MANIFEST, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
+ console.log('Wrote manifest version', manifest.version, 'with', Object.keys(textures).filter((k) => k.endsWith('.south') && k.startsWith('enemy.')).length, 'enemy south keys');
+}
+
+main().catch((e) => {
+ console.error(e);
+ process.exit(1);
+});