feat(engine): Tiled P1 — rt_tmap model + GID resolver + render + projection (#69)
The heart of Tiled support: load a map and draw it.
- rt_tmap model (Tmap/TmLayer/TmTileset in tiled.ludic): map header + ordered
layers (dense int32 GID arrays, heap-allocated to w*h, lifting the 96x64 cap)
+ tilesets. Built from the intermediate Value tree, so the TMX and TMJ paths
both feed it.
- GID resolver (Tiled.resolve): gid -> (tileset, localId, flipH/V/D); the three
flip flags masked off before the local-id lookup, returned alongside. gid==0
is empty.
- Image-backed render (Tiled.draw): every visible tile layer in file order,
blitting each tile from its tileset image with flips applied at draw.
- Compatibility projection (Tiled.project / auto on load): a designated
collision layer projects to the legacy byte tilemap ('#' solid, '=' one-way
via the oneway property, ' ' empty) so Grid.*/Path.*/esys_move are unchanged.
- Tiled.load resolves external tilesets + images relative to the map file and
auto-projects a collision/solids/walls layer.
- The grid and physics_tiles demos now run off a loaded map (grid_maze.tmx /
physics_map.tmx) instead of hand-authored Map.row strings, byte-identically.
Two compiler fixes fell out of this (see the changeset):
- emit_index_addr set g_addr_ty before evaluating the index, so slice[obj.field]
came back mis-typed; set it last, like the raw-pointer branches.
- @strcmp was declared by both the world table and the fs prelude; centralise
it in the head prelude so a game that uses Fs/Path links.
Proven by library/tiled_p1.ludic (14 assertions: loads+renders Kenney map
identically from .tmx and .tmj, flip mirroring) + the converted grid/
physics_tiles demos. x test: 92 passed; self-host bootstrap fixpoint intact.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
071c268de7
commit
bc301c8d17
29 changed files with 12932 additions and 9582 deletions
|
|
@ -7,13 +7,16 @@ program Grid {
|
|||
model Marker { Tag }
|
||||
|
||||
handler Boot phase Start {
|
||||
# The maze is now a loaded Tiled map (issue #69): its `collision` layer —
|
||||
# 1 = wall, 0 = open — projects to the same byte tilemap Map.row used to fill,
|
||||
# so every Grid.* / Path.* query below is byte-identical.
|
||||
# 01234567
|
||||
Map.size(8, 5)
|
||||
Map.row(0, "########")
|
||||
Map.row(1, "#......#")
|
||||
Map.row(2, "#.####.#")
|
||||
Map.row(3, "#....#.#")
|
||||
Map.row(4, "########")
|
||||
# row 0 ########
|
||||
# row 1 #......#
|
||||
# row 2 #.####.#
|
||||
# row 3 #....#.#
|
||||
# row 4 ########
|
||||
Tiled.load("assets/tiled-fixtures/grid_maze.tmx")
|
||||
|
||||
# --- Bresenham line ---
|
||||
let hl = Grid.line(1, 1, 6, 1)
|
||||
|
|
|
|||
|
|
@ -35,13 +35,13 @@ program PhysicsTiles {
|
|||
let f_grd = World.field_id(pb, "on_ground")
|
||||
let f_hw = World.field_id(pb, "hit_wall")
|
||||
|
||||
# 16px tiles; '#' (35) is solid, '=' (61) is a one-way platform.
|
||||
Map.size(16, 10)
|
||||
Map.row(4, " === ") # one-way platform, cols 10-12 (y 64)
|
||||
Map.row(5, " # ") # wall column, col 6, rows 5-7
|
||||
Map.row(6, " # ")
|
||||
Map.row(7, " # ")
|
||||
Map.row(8, "################") # solid floor, row 8 (top edge y 128)
|
||||
# 16px tiles; the map is now a loaded Tiled map (issue #69). Its `collision`
|
||||
# layer projects to the same byte tilemap: a non-zero GID becomes '#' (35,
|
||||
# solid), and a GID whose tile carries the bool `oneway` property becomes
|
||||
# '=' (61), a one-way platform — so esys_move sees the identical grid.
|
||||
# one-way platform, cols 10-12 (y 64); wall column, col 6, rows 5-7;
|
||||
# solid floor, row 8 (top edge y 128).
|
||||
Tiled.load("assets/tiled-fixtures/physics_map.tmx")
|
||||
spawn Config { Solids { tile: 16, wall: 35, oneway: 61 } }
|
||||
|
||||
# ---- T1. fall and land on the tile floor -------------------------------
|
||||
|
|
|
|||
100
examples/library/tiled_p1.ludic
Normal file
100
examples/library/tiled_p1.ludic
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# tiled_p1.ludic — the Tiled P1 core (issue #69): the rt_tmap model, the GID
|
||||
# resolver (flip decode), image-backed render, and load-and-draw over the in-repo
|
||||
# Kenney sampleMap. Proves the map loads and renders identically from both the
|
||||
# `.tmx` and its `.tmj` export, and that flipped tiles draw mirrored.
|
||||
#
|
||||
# A game so the runtime (framebuffer + tilemap) is initialised; the Start handler
|
||||
# runs the checks and Update quits. A full run prints:
|
||||
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
||||
program TiledP1 {
|
||||
property Tag { v: int = 0 }
|
||||
model Marker { Tag }
|
||||
|
||||
# a rolling hash of the whole framebuffer (deterministic; equal iff identical).
|
||||
function fb_hash() -> int {
|
||||
var h = 0
|
||||
var y = 0
|
||||
while y < 240 {
|
||||
var x = 0
|
||||
while x < 320 {
|
||||
h = h * 31 + rt_get_px(x, y)
|
||||
x = x + 4
|
||||
}
|
||||
y = y + 4
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
handler Boot phase Start {
|
||||
let m = Tiled.load("assets/kenney/tiny-dungeon/Tiled/sampleMap.tmx")
|
||||
if Tiled.width(m) == 32 { print(1) }
|
||||
if Tiled.height(m) == 20 { print(2) }
|
||||
if Tiled.gid(m, 0, 0, 0) == 14 { print(3) }
|
||||
|
||||
# resolve a flip-flagged GID: 1610612787 = 0x60000033 -> V+D, global tile
|
||||
# 0x33 = 51; firstgid 1 makes the tileset-local id 50; tileset 0
|
||||
let r = Tiled.resolve(m, 1610612787)
|
||||
if r.local == 50 { print(4) }
|
||||
if r.fv == 1 { print(5) }
|
||||
if r.fd == 1 { print(6) }
|
||||
if r.fh == 0 { print(7) }
|
||||
if r.tileset == 0 { print(8) }
|
||||
let e = Tiled.resolve(m, 0)
|
||||
if e.empty == 1 { print(9) }
|
||||
|
||||
# render the .tmx and hash the framebuffer
|
||||
rt_clear(0)
|
||||
Tiled.draw(m, 0, 0)
|
||||
let hx = fb_hash()
|
||||
if hx != 0 { print(10) } # something was drawn
|
||||
|
||||
# render the .tmj of the same map — must be pixel-identical
|
||||
let j = Tiled.load("assets/kenney/tiny-dungeon/Tiled/sampleMap.tmj")
|
||||
rt_clear(0)
|
||||
Tiled.draw(j, 0, 0)
|
||||
if fb_hash() == hx { print(11) }
|
||||
|
||||
# --- flips draw with the correct orientation ---
|
||||
# find a horizontally-asymmetric tile so the mirror test is meaningful
|
||||
var chosen = 0 - 1
|
||||
var t = 0
|
||||
while t < 132 {
|
||||
rt_clear(0)
|
||||
tmap_draw_gid(m, 1 + t, 0, 0)
|
||||
var asym = 0
|
||||
var yy = 0
|
||||
while yy < 16 {
|
||||
var xx = 0
|
||||
while xx < 8 {
|
||||
if rt_get_px(xx, yy) != rt_get_px(15 - xx, yy) { asym = 1 }
|
||||
xx = xx + 1
|
||||
}
|
||||
yy = yy + 1
|
||||
}
|
||||
if asym == 1 and chosen < 0 { chosen = t }
|
||||
t = t + 1
|
||||
}
|
||||
if chosen >= 0 { print(12) }
|
||||
|
||||
# draw the chosen tile normal at x=0 and H-flipped at x=16
|
||||
rt_clear(0)
|
||||
tmap_draw_gid(m, 1 + chosen, 0, 0)
|
||||
tmap_draw_gid(m, (1 + chosen) | (1 << 31), 16, 0)
|
||||
var mirror = 1
|
||||
var diff = 0
|
||||
var yy2 = 0
|
||||
while yy2 < 16 {
|
||||
var xx2 = 0
|
||||
while xx2 < 16 {
|
||||
if rt_get_px(xx2, yy2) != rt_get_px(31 - xx2, yy2) { mirror = 0 } # H-flip mirrors
|
||||
if rt_get_px(xx2, yy2) != rt_get_px(15 - xx2, yy2) { diff = 1 } # the tile isn't symmetric
|
||||
xx2 = xx2 + 1
|
||||
}
|
||||
yy2 = yy2 + 1
|
||||
}
|
||||
if mirror == 1 { print(13) }
|
||||
if diff == 1 { print(14) }
|
||||
}
|
||||
|
||||
handler Run phase Update { quit() }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue