Bumps the module version to v0.3.1 (lib-core.maps dep pin was already bumped to 0.5.7 in CT6 to allow the editor to load). Updates the file header, startup banner, cheatsheet Tab entry (now mentions all three modes), and appends a Cell-Tile Mode subsection to the README. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
156 lines
6.6 KiB
Markdown
156 lines
6.6 KiB
Markdown
# sporel-module-map-editor
|
||
|
||
Interactive map editor for Sporel `.map.json` files. v0.2 series ships
|
||
the Rev 4 UX architecture (see design paper
|
||
`design/2026-05-28-autotile-blob-styles-design.md` §11 in `sporel-meta`):
|
||
top toolbar with mode-switch + actions, right Layers side-panel, bottom
|
||
palette strip of material slot thumbnails, mouse drag-paint, and two
|
||
mutually-exclusive paint modes (Auto-Tile vs Direct).
|
||
|
||
## Run
|
||
|
||
Default — edit the module's in-place work map (`maps/work.map.json`,
|
||
a 16×16 schema-v3 map bound to the `blob_rect_stone` material):
|
||
|
||
```bash
|
||
bash scripts/run-map-editor.sh
|
||
```
|
||
|
||
With a target — copy a real map in, edit, copy back out:
|
||
|
||
```bash
|
||
bash scripts/run-map-editor.sh ~/Projects/Sporel/sporel-modules/spine-prototype/maps/demo.map.json
|
||
```
|
||
|
||
Or via the install layer (per `install-reference.sh`):
|
||
|
||
```text
|
||
Doubleclick C:/Games/Sporel/bin/Sporel.exe → launcher → map-editor
|
||
```
|
||
|
||
## UI
|
||
|
||
- **Top toolbar** (full-width): `Auto-Tile`/`Direct` mode buttons (mutually
|
||
exclusive, Tab toggles), `Save` (S), `Erase` (E), `Rotate` (R),
|
||
`Flip` (H), `Reset` (0), `Help` (I). Each button shows its hotkey
|
||
label inline. The active mode is highlighted.
|
||
- **Right Layers side-panel** (8 rows top-down: `canopy`, `upper_wall`,
|
||
`wall`, `lower_wall`, `topsurface`, `surface`, `subsurface`,
|
||
`foundation`). Per row: visibility eye + active marker + layer name.
|
||
- **Left-click name** → set active layer
|
||
- **Left-click eye** → toggle layer visibility
|
||
- **Right-click row** → also toggles visibility
|
||
- **Bottom palette strip**: 14 swatches (slots 1-14). Real tile
|
||
thumbnails rendered from the active atlas via
|
||
`maps.atlas_tile_uv`. Active slot has a gold border.
|
||
- **Left-click** → set active slot
|
||
- **Status chip** (bottom-left of palette band): mouse-cell, dirty flag
|
||
(`*` when unsaved), and current transform indicator
|
||
(`slot=N rot=N flip=N`) for Direct-mode awareness.
|
||
- **Auto-Tile-mode world overlay**: cell grid (1 px), vertex dots (4 px
|
||
squares), and a snapped-vertex highlight ring (green) at the nearest
|
||
vertex to the cursor.
|
||
- **Cheatsheet modal** (I): centered dim-overlay listing all hotkeys
|
||
+ LMB/RMB drag semantics.
|
||
|
||
## Modes
|
||
|
||
| Mode | LMB on canvas | RMB on canvas | Notes |
|
||
|---|---|---|---|
|
||
| **Auto-Tile** | Paints vertex at snap target — `maps.set_vertex(layer, vx, vy, true)`. Any-corner rule flips up to 4 surrounding cells to material; renderer derives slot/rot/flip from neighbour bitmask. | Clears vertex — `set_vertex(..., false)` | Transform controls ignored (slot is derived from neighbours). |
|
||
| **Direct** | Writes per-cell override — `maps.set_override(layer, x, y, {slot, rot, flip})`. Bare slot int when canonical (rot=0+flip=0). | Clears override — `maps.clear_override(layer, x, y)` | Honours active Rot/Flip from toolbar. Use to force-place a specific orientation that the auto-derived slot wouldn't pick. |
|
||
|
||
Both modes support **drag-paint**: hold LMB (or RMB) and stroke across
|
||
the canvas to apply the action at every hovered vertex/cell. UI hits
|
||
on the toolbar / side-panel / palette never enter drag-mode.
|
||
|
||
## Hotkeys
|
||
|
||
| Key | Action |
|
||
|---|---|
|
||
| `Tab` | Toggle mode (Auto-Tile ↔ Direct) |
|
||
| `I` | Show / hide cheatsheet |
|
||
| `S` | Save current map to `work.map.json` |
|
||
| `E` | Erase tile at mouse cell |
|
||
| `R` | Cycle rotation 0 → 1 → 2 → 3 → 0 (Direct-mode override) |
|
||
| `H` | Toggle flip 0 ↔ 1 (Direct-mode override) |
|
||
| `0` | Reset transform (rot=0, flip=0) |
|
||
| `Esc` | Quit |
|
||
| `LMB-drag` | Stroke-paint (mode-dependent) |
|
||
| `RMB-drag` | Stroke-clear |
|
||
|
||
## Headless / smoke
|
||
|
||
`ci_frames = 30` in `manifest.module`. The editor reads
|
||
`SPOREL_CI=1` and auto-exits after 30 frames so the smoke harness
|
||
can run it without a window-close click.
|
||
|
||
```bash
|
||
SPOREL_CI=1 ./Sporel.exe --module=map-editor # exits with rc=0 after ~0.5 s
|
||
```
|
||
|
||
## Limitations (current scope)
|
||
|
||
- **Single-atlas palette**: the palette draws from `atlas_idx=0` only.
|
||
Multi-atlas selector is a future slice. Workaround: keep one material
|
||
atlas per map for now.
|
||
- **Single Flip flag**: design paper mentions Flip-H + Flip-V as
|
||
separate controls, but the override format only stores one `flip`
|
||
field; combined with `rot` (0-3) the single flip covers all 8 D4
|
||
orientations, so the second button would be redundant.
|
||
- **No modifier hotkeys**: `lib-core.input` doesn't expose modifier
|
||
combos, so Shift+R for CCW rotation isn't supported. Workaround:
|
||
press R three times.
|
||
- **No Decal / Entity sub-selectors**: Direct mode on non-terrain
|
||
layers is unimplemented (lands in `0.2.0d`).
|
||
- **No Material-Properties modal**: `base_color` re-bake trigger
|
||
+ tint editor are deferred (also `0.2.0d`).
|
||
- **No multi-vertex brush sizes / procedural fill / undo-redo** — see
|
||
design paper §11 "Out of scope" for the full list.
|
||
|
||
## Plan + spec
|
||
|
||
- Design paper: `docs/design/2026-05-28-autotile-blob-styles-design.md`
|
||
§11 in `sporel-meta`
|
||
- Plan: `docs/superpowers/plans/2026-05-28-map-editor-blob-v2.md`
|
||
|
||
## Menu UI (v0.3.0)
|
||
|
||
Top toolbar replaced with a seven-category menu bar (File, Edit, View,
|
||
Map, Tools, Window, Help) plus a sticky two-segment Mode-pill on the
|
||
right end. Item types: action, modal, toggle, submenu, separator;
|
||
items can be conditionally disabled. A single-stack modal framework
|
||
hosts the existing Cheatsheet (I) and three new modals: Save As, Map
|
||
Properties (read-only), About. The Esc key cascades modal -> menu ->
|
||
quit.
|
||
|
||
New Map and Open Map appear in the File menu as disabled placeholders
|
||
pending a v3-aware maps.create and an engine.asset.list_dir in a
|
||
future engine + lib slice.
|
||
|
||
All previous hotkeys (Tab, S, E, R, H, 0, D, G, I, Esc) continue to
|
||
work; their bindings are documented in the items where applicable
|
||
and in the in-editor Cheatsheet modal.
|
||
|
||
## Cell-Tile Mode (v0.3.1)
|
||
|
||
Third painting mechanic alongside Auto-Tile and Direct. LMB paints a
|
||
per-cell material flag at the cursor cell; RMB clears it. The
|
||
renderer uses the classical 47-blob 8-neighbour bitmask for
|
||
cell-tile cells, unlocking the 9 atlas slots that Auto-Tile's
|
||
dual-grid 4-corner rule structurally cannot reach (isolated, end,
|
||
corner_open, straight, tee_open, tee_half, cross_open, cross_q1,
|
||
cross_q2adj).
|
||
|
||
Auto-Tile (vertex-paint, 5 reachable slots, dual-grid look) and
|
||
Direct (manual override placement, all 14 slots via explicit pick)
|
||
are unchanged. Tab cycles through all three modes.
|
||
|
||
For clean visuals, use a single painting mode per layer. Mixing
|
||
Vertex-Tile and Cell-Tile material in the same layer is allowed but
|
||
produces an asymmetric seam at the boundary because vertex-tile
|
||
cells are structurally blind to their cell-tile neighbours.
|
||
|
||
See `sporel-meta/docs/superpowers/specs/2026-06-01-map-editor-cell-tile-mode-design.md`
|
||
for the full design rationale and the Boris-taxonomy background.
|