city
Builds the stylized San Francisco map for the Godzilla rampage game and owns its destructible registry. Everything spawns under one root entity named `CITY` (spawned `temporary`, so the map never persists into scene saves — the game rebuilds it each session).
godzilla.city
Builds the stylized San Francisco map for the Godzilla rampage game and owns
its destructible registry. Everything spawns under one root entity named
CITY (spawned temporary, so the map never persists into scene saves —
the game rebuilds it each session).
Map content: bay water west + north, Golden Gate Bridge at x ≈ -300 (two international-orange towers, 10 destructible deck segments, main cables + hangers), downtown tower grid (x 50..350, z -150..150, 41 towers, 30–120 m), Transamerica Pyramid, Coit Tower (on a hill), Alcatraz island + cellhouse, ~150 pastel painted-lady low-rises, asphalt street grid, and 41 dynamic vehicles (35 cars + 6 cable cars).
API
local city = require("godzilla.city")
city.build() -- builds the map under CITY; idempotent; edit mode
-- only; returns the CITY root entity id
city.clear() -- despawns CITY (debris included) + resets registry
city.registry() -- { [rootEntityId] = def }; treat as read-only
city.ownerOf(entityId) -- segment/child entity id -> registry root id (or nil)
city.damage(rootId, amt) -- apply damage; returns DamageResult (nil for
-- unknown root ids)
city.landmarks() -- { { id, name, score, roots } }
city.tick(dt) -- reaps expired debris/dust; the game MUST call
-- this every frame (debris auto-despawns ~20 s,
-- dust ~3 s, with no background tasks)
def = { kind = "building"|"landmark"|"vehicle", name, score, hp, maxHp, segments = { entityId } (bottom-up), landmark?, pos = { x, y, z }, alive = number } (alive = still-standing segment count, maintained by
damage).
DamageResult = { destroyed, score (0 until destroyed), debris = { entityId }, name, landmark? }.
Damage model
- Partial damage knocks the top remaining segment(s) off as short-lived dynamic debris boxes and darkens the surviving segments via Model tint.
- Reaching 0 hp collapses the root: all segments despawn, 3–6 dynamic
debris boxes (
debris_*, grabbable) and a dust burst spawn, and the full score is returned once. - Debris/dust lifetimes are driven purely by
M.tick(dt)— no spawned tasks — so the game owns the frame loop.
Scores
Filler houses 100–300 · downtown towers 500–2500 (by height) · vehicles
50–150 · each bridge deck segment 1500 · each bridge tower 5000 (all bridge
roots share landmark = "golden_gate") · Coit 4000 (coit) · Alcatraz 5000
(alcatraz) · Transamerica Pyramid 8000 (transamerica).
Budget & materials
~560 static entities + 41 dynamic vehicles (limit 1200/60). 15 shared
materials (gz_*), created once via Material.create; dark-glass towers
carry warm/cool emissive window tints for the golden-hour read. Material
assets live at /zero/source/gz_*.material — the engine's material
destination is the source root and names must match ^[A-Za-z][A-Za-z0-9_]*$,
so they are namespaced by the gz_ prefix. ensureMaterials guards each
create with Material.exists, so rebuilds reuse the registered materials.
Notes
- The registry lives in the module instance, not on the entities. After a
hot-reload of this module while CITY stands,
build()adopts the existing CITY but the registry is empty — callclear()thenbuild()to recover (registry()returning an empty table whileCITYexists is the signal). damage()returns nil for an id that is not a registry root.damage()darkens surviving segments viaModel:setTint; debris initial velocity usesPhysics:setVelocity. Both only show motion in play mode.
Scoped to this part · feeds back into the world's score.