UI:
- Bottom palette strip (above status, left of side-panel): 14
coloured swatches numbered 1-14. Click selects state.active_tile.
- World-space overlay (Auto-Tile mode only): cell grid + vertex
dots + snapped-vertex highlight ring.
- Side-panel + status repositioned to clear the palette band.
- Cheatsheet updated with the new LMB/RMB canvas + palette rows.
Paint:
- Auto-Tile mode: LMB on canvas -> maps.set_vertex(active_layer,
snap.vx, snap.vy, true); RMB -> set_vertex(..., false). Snap is
computed in update() by rounding world coords to the nearest
(W+1)x(H+1) vertex; off-map cursor publishes nil.
- Direct mode: unchanged from 0.2.0a (paint_cell_at with active
slot + rotation).
Notes:
- Palette swatches are placeholder solid colours, not real tile
thumbnails — that needs a tile-UV getter on lib-core.maps, which
is out of scope for this sub-slice.
- Transform controls, Direct-Override transform writes, and the
Decal/Entity sub-selectors land in 0.2.0c/d.
Layout (per design paper §11):
- Top toolbar with Mode buttons (Auto-Tile / Direct), Save, Erase,
Rotate, Help.
- Right Layers side-panel (8 rows, top-down draw order) as single
source of truth for visibility + active layer — replaces the old
left-edge layer chips.
- Bottom status bar.
State + actions:
- mode = "auto-tile" | "direct" (Tab toggles).
- cheatsheet_open modal toggle (I key, dimmed overlay + key/action
table).
- SPOREL_CI=1 auto-exits after 30 frames (matches vagrant pattern;
ci_frames in manifest is not engine-enforced for kind:"module").
Paint behaviour itself is unchanged from 0.1.0 — bottom palette + the
real Auto-Tile / Direct paint loops land in 0.2.0b/c/d.
deps: lib-core.maps 0.5.1 -> 0.5.2 (consumes set_vertex / get_vertex).
Without the camera scope, maps.draw_map() rendered the map at
screen coords starting at (0,0) regardless of camera.set_target,
and screen_to_world's inverse produced bogus cell coords (mouse
in screen-center mapped to top-left tile). Spine-prototype's
render() shows the right pattern: world-space draws inside
camera.begin/finish, screen-space overlays after.
The engine module-lifecycle looks up HOOK_RENDER by the global
name 'render' (engine_module_lifecycle.c:166). The previous draw()
function was a global with no caller, so the editor's UI never
rendered — the engine fell back to its 'no render hook defined'
placeholder.
The engine's Lua sandbox rejects require() for non-declared-dep
module names like 'src.state'. Multi-file modules in this codebase
either inline everything (spine-prototype, vagrant-skeleton) or
use dofile (lib-sporel.launcher). For three modules that share a
state singleton, dofile would cause state divergence. Cleanest
fix is to inline all three into init.lua via IIFE-wrapped local
module tables, preserving the file-internal namespacing while
satisfying the sandbox.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
init.lua loads the work map, binds hotkeys (E/R/S/Esc), tracks
mouse-cell per frame, dispatches clicks to the UI hit-tester
and falls through to paint-cell for unconsumed map-area clicks.
run-map-editor.sh handles copy-in/copy-out for a real target map
or runs in-place against the module's work slot.
Empty launchable module with deps on lib-core.maps 0.4.0, render,
camera, input, and the fa_terrain_v1 asset alias. Renders the
default 4x4 empty work-map; interactive editing wired in
subsequent commits.