Rift includes a built-in level editor that mutates the active Tilemap in real time. Open it with E while in-game; the tile picker opens automatically and editor input takes over from gameplay (player movement, dialogue, etc. are suppressed while the editor is active).
Only one sub-mode is active at a time. Each mode is selected by hotkey:
| Key | Mode | Left-Click Action | Right-Click Action |
|---|---|---|---|
| T | Tile Picker | Select tile / multi-tile region | - |
| M | Navigation Edit | - | Toggle walkability (drag) |
| N | NPC Placement | Place / remove NPC | - |
| B | No-Projection Edit | Set no-projection flag (flood) | Clear flag (flood) |
| G | Structure Edit | Anchor + flood assign structure | Clear structure assignment |
| H | Elevation Edit | Paint elevation value | Clear elevation |
| J | Particle Zone Edit | Drag to create zone | Remove zone |
| K | Animation Edit | Apply animation to tile | Remove animation |
| Y | Y-Sort-Plus Edit | Set Y-sort-plus flag | Clear Y-sort-plus flag |
| O | Y-Sort-Minus Edit | Set Y-sort-minus flag | Clear Y-sort-minus flag |
| - | Default | Place selected tile (drag) | Toggle collision (drag) |
Mode-specific notes:
- Tile Picker (T) - Drag to select a multi-tile region; placement stamps the whole region per click.
- Navigation (M) - Drag-paint walkability. Clearing a tile that an NPC is standing on removes that NPC; the snapshot is captured by the undo stack so
Ctrl+Zbrings the NPC back. - NPC Placement (N) - Single-click toggles. Placement only succeeds on walkable (navmesh) tiles. The NPC's dialogue tree is randomly assigned at placement time.
- No-Projection (B) - Single click sets the flag on the current layer. Shift+click flood-fills the connected component. Right-click clears the flag on all 10 layers for the clicked tile (or flood across one layer).
- Structure (G) - Two-step workflow: Ctrl+click for left anchor, Ctrl+click again for right anchor (creates the structure). Then Shift+click flood-fills the connected component, stamping
noProjectionand assigning the new structure id. Right-click clears the structure assignment. - Elevation (H) - Drag-paints the current elevation value. Right-click clears to 0.
- Particle Zone (J) - Drag-release defines a rectangular zone. Right-click removes the zone under the cursor.
- Animation (K) - In the tile picker, click to add frames to the active sequence; press Enter to finalize the animation definition. Then click on map tiles to apply. Right-click removes the animation from a tile.
- Y-Sort-Plus (Y) / Y-Sort-Minus (O) - Per-layer flags. Single click for one tile, Shift+click for flood-fill, right-click to clear.
- Default - Tile placement (left-click drag) and collision toggle (right-click drag).
When the editor is active, a bottom HUD shows the current tool, layer, selected tile, rotation, elevation, selected NPC/particle/structure, cursor tile, active selection size, saved/unsaved state, and valid left/right click actions. Tile names are generated from the tile ID and atlas coordinate because the project does not currently define tile-name metadata.
The unsaved indicator means the map has changed since the last successful save or load. It does not perform a full content-hash comparison.
Every editor mutation is wrapped in an EditorCommand and pushed onto a bounded undo stack (capacity 100, FIFO eviction). Hotkeys:
| Key | Action |
|---|---|
Ctrl+Z |
Undo the most recent action. |
Ctrl+Y |
Redo the most recently undone action. New mutations clear the redo stack. |
A status toast at the bottom of the screen shows the label of the action that was undone/redone (e.g., Undo: Place 12 tile(s)).
What's tracked:
- Tile placement (single, drag-paint, multi-tile region)
- Collision toggle (single, drag-paint)
- Elevation (paint, right-click clear)
- Navigation (drag-paint, with displaced-NPC restore)
- NPC placement / removal
- No-projection / Y-sort-plus / Y-sort-minus flags (single, flood-fill, multi-layer right-click clear)
- Structure: add (anchor placement), remove (right-click clear, captures per-tile reference snapshot)
- Particle zones: add (drag-release), remove (right-click)
- Animation definitions (Enter on collected frames) and per-tile animation apply / remove
- Region paste (Ctrl+V - see below)
What's not tracked:
- Mid-drag mode switches strand the in-progress drag without committing. Tiles already painted during the dropped drag stay (no crash, but no undo entry for them). Press the desired mode key cleanly between drags.
- Loading a map (
rift.save.json) clears the undo stack - captured commands cannot be safely Reverted against a different map.
| Key | Action |
|---|---|
Ctrl+drag |
Define a rectangular tile-region selection on the map. Works in any mode except Structure (which uses Ctrl-click for anchor placement). Press Esc to clear an active selection. |
Ctrl+C |
Copy the selected region into the clipboard. Captures all 10 layers (tile id, rotation, no-projection, structure id, y-sort flags, animation id) plus collision, navigation, and elevation per tile. |
Ctrl+V |
Paste the clipboard at the cursor (top-left of paste). Out-of-bounds tiles are skipped; the paste is undoable as a single command. |
NPCs, particle zones, and structures are not included in the clipboard. NPC copy would require texture re-load and dialogue-tree replication; structures and particle zones use vector-index identity that doesn't transfer cleanly across maps. These are deferred follow-ups; for now, copy-paste covers tile data and per-tile flags only.
| Key | Action |
|---|---|
S |
Save the current map to the defaultMap path from rift.project.json. |
L |
Reload the configured defaultMap. Discards the undo stack and current selection. |
The save file is human-readable JSON; manual edits work but the editor is the supported entry point.
If no manifest is found, the default path remains rift.save.json.
| Key | Action |
|---|---|
1-0 |
Switch to layer 1-10 for tile placement, no-projection, y-sort, etc. |
R |
Rotate the selected tile (or multi-tile selection) by 90 degrees. |
F |
Toggle brush flipX (the upcoming stamp paints mirrored around the vertical axis) AND reflect the active selection along X. Selection target is the Ctrl+drag rectangle if active, else the tile under the cursor on the current layer (toggles per-tile flipX and negates rotation). Reserved for noProjection toggle while in particle-zone mode (J). |
Shift+F |
Toggle brush flipY AND reflect the active selection along Y. Same selection contract as F; toggles per-tile flipY and negates rotation. |
| Arrow keys | Pan the camera (or the tile picker when it's open). |
Shift+arrows |
Fast-pan (2.5x speed). |
Esc |
Cancel the current operation (anchor placement, selection, animation frames). |
Del (drag) |
Delete tiles under the cursor on the current layer. |
Ctrl+scroll |
Zoom (camera, or tile picker when it's open). |
Reflection caveat: when reflecting a region that overlaps a no-projection structure, individual tile content is flipped but the structure's leftAnchor/rightAnchor (world coords on NoProjectionStructure) stay put. This matches R-rotate semantics - reposition the anchors manually if needed.
Editor is decoupled from Game via EditorContext (src/Editor.hpp:45-65), a struct of references built fresh every frame in Game::MakeEditorContext(). Editor never includes Game.hpp and never stores the context - reference members dangle if held across frames. This is why EditorCommand subclasses capture concrete tile coordinates / IDs / values rather than pointers into the context.
UndoRedoStack (src/UndoRedoStack.hpp) holds two deques of std::unique_ptr<EditorCommand>. New commands push to the undo stack and clear the redo stack; capacity overflow drops the oldest entry from the front. Stroke accumulators in src/EditorStrokeAccumulators.hpp batch per-frame mutations during a drag-paint into a single composite command at mouse-up.
\htmlonly
sequenceDiagram
participant Input as "Mouse Drag"
participant Accum as "Stroke Accumulator"
participant Cmd as "Composite Command"
participant Stack as "UndoRedoStack"
participant Tilemap
Input->>Accum: Touch tile/value changes
Input->>Tilemap: Apply live preview edits
Input->>Accum: Mouse released
Accum->>Cmd: Build command from captured before/after state
Cmd->>Stack: Push undo, clear redo
Stack->>Cmd: Undo()
Cmd->>Tilemap: Restore before state
Stack->>Cmd: Redo()
Cmd->>Tilemap: Restore after state
\endhtmlonly