Files
forgotten_caves/notes/07-map-loader.md
T
irrlichtandClaude Opus 5 64a6e1d42d Rewrite notes for the single-player architecture
Drop protocol.md; rewrite the tick-loop note around the game-loop-driven sim
and the intent model; rewrite the roadmap (network items removed, sim/game
crate layout, save/load and NPC blocking added); fix crate paths in the
remaining notes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 23:00:19 +02:00

3.0 KiB
Raw Blame History

Map Loader

Loads level data from a Tiled CSV export, replacing the old hardcoded build_world wall loop. Implemented in sim/src/map.rs; wired into the world in sim/src/lib.rs::load_world.


Format

A bare comma-separated tile grid — Tiled's "CSV" layer export, stripped of the surrounding TMX/XML. No header, no dimensions. The loader derives them from the content:

  • width = number of fields in the first row
  • height = number of rows

The current test map lives at assets/map_test (30×30, tile ids 25 / 74 / 75 / 146). A trailing comma per row is tolerated; ragged rows panic (they indicate a corrupt export).


Tile flip flags — the "negative numbers"

Tiled packs per-tile flip/rotation into the top 3 bits of each 32-bit GID. A flipped tile therefore shows up in the CSV as a large negative decimal when read as i32. Example:

-2147483574  as u32 = 0x8000004A
                       │       └── 0x4A = 74   ← the real tile id
                       └────────── 0x80000000  = horizontal flip
Bit Mask Meaning
31 0x80000000 horizontal flip
30 0x40000000 vertical flip
29 0x20000000 diagonal flip (rot)

The loader parses each field as i64, reinterprets the low 32 bits as u32, and strips the flip flags with & 0x1FFF_FFFF, leaving the bare tile id. Flip orientation is discarded for now — flipped tiles render unflipped. Real flipping would need a flip-aware blit_tile plus the flags carried through the chunk palette; deferred to the camera/sprite pass (09).


Tile id ↔ tileset

Tile ids are used directly:

  • Sim — tile_flags(id) (in map.rs) maps an id to gameplay TileFlags (collidable / opaque) via the sim::tile_collidable vocabulary. Hand-maintained; test values only.
  • Renderer — the id indexes straight into overworld.tga (game.rs). The tileset's tile order is the id space; keep the Tiled tileset and overworld.tga in lockstep.

No firstgid subtraction: the authored ids already line up with overworld.tga.


World assembly

load_world slices the map into 32×32 chunks via Chunk::generate, which builds each chunk's 6-bit palette automatically. The map's top-left tile sits at world (0, 0).

Edge border: a 30×30 map only fills part of chunk (0,0) (which spans tiles 0–31). Tiles inside a loaded chunk but outside the authored map are emitted as an invisible solid border (id 0, COLLIDABLE) so the walkable edge sits flush with the visible map rim rather than two tiles past it at the chunk boundary. Beyond any loaded chunk, movement is blocked anyway by sim.rs's map_or(true) (no chunk = world edge).


Not done yet

  • Multiple layers (only a single tile layer is read)
  • Tile properties from Tiled (collision comes from the hand-maintained tile_flags, not the file)
  • Object layers (spawns, triggers)
  • Tile flipping (orientation discarded — see above)