Static content system (runtime): sparse statics, server collision, client render #89
Labels
No labels
alpha:wave-0
alpha:wave-1
alpha:wave-2
alpha:wave-3
area:assets
area:combat
area:ecology
area:infra
area:render
area:scripting
area:ui
area:world
enhancement
epic
migration
post-alpha
roadmap
tech-debt
type:bug
type:chore
type:design
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
marco/IsoMmo#89
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of the Alpha epic (pillar 2). Split from the original #89: this is the runtime static-content system — data model + server collision + client render. The authoring tool is #113; the PvP arena (#109) is content authored on top of this; the large world map is post-alpha (#114).
Scope
{ x, y, staticId, blocks }—staticIdnames art in the packstatic/namespace;blocks= does it block movement.content/maps/world.mapor a compact sibling) + a sparse statics file (x y staticIdrows), both committed and copied to server & client output likeworld.maptoday.TileMap, soIsWalkableand the existingHasLineOfSightboth see it — render and collision derive from the SAME data (a wall that draws must block, incl. for ranged LOS #111).GridRenderer.VisibleObstacles/ viewport bounds), real pack art with placeholder fallback, correct iso depth order.staticIdresolves to pack art, coherent flags — fail fast with a clear error, never corrupt the world.Out of scope
Format decision (stay text for now)
Keep the on-disk authoring format textual and diffable — two committed files (dense terrain grid + sparse statics list), read/written by the editor (#113). Rationale: at friends scale nothing forces binary (1M terrain tiles ~1MB text, gzip-friendly; parse is a one-time ~tens of ms; statics are sparse), and a committed binary would kill PR review and merge of map content — the worst option is a hand-committed opaque blob.
Put the loader behind a swappable seam (
TileMap.Load(path)via anIMapSourceabstraction) so the format can change in ONE place without touching game logic. Binary is deferred to the large-world scale (#114) and, if adopted, as a source->compiled build step (author-friendly source committed + ajust build-mapproducing the runtime binary), mirroring the existingassets.isoapipeline — not a hand-committed blob.Definition of Done
Base DoD applies on top.
TileMap/World).Static content system: per-tile static art (houses, walls, …) beyond the single treeto Static content system (runtime): sparse statics, server collision, client renderTechnical design (agreed —
critical-design-review)Verdict: go. No wire/persistence/threading change; fits the existing
TileMap/GridRendererseams; size-agnostic. Owner decisions confirmed:blocksvia aStaticCatalog(Shared, keyed bystaticId) — statics file stays tersex y staticId; a new static kind = one catalog entry +static/<id>art (extend-by-type).world.map): chars →TerrainTypevia a table (.grass,~marsh/water,Sspawn, room for dirt/stone); trees stop being a terrain type and become atreeblocking static inworld.statics.Design
TerrainType[,]grid (enum generalized, each terrain{walkable, artName}); add a sparseIReadOnlyDictionary<GridPosition, StaticTile>(StaticTile = (string StaticId, bool Blocks),BlocksfromStaticCatalog).IsWalkable(p)= terrain-walkable AND no blocking static;BlocksSight(p)= terrain-opaque OR blocking static →HasLineOfSightunchanged. OOB = impassable+opaque.content/maps/— the terrain grid +world.statics(x y staticId). NewTileMap.Load(mapPath, staticsPath)(keepParse(text)for terrain-only unit tests); replace the two inlineFile.ReadAllTextcall sites; add the copy rule to the 3 csprojs (server, client, tests). Load validates: bounds, ≥1 spawn, known terrain chars + known static ids (fail fast).static/<id>→texture dict (placeholder fallback) folded into the depth-sorted_worldSprites(keyed onTileFoot.Y), tree occlusion-fade generalized to tall statics; ground picksland/<terrain>art by type.Invariants Check (CLAUDE.md
## Design checklist)TileMap+statics; statics offline-authored (no client placement, no new spammable intent); move/drop/spawn/LOS reject blocking-static tiles.land/<terrain>; char→terrain becomes a table, lookup by id not switch.static/<id>→ PlaceholderArt; missing statics file → empty; runnable without assets.isoa.No ✗, no HARD GATE tripped.
Risks (mitigated)
#=tree is terrain today → becomes a static: format-breaking forworld.map+TileMapTests; migrate the map (terrain-only +world.staticstrees) and update tests.HasLineOfSightmust consult blocking statics (walls stop arrows #111).Implementation to follow on a branch.
Implementing now on branch feat/static-object-layer, on top of the biomes foundation (#142) — keep Andrea TerrainInfo/biomes, add the sparse static/object layer and move Tree from terrain to a blocking static. Consolidates #139 (Biomes D fixtures object-layer), closed as dup. Server + client become purely flag-based (walkable? blocks?), statics drawn generically by static/.
Delivered in #144 (merged to
main). The PR closed #139 but not this issue — closing now.Runtime static-content system is live: dense terrain grid + sparse statics, server collision (
IsWalkableandHasLineOfSightderive from a static'sblocks), viewport-culled generic client render (static/<id>), size-agnostic. Follow-ups tracked separately: #141 (fold marsh scatter into passable statics) and #143 (express terrain+static defs as a data file).