feat: modeled market stalls, mega wall ads, traffic lights, awnings

Final detail pass — no gameplay object remains a plain box:

- Markets: box stalls replaced with modeled kit stalls (4 styles seeded),
  hidden box colliders keep the wood acoustics; hand carts at the market
  mouths; standing lanterns between stalls
- Shrines: hanging lanterns on the stone pillars, wooden fences flanking
  the torii approach
- Towers: giant mid-height wall ads on the street faces (the anime-city
  mega-ad look), on top of crown boards and corner columns
- Storefront awnings (kit detail piece) on ~40% of buildings
- Traffic signals at every central-avenue crossing, diagonal from the
  streetlights
- Fantasy Town Kit (CC0) installed at assets/props/town

Verified via street capture (wall-scale glyph ads, pharmacy signs,
banner ads, stalls, carts); movement 11/11, smoke 0, acoustics PASSED.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Nicholas Butzke
2026-07-19 02:50:28 -04:00
co-authored by Claude Fable 5
parent 648872a16d
commit c8a85586bd
15 changed files with 57 additions and 5 deletions
+29 -5
View File
@@ -177,6 +177,10 @@ func _kit_fill_side(front_x: float, dir: int, z0: float, z1: float, seed_id: Str
var h: float = KIT_BUILDINGS[key].y * KIT_SCALE
_kit_building(key, Vector3(front_x, 0, zc), yaw, "K%s_%d" % [seed_id, idx])
var face_yaw := -90.0 if dir > 0 else 90.0 # ad quads face the street
# Storefront awning on some buildings (kit detail piece)
if _rng.randi() % 5 < 2:
_kit_prop_path(KIT_DIR + "detail-awning-wide.glb",
Vector3(front_x, 3.1, zc), face_yaw, minf(w * 0.9, 11.0))
# Street-level posters plastered on the facade (graphic density)
var posters := 1 + _rng.randi() % 3
for p in posters:
@@ -467,6 +471,10 @@ func _tower_block(c: Vector3, bx: int, bz: int) -> void:
_box_static(c + Vector3(0, h + 2.4, 0), Vector3(0.6, 4.0, tw - 4.0), METAL, n + "_Crown", "metal")
_ad_quad(c + Vector3(0.42, h + 2.4, 0), Vector2(tw - 4.6, 3.3), 90.0, _board_tex())
_ad_quad(c + Vector3(-0.42, h + 2.4, 0), Vector2(tw - 4.6, 3.3), -90.0, _board_tex())
# Giant mid-height wall ad on the tower's street faces (ZZZ-style)
var wall_off := dims.z * s * 0.5 + 0.15
_ad_quad(c + Vector3(0, FLOOR_H + dims.y * s * 0.45, -wall_off).rotated(Vector3.UP, deg_to_rad(yaw)),
Vector2(tw * 0.55, tw * 0.8), yaw + 180.0, _poster_tex())
_spawn_points.append(c + Vector3(0, FLOOR_H + 1.5, (BLOCK - 4.0) * 0.4))
@@ -492,14 +500,18 @@ func _shrine_block(c: Vector3, bx: int, bz: int) -> void:
# Hall with slide roof
_box_static(c + Vector3(0, 2.0, -10.0), Vector3(12.0, 4.0, 8.0), WOOD.darkened(0.2), n + "_Hall", "wood")
_ramp_static(c + Vector3(0, 4.9, -8.2), Vector3(13.0, 0.3, 5.6), Vector3(-24, 0, 0), VERMILION.darkened(0.35), n + "_Roof", "wood")
# Stone lanterns + real modeled trees (trunk collider under each)
# Stone + hanging lanterns, wooden gate fence, real modeled trees
for i in 2:
var lx := -8.0 + 16.0 * float(i)
_box_static(c + Vector3(lx, 1.0, 8.0), Vector3(1.0, 2.0, 1.0), Color(0.55, 0.55, 0.52), n + "_Lant%d" % i, "brick")
_kit_prop_path("res://assets/props/town/lantern.glb", c + Vector3(lx, 2.0, 8.0), 0.0, 2.6)
_box_static(c + Vector3(lx, 1.6, -2.0), Vector3(0.8, 3.2, 0.8), TRUNK, n + "_Trunk%d" % i, "wood")
_kit_prop_path("res://assets/props/nature/" + ["tree_detailed.glb", "tree_default.glb"][i % 2],
c + Vector3(lx, 0, -2.0), _rng.randf_range(0.0, 360.0), 7.0)
_kit_prop_path("res://assets/props/nature/tree_oak.glb", c + Vector3(0, 0, 2.0), _rng.randf_range(0.0, 360.0), 6.0)
# Fence gate flanking the torii approach
_kit_prop_path("res://assets/props/town/fence.glb", c + Vector3(-8.0, 0.15, tz - 0.2), 0.0, 3.0)
_kit_prop_path("res://assets/props/town/fence.glb", c + Vector3(8.0, 0.15, tz - 0.2), 0.0, 3.0)
_spawn_points.append(c + Vector3(0, 1.5, 12))
@@ -513,17 +525,26 @@ func _market_block(c: Vector3, bx: int, bz: int) -> void:
for pi in 3:
var pp := c + Vector3(_rng.randf_range(-9.0, 9.0), 0.1, _rng.randf_range(-12.0, 12.0))
_kit_prop(["detail-parasol-a", "detail-parasol-b"][pi % 2], pp, _rng.randf_range(0.0, 360.0), 6.0)
# Stall rows down the middle
# Modeled stall rows down the middle (kit stalls + carts, box colliders)
var stall_keys := ["stall", "stall-green", "stall-red", "stall-bench"]
for r in 2:
var x := c.x + (-5.0 if r == 0 else 5.0)
for s in 4:
var z := c.z - 15.0 + float(s) * 10.0
_box_static(Vector3(x, 1.0, z), Vector3(3.2, 2.0, 2.2), WOOD, "%s_S%d_%d" % [n, r, s], "wood")
_box_static(Vector3(x, 2.35, z), Vector3(4.0, 0.16, 3.0),
SIGN_COLORS[(r + s) % SIGN_COLORS.size()].darkened(0.5), "%s_C%d_%d" % [n, r, s], "wood")
var body := _box_static(Vector3(x, 1.3, z), Vector3(3.4, 2.6, 3.0), WOOD, "%s_S%d_%d" % [n, r, s], "wood")
for mi in body.find_children("*", "MeshInstance3D", false, false):
mi.visible = false # collider only; the kit stall is the visual
_kit_prop_path("res://assets/props/town/%s.glb" % stall_keys[(r * 4 + s + bx) % stall_keys.size()],
Vector3(x, 0.02, z), 90.0 if r == 0 else -90.0, 3.2)
if s % 2 == 0:
_kit_prop_path("res://assets/props/town/lantern.glb",
Vector3(x + 2.2, 0.02, z + 1.8), 0.0, 3.0)
# Lantern string over each row
for l in 6:
_deco_box(Vector3(x, 3.6, c.z - 17.5 + float(l) * 7.0), Vector3(0.32, 0.45, 0.32), Color(1.0, 0.62, 0.25), true)
# Hand carts at the market mouths
_kit_prop_path("res://assets/props/town/cart.glb", c + Vector3(0, 0.02, 19.0), _rng.randf_range(-30, 30), 3.0)
_kit_prop_path("res://assets/props/town/cart-high.glb", c + Vector3(2.0, 0.02, -19.0), _rng.randf_range(150, 210), 3.0)
# Vending pair at the north entry
for v in 2:
_box_static(c + Vector3(-1.5 + 3.0 * float(v), 1.0, -BLOCK * 0.5 + 3.0), Vector3(1.1, 2.0, 0.9),
@@ -700,6 +721,9 @@ func _build_avenues() -> void:
_box_static(Vector3(x, 3.4, z), Vector3(0.26, 6.8, 0.26), METAL, "LampP_%d_%d" % [int(z), int(side)], "metal")
_kit_prop_path("res://assets/props/roads/light-curved.glb",
Vector3(x, 0, z), 90.0 if side > 0 else -90.0, lamp_scale)
# Traffic signal on the opposite diagonal corner of each crossing
_kit_prop_path("res://assets/props/roads/light-square-cross.glb",
Vector3(x, 0, z - side * (ROAD_W * 0.5 + 0.8)), 90.0 if side > 0 else -90.0, lamp_scale * 0.8)
for x in lines:
if absf(x) < 1.0:
continue