Commit Graph

33 Commits

Author SHA1 Message Date
calic
e3b6e38c5a feat(maps): v0.5.5 — cell_material_slot public getter
Exposes what the v3 vertex/material render path computes internally:
per (layer, x, y), returns {slot, rot, flip} for material cells,
nil for non-material. Override entry wins (normalised via existing
normalize_override_entry); otherwise resolves through
compute_cell_bitmask_v3 + SLOT_LOOKUP.

Primary consumer: debug-overlay tooling (map-editor 0.2.0c.3) that
labels each rendered cell with its blob-14 slot identity for
authoring verification.
2026-05-29 09:56:45 +02:00
calic
b4c0e0f4b5 docs(README): bump header 0.2.0 -> 0.5.4 + add v0.3-0.5.4 API summary
Adds schema-version table, v3 painting model (vertex grid + any-corner
rule + blob-14 SLOT_LOOKUP + override sublayer), and a per-version
summary of APIs added between 0.3.0 and 0.5.4. Original v0.2.0 entries
remain in their existing layout below the new summary.
2026-05-29 09:33:29 +02:00
calic
be6d419061 feat(maps): v0.5.4 — atlas_diffuse_handle + atlas_tile_size_px + atlas_tile_uv
Public accessors for palette-like consumers (map-editor 0.2.0c.2)
that need to render real tile thumbnails. All three return nil when
the atlas-idx is out of range, the slot has no tile in the atlas,
or the texture failed to load (headless test env).

atlas_tile_uv resolves the slot via the same `slot_NN_` regex used
internally by the v3 vertex/material render path, so callers see
the same slot -> tile_id mapping the renderer uses.
2026-05-29 01:37:38 +02:00
calic
9bd4e5a415 fix(maps): v0.5.3 — refresh atlas_by_alias after load_textures
build_map_v3 caches an alias->atlas dict (atlas_by_alias) at map-load
time, pointing at the resolved stubs from load_tilemap. load_textures
later REPLACES m.atlases[i] in-place with the populated record
(diffuse_texture_handle + per-tile UVs from the lib-asset's
tiles.atlas.json) but never updated atlas_by_alias.

Effect: the v3 vertex/material render path resolves
m.atlas_by_alias[layer.material] to the PRE-load stub, finds no
diffuse handle, and short-circuits to the MISSING_ASSET_COLOR
fallback — every material-bound cell renders solid yellow regardless
of the bitmask/slot computation.

Fix: after each m.atlases[atlas_idx] = ... write in load_textures,
also overwrite m.atlas_by_alias[atlas_alias] with the new record.
Guarded on `if m.atlas_by_alias` so v2 maps (no autotile path) are
unaffected.

Found by the editor's freshly-bound work map after 0.2.0b.1 — vagrant
was unaffected by the bug because its hand-authored map was loaded
fine on the very first frame, masking the issue.
2026-05-29 01:27:49 +02:00
calic
714ec57010 feat(maps): v0.5.2 — set_vertex / get_vertex public APIs
Vertex-grid write/read for the autotile painting path. The map-editor
0.2.0a Auto-Tile mode is the primary consumer; painting a single
vertex causes up to 4 surrounding cells to flip to material via the
any-corner rule. Coordinate-checks reject OOB writes; grid is lazily
allocated on first paint.
2026-05-28 23:51:39 +02:00
calic
d0f4d17b65 maps v0.5.1 — opaque-flag + manifest fields + override format
Three small follow-ups bundled into a single version bump:

1. Real tile.opaque consumption (cell_is_opaque_on_layer)
   v0.5.0e shipped a slot-13 heuristic for vertex-painted layers
   because the atlas-baker wasn't computing real opacity. Atlas-baker
   v0.2.0 now sets tile.opaque via alpha-analysis (all-alpha-255
   detection). Both vertex-painted and tiles[] paths now resolve the
   actual slot and read its tile.opaque from atlas metadata. Pre-
   v0.2.0 atlases without the flag report not-opaque (safe-
   conservative: extra draws, no missed cells).

2. Manifest-schema patch (reserved fields, accept-but-ignore)
   Type-checked acceptance of forthcoming manifest fields the
   validator will need before consumers can ship them in real
   manifests without breaking changes:
     map-level:    z_level (int), z_below (string)
     per-layer:    base_color (string #RRGGBB), tint_override (table),
                   collision_policy (table), traversal_modes (table),
                   foundation_mode (array)
   Future slices (lib-core.maps v0.6 multi-z-level, lib-core.actor
   movement-modes, etc.) wire the fields into actual behaviour.

3. Override format extension {slot, rot, flip} (design paper §15.1)
   Override entries now accept EITHER a bare integer slot_id (compact
   canonical-orientation form, backwards-compat with 0.5.0d) OR an
   object `{slot, rot?, flip?}` for explicit-transform placement
   (unblocks map-editor v0.2's Direct-mode Transform controls).

   New normalize_override_entry helper converts disk-form to runtime
   {slot, rot, flip}. Validator (validate_map_table_v3) type-checks
   per-entry. set_override accepts either form and auto-stores as
   compact (bare-int) when rot+flip both 0, object form otherwise —
   minimises disk diff for the common canonical case. get_override
   returns normalized form regardless of how the entry is stored.
   draw_layer + cell_is_opaque_on_layer updated to read the
   normalized form.

Closure-gate: milestone-check.sh ctest + test-all-modules GREEN.
SPOREL_CI=1 vagrant-skeleton rc=0 with 60 render_frame_ok; no
magenta-placeholders, no render-hook errors. Existing override-cells
(vagrant's 3 wall pillars) still render correctly through the new
normalize path; opaque-ceiling cache now reads real atlas data.

Consumer dep-bumps for v0.5.1 land in their own commits per repo:
sporel-lib-core.render, sporel-module-vagrant-skeleton, sporel-
module-map-editor[-test].

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 23:09:24 +02:00
calic
f8994c7ffa maps v0.5.0 — schema-v3 + autotile + override + render-opt
Five sub-slices closed in-session 2026-05-28 against the design at
sporel-meta/docs/design/2026-05-28-autotile-blob-styles-design.md.

0.5.0a — schema-v3 + auto-upgrade
  validate_map_table_v3 + build_map_v3 + serialize_map_v3 +
  upgrade_v2_to_v3 (no-op shape transformation). load() chains
  v1 -> v2 -> v3 transparent. All runtime `m.schema_version`-
  comparisons switched from `== 2`/`~= 2` to `>= 2`/`< 2` so v3
  inherits v2 behaviour for existing code paths. Per-layer optional
  fields (material, vertices, overrides) accepted by validator,
  preserved on round-trip serialize, ignored by renderer until
  0.5.0c/d wire them in.

0.5.0b — multi-layer terrain rendering validated
  Existing LAYER_ORDER_PRE/POST_ENTITIES loop already covered multi-
  layer; sub-slice validated end-to-end against a v3 map with three
  non-empty layers in vagrant-skeleton.

0.5.0c — vertex-painted autotile path
  ~150 LOC autotile section. rotate_bitmask_90cw + mirror_bitmask_h
  + apply_blob_gating helpers. SLOT_LOOKUP[bitmask] -> {slot, rot,
  flip} precomputed at module-init via D4-orbit walk over 14
  canonical patterns. cell_has_material (any-corner rule),
  compute_cell_bitmask_v3 (8-neighbour bitmask + blob-gating),
  atlas_slot_index (slot_NN_* tile-name parser, cached per atlas).
  build_map_v3 exposes atlas_by_alias for O(1) layer.material ->
  atlas lookup. draw_layer branches on layer.vertices+material:
  autotile path computes bitmask, looks up slot, draws with rot+flip
  via draw_sprite_transform. Legacy tiles[] path unchanged. cell_gid
  returns sentinel 1 for vertex-painted material cells so
  is_walkable / blocks_walk / blocks_sight stay transparent.

0.5.0d — sparse override sublayer
  Override map keyed "x:y" -> slot_id (0..13). Override implies
  cell-has-material (restores the 1-cell-isolated case the any-corner
  rule cannot express). Renders canonical slot in default orientation
  (no rot/flip), bypassing bitmask compute. Public APIs:
  set_override / clear_override / get_override with validation
  (v3-only, valid layer, in-bounds, integer 0..13). Internal helpers
  require_v3_layer + check_coords.

0.5.0e — render optimisation
  LAYER_Z + LAYER_ORDER_TOP_DOWN constants. m._layer_has_content
  precomputed in build_map_v3 (per-layer scan: tiles[] non-zero OR
  vertices painted OR overrides set); draw_map_pre/post_entities
  skip empty layers cheaply. m._opaque_ceiling per-cell topmost-
  opaque z-index, lazily built via ensure_opaque_ceiling. Opaque
  heuristic (light): vertex-painted cell opaque iff bitmask resolves
  to slot 13 OR override == slot 13. Legacy tiles[] reads
  atlas.tile.opaque (currently always nil pending atlas-baker E2
  alpha-analysis; safe-conservative default = not opaque). draw_layer
  skips cells where ceiling > current layer z. Writes nil out
  _opaque_ceiling for lazy rebuild.

manifest.lib bumped 0.4.0 -> 0.5.0. Consumer modules bumped in
their own commits (sporel-lib-core.render, sporel-module-vagrant-
skeleton, sporel-module-map-editor[-test]).

Closure-gate per sub-slice: milestone-check.sh build + ctest +
test-all-modules GREEN; SPOREL_CI=1 vagrant-skeleton smoke rc=0
with 60 render_frame_ok and no magenta-placeholders.

Detailed sub-slice closure logs live in
sporel-meta/docs/superpowers/plans/2026-05-28-map-schema-v3.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 20:48:47 +02:00
Axel Meyer
5cf07c8549 Bump lib-core.maps to v0.4.0 and document the write APIs
Three new public functions (set_cell_gid, set_roof, save_to_disk)
documented in the README alongside the existing read API. Purely
additive bump — existing consumers continue to compile and run
unchanged.
2026-05-24 00:51:09 +02:00
Axel Meyer
d80c0ecb5f Add save_to_disk plus hand-rolled JSON serializer
Reverses the internal resolved-atlas representation back to the
v2 on-disk shape (atlas_aliases as strings, only whitelisted layers
emitted, roof key omitted when nil). Hand-rolled JSON stringifier
because the engine exposes no cjson Lua binding and load_json has
no symmetric save_json counterpart. Output is byte-deterministic
(sorted object keys) and pretty-printed with 2-space indent.
2026-05-24 00:43:25 +02:00
Axel Meyer
84d1980c18 Add set_roof write API to lib-core.maps
Allocates the roof array on-demand if the map didn't have one,
validates value in {0, 1}, and bounds-checks the target cell.
2026-05-24 00:34:14 +02:00
Axel Meyer
45b1f93363 Add set_cell_gid write API to lib-core.maps
Validates layer name against VALID_LAYER_NAMES, bounds-checks the
target cell, auto-allocates the layer table if the caller writes
to a previously-empty layer. Marks the map as dirty for callers
that track in-memory mutations.
2026-05-24 00:25:06 +02:00
Axel Meyer
b8705c0ec7 Render magenta placeholder when atlas is unloaded
When a consumer renders via maps.draw_map_pre/post_entities without
having called maps.load_textures (or when asset-alias resolution
failed), the per-atlas diffuse_texture_handle is nil. The previous
draw_layer passed nil to draw_sprite_transform and crashed. Now it
falls back to engine.render.draw_rect with the classic magenta
missing-asset color so the consumer sees the gap visually instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 00:15:00 +02:00
Axel Meyer
e5a350fcb2 Fix load_textures overwriting tile_size with nil for variable-size atlases
When an atlas declares tile_size_px = null (variable-size tiles), the
load_textures call was overwriting the map's tile_size with nil, causing
an arithmetic error in draw_layer on the first render frame.

Guard the tile_size update so it only applies when tile_size_px is
non-nil; maps using variable-size atlases keep their tile_size from
the stub default (32) set at load time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:28:33 +02:00
Axel Meyer
99d00bd115 Rewrite draw_layer for atlas-handle + uv rendering; bump to 0.3.0
draw_layer now samples tile sub-rectangles from the atlas diffuse
texture via the ADR-0044 source-rect render primitive (13-arg
draw_sprite_transform). load_textures gains a walkable field in
tile-records (from atlas JSON) so gameplay queries work after the
tiles-dir is removed. load_tilemap falls back to an atlas-bootstrap
stub when the legacy tilemap JSON is absent, allowing maps.load to
succeed before load_textures is called. Texture loading uses pcall
to degrade gracefully in headless environments. Bumps lib version to
0.3.0 to flag the breaking change in tile-record shape (id is now
integer, name is the stable string identifier).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 23:09:02 +02:00
Axel Meyer
8a75aee48a Rewrite load_textures for paired-PNG atlas format
Replaces the per-tile texture-handle pattern with per-atlas handles
plus per-tile UV-rects. Each map-lib atlas record now holds a tiles-by-
id dictionary loaded from tiles.atlas.json and two texture handles
(diffuse + height) loaded from the paired PNGs. Tile-records expose
{id, name, uv, blocks_sight} fields; the breaking-change to numeric id
+ named name flows to all consumers in this slice.

Adds get_layer_diffuse_texture and get_layer_height_texture that
aggregate to a single handle when every cell in a layer references the
same atlas, returning nil for multi-atlas layers so lighting can fall
back to per-cell sampling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:57:12 +02:00
Axel Meyer
6b2dc22227 Document v0.2.0 API and schema-v2 map format
Update README.md to version 0.2.0: add API entries for the 16 new
public functions (encode_gid, decode_gid, upgrade_v1_to_v2,
validate_map_table_v2, atlas_count, atlas_id_at, has_layer, is_indoor,
cell_gid, tile_at_layer, blocks_walk, blocks_sight,
iterate_layers_pre_entities, iterate_layers_post_entities,
draw_map_pre_entities, draw_map_post_entities), add the schema-v2
reference section documenting layer-stack, packed-u32 GID bit layout,
auto-migration, and a v0.2.0 changelog entry.

Fix tile-id OOB validation regression in build_map_v2: after v1->v2
upgrade, packed GIDs were not checked against atlas palette sizes.
Restore the check in build_map_v2 so loading a map with an out-of-range
tile_id still produces a fatal error with the same message pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:33:27 +02:00
Axel Meyer
caf29b314d Make load_textures iterate atlases for v2 maps
M.load now auto-upgrades v1 maps to v2 shape (no m.tilemap field), but
load_textures still dereferenced m.tilemap unconditionally and crashed on
every v2 consumer. Extract per-tilemap loop into a helper and dispatch by
schema_version.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:18:34 +02:00
Axel Meyer
fffaddc190 Bump version to 0.2.0 for multi-layer schema support
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:13:57 +02:00
Axel Meyer
8cec7b6812 Iterate layers in z-order with entity-slot split; multi-layer draw_map
Adds LAYER_ORDER_PRE/POST_ENTITIES constants, iterate_layers_pre/post_entities
helpers, draw_layer and draw_v1_legacy locals, and draw_map_pre/post_entities
public functions. draw_map becomes a backward-compat wrapper over both.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:54:27 +02:00
Axel Meyer
53c9fe4c0c Add v2 gameplay queries: walkable / blocks_walk / blocks_sight
Replace the v1 is_walkable stub with a v2-aware version that requires
surface tile present and no blocking wall layer cell. Add blocks_walk
(wall-layer OOB-safe check) and blocks_sight (wall + upper_wall with
per-tile blocks_sight override). v1 path unchanged via schema_version
branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:50:59 +02:00
Axel Meyer
15fd68670e Add tile_at_layer and route legacy tile_at to surface layer
Introduces M.tile_at_layer(layer_name, x, y, map_id) for direct per-layer
tile lookup with proper atlas resolution. Refactors the v2 branch of M.tile_at
to delegate to tile_at_layer("surface", ...) instead of duplicating the GID
decode logic inline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:48:07 +02:00
Axel Meyer
880c67fef3 Expose cell_gid query for multi-atlas v2 maps
Adds M.cell_gid(layer_name, x, y, map_id) to read raw GID values from
named layers, enabling callers to decode atlas/tile/rotation from any
layer without going through tile_at().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:43:02 +02:00
Axel Meyer
6af36e9c6e Load schema-v2 maps with multi-layer storage and roof field
Replace the v1-only M.load path with a unified v2 load path that
auto-upgrades v1 JSON on read. Add build_map_v2 to produce map records
with atlases[], layers{}, and roof arrays. Add atlas_count, atlas_id_at,
has_layer, and is_indoor to the public API. Update tile_at to decode GIDs
from the surface layer for v2 maps, keeping M.create/build_map for
programmatic callers that supply a v1-style tilemap_table directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:37:41 +02:00
Axel Meyer
1562ee0068 feat: validate schema-v2 map tables with layer-name whitelist
Adds validate_map_table_v2 — enforces schema_version=2, required
fields (id, size, atlases), non-empty atlases[], per-layer tile-count
check against w*h, and a VALID_LAYER_NAMES whitelist that logs a
warning for unknown layers instead of rejecting them.

Moves require_field before the v2 validator so it is in scope at
call time. engine.warn replaced with engine.print (no warn binding).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:26:48 +02:00
Axel Meyer
0985141fb9 feat: add v1->v2 schema auto-upgrade for map tables
Converts legacy single-tilemap maps (tiles[], tile_rotations[]) into the
schema-v2 format (atlases[], layers.surface with packed GID cells).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:20:32 +02:00
Axel Meyer
446d37fb0e feat: add packed-u32 GID encoding helpers for schema-v2 maps
encode_gid / decode_gid pack atlas_index (8 bits), tile_id (20 bits), and
rotation (2 bits) into a single u32; gid == 0 is reserved as the empty-cell
sentinel. Lua 5.4 native bitwise operators used throughout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:15:34 +02:00
Axel Meyer
2b5acb5caf feat: v0.1.2 sprite-mode in draw_map + tile_rotations + load_textures
Sprite-mode in draw_map:
- tilemap-tiles with texture atlas-id render via
  engine.render.draw_sprite_transform with rotation pivot at tile center
- tiles without texture continue to render via draw_rect with color
  (Phase 1 mode preserved)

Tile rotation:
- maps support an optional tile_rotations parallel array in the map
  JSON with per-cell 90-degree rotation values (0/90/180/270)
- absent or nil entries default to 0 (no rotation)
- enables 9-segment tile reuse via rotation rather than per-orientation
  sprites

New API:
- maps.load_textures(asset_aliases) resolves the current tilemap's
  tile atlas-ids to texture-handles via the asset-lib indirection
  (asset_aliases[tilemap.asset_pack] -> lib-id -> atlas.json lookup
  per tile)

Schema additions:
- tilemap.asset_pack (alias-key, optional)
- tile.texture (atlas-id, optional)
- map.tile_rotations (parallel array, optional)

All existing Phase 1 color-only tilemaps render unchanged.
2026-05-18 03:21:52 +02:00
Axel Meyer
34f8f897c8 chore: add Tier-1 LICENSE file
Adds the proprietary all-rights-reserved license that applies to this
library. The text is identical to the engine LICENSE — this lib is
Tier-1 official platform content, distributed under the same terms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:04:34 +02:00
Axel Meyer
a8606ff621 docs: migrate README to API-Doc-Convention (S2-cascade.libs)
Restructured per ADR-0038: Abstract + bold-list Badges + Topology H2
+ H3-API-Subsections (one per public function) + Conventions/Consumer
pattern/CHANGELOG/References.

Topology auto-populated via Sporel.exe --lint --fix. Pre-commit hook
installed via --install-hooks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:39:42 +02:00
Axel Meyer
db9eb222cb fix(P.3.1): split_namespaced_id replaced with current-module-prefix match; v0.1.1
Pre-existing bug: split_namespaced_id used first-dot-split. For modules
with dotted IDs (e.g. lib-core.maps-test), this misclassified
'<dotted-module-id>.<tilemap-name>' as cross-lib reference.

Replaced with split_local_tilemap() that matches by exact current-
module-id prefix. Module-id can now have arbitrary dots.

Surfaced by P.3.1 reference test-module 'lib-core.maps-test' which
has a dotted module-id.
2026-05-11 02:50:32 +02:00
Axel Meyer
ecc6796b0d fix(audit-M1): tile_at 3-arg form accepts nil map_id with current_map fallback 2026-05-10 15:46:35 +02:00
Axel Meyer
94c2197d46 feat: P.0.lib.maps single-tile-grid implementation (replaces trivial-stub) 2026-05-09 18:13:59 +02:00
Axel Meyer
5cf3d0c2fb feat: P.0.module.skeleton trivial-stub for lib-core.maps 2026-05-09 15:42:39 +02:00