Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
79b10b3
Update snapmap-plus-guide.md
mefisme Jul 20, 2026
9fd71fb
Merge remote-tracking branch 'upstream/main'
mefisme Jul 22, 2026
176fbfc
Merge branch 'main' of https://github.com/doom-snapmap/snapmap-plus
mefisme Jul 23, 2026
1e527f9
Update gitignore to exclude files
mefisme Jul 23, 2026
cfacf61
Update .gitignore
mefisme Jul 23, 2026
34c7edf
Merge remote-tracking branch 'upstream/main'
mefisme Jul 23, 2026
e5f0724
Merge remote-tracking branch 'upstream/main'
mefisme Jul 23, 2026
067bc94
ui: keyboard paging for list/combo widgets, hide built-in filter enti…
mefisme Jul 27, 2026
65b9c7e
backend: bake the Lift entity into the built-in *Custom palette
mefisme Jul 27, 2026
6d29ece
backend: Ctrl+C copy out of the editor's SWF text fields
mefisme Jul 27, 2026
eda795a
backend: Ctrl+V paste into the editor's SWF text fields
mefisme Jul 27, 2026
7bd3412
swf-textedit: collapse newline runs on paste; document today's work
mefisme Jul 27, 2026
3320152
Fix native 3D-viewport deselect for list-driven selections
mefisme Jul 27, 2026
5b0a262
Guard the selection-state write to idle/selected only
mefisme Jul 27, 2026
8277e1e
docs: record list-assembled group grab and the logic-mode finding
mefisme Jul 27, 2026
000a10d
Refuse selection changes while the editor is holding something
mefisme Jul 27, 2026
967648f
apply_engine: drop the bogus idStr small-string branch
mefisme Jul 28, 2026
905d8be
Fix staged prefab poisoning the paste slot across a Play round-trip
mefisme Jul 28, 2026
f433a60
Fix C unit test link failure: register the tick hook instead of calli…
mefisme Jul 28, 2026
79242f1
Resolve the prefab ctor/populate by signature instead of hardcoded RVA
mefisme Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ dist/
.claude/
.codex/

# Personal local-dev convenience script (not for upstream)
/sync-and-build-webview.ps1
# Personal local-dev convenience scripts (not for upstream)
/testScript.ps1

# wrangler's local cache (account metadata -- never commit)
feedback/.wrangler/
Expand Down
43 changes: 43 additions & 0 deletions docs/backend-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,49 @@ where our own reimplementation was wrong, not the original SnapHak's behavior; a
(or faithful reproduction of) the *original's* behavior belongs in [`fidelity.md`](fidelity.md)
instead. Entries are chronological, newest first.

## 2026-07-28 — a staged prefab poisoned the paste slot and killed vanilla Ctrl+C / Ctrl+V after Play

**Symptom.** Stage a prefab with Load/Place, press Play, come back to the editor — and the game's own
copy and paste were dead. They stayed dead until you pressed Load/Place again, which appeared to "fix"
them. Vanilla copy/paste with no Load/Place involved was completely unaffected, which is what made this
look like an editor-state problem for a long time.

**What was actually happening.** Ctrl+C was not being refused — it was *running and faulting*. The
shield log showed repeated first-chance access violations at `rip+0x1AB32EE` reading `-1`, classified
`in-editor draw fault -> aborted draw, resumed editor frame`. The engine's copy handler calls
`CreatePrefab` on the shared staging slot at `editor+0x209a8`, and `CreatePrefab`'s first act is to tear
down whatever is already in that slot. Our staged prefab does not survive a Play round-trip, so that
teardown walked pointers the map teardown had already freed. Pressing Load/Place "fixed" it only because
`ae_mkcmd_one` re-ctors the slot before staging, which happens to heal it.

This is **pre-existing** and independent of any Load/Place automation work — it reproduces with plain
stage-only behaviour, and would hit anyone who staged a prefab and then played their map.

**Fix.** `sh_apply_prefab_poll_play()` (polled from the per-tick drain) watches the engine `load_state`
global and re-initialises the staging slot on the way **into** Play, while its memory is still intact.
Re-ctor'ing is the right tool: the prefab ctor rewrites every field unconditionally with no reads and no
frees, so it turns dangling pointers into a clean empty prefab — it leaks the already-dead allocations
rather than double-freeing them.

Two guards matter as much as the fix:

- **Ownership.** We only touch the slot when *we* staged it and the entity count is unchanged. An
engine-made Ctrl+C clipboard is left strictly alone — it legitimately survives a Play round-trip and
across maps, and an earlier revision of this fix that cleared the slot unconditionally destroyed the
user's clipboard and disabled paste. Don't do that.
- **Direction.** Cleaning on the way *back* is too late; the engine can touch the dangling slot first.

**Detecting Play is harder than it looks.** Two obvious signals do **not** work and should not be
retried: the editor session going null (`editor+0x204c8`), and the loaded-map object pointer changing.
Both survive a Play round-trip, so neither ever fired. The engine's `load_state` global
(`base+0x6DDE198`; `3` = RUNNING) does move, and per the fault-shield's own notes an in-editor in-place
load never writes it.

**Known limitation.** Our staged prefab still does not survive a Play — we now discard it deliberately
rather than leave a landmine. Press Load/Place again after returning; it re-reads from disk. The durable
fix is to make `ae_deserialize_to_obj` build an object structurally equivalent to what `CreatePrefab`
produces, which is not yet understood.

## 2026-07-13/14 — SnapStack lives in the backend (`snapstack.c` + `json_patch.c`); the `json_patch` empty-`edit` fix; store slots + management commands

**What & why.** The SnapStack subsystem — the stack-of-stacks + named-group stores and all 20 `sh`
Expand Down
9 changes: 7 additions & 2 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ editor (run `sh` in the console if it doesn't auto-open). Full detail: [`webview
| Surface | What it does |
|---|---|
| Window shell | The Win32 host window + the manual 30 Hz think-loop; a menu bar with a persistent light/dark theme toggle (seeded before the page is first shown, so a saved dark theme never flashes light); the always-visible Camera-Origin bar (X/Y/Z track the live editor camera; "Lock Position" pins it). |
| Entities tab | A filterable entity list (multi-select, a persistent Show Hidden toggle, and one persistent selection direction: Follow Selection or Select in 3D; right-click for Copy ID / Delete / Push to stack 0 / Clear stack 0) plus the Entity State panel: classname / inherit / displayname fields and the Decl Text editor — line numbers, syntax coloring, structural lint, advisory schema checks, a distraction-free focus mode. "Save to Decl" commits the edits in memory. |
| Entities tab | A filterable entity list (multi-select, a persistent Show Hidden toggle, and one persistent selection direction: Follow Selection or Select in 3D; right-click for Copy ID / Delete / Push to stack 0 / Clear stack 0) plus the Entity State panel: classname / inherit / displayname fields and the Decl Text editor — line numbers, syntax coloring, structural lint, advisory schema checks, a distraction-free focus mode. "Save to Decl" commits the edits in memory. SnapMap's own built-in filter/droppable helper entities are excluded from the list and from every entity picker (dev-layer-only; a mapper's own filters are unaffected). |
| Keyboard paging | ArrowUp/ArrowDown page the Entities, Timelines and Prefabs lists, and — while their dropdown is open — the Inherit / Classname combos and the Timelines "Runs on" picker. Filtering still works alongside it; text editors and rename fields keep normal caret movement. |
| Native selection parity ("Select in 3D") | Entities selected from the list behave exactly like ones clicked in the 3D view: an empty-space click deselects them, Delete removes all of them, Move works, and the bottom-bar controls apply — single or multi-entity, including switching between entities. Deselecting natively also clears the list highlight. Previously only the explicit Deselect button worked, and Delete/Move misbehaved (Move could soft-lock the game); the cause was that the editor's own mode state was never told a selection existed. |
| Selection guard while holding | Selecting from the Entities list is refused (with an explanatory toast) while you're grabbing an entity or holding a staged prefab. The engine's Escape/cancel path restores a snapshot keyed positionally to the selection that was live when the grab began, so changing the selection first makes Escape swap entity pointers inside the live map — duplicating entities, deleting others outright, and freezing the game. A pre-existing engine bug (reproduces on released builds); only cancellation triggers it, never accept. Placing a *new* palette entity captures no snapshot and is left unrestricted. |
| List-assembled group grab | With a selection pushed from the Entities list, grabbing any one of those entities in the 3D view grabs **all** of them — not previously possible. Practical use: browse a logic chain in the 3D view, add the other entities you want to bring along from the Entities list, then grab the node you're on and move the whole group. You can also hold a grabbed entity or a staged prefab, push a selection from the list, and place the held object without losing the selection. |
| Prefabs tab | Save and load selection prefabs as JSON files under `%LOCALAPPDATA%\snapmap-plus\prefabs\` — one folder level with rename/delete/drag-between-folders; per-prefab description + tags (stored in a `<name>.meta.json` sidecar; the filter box matches tags across folders); "Load / Place" stages the prefab and the user pastes it with Ctrl+V. |
| Timelines tab | The list of timeline entities; opening one edits its events and per-event parameters, with reference/decl/enum parameters constrained to valid choices, entity pickers for entity-typed args, and per-event documentation. |
| Feedback ("?") | The "?" button at the statusbar's right edge opens the Send-feedback dialog: category (bug / feature / incorrect info / other), title, details, optional contact. Sending files it as a labeled issue on this repo's tracker — no GitHub account needed. See the network note below + [`feedback.md`](feedback.md). |
Expand Down Expand Up @@ -154,8 +158,9 @@ Engine detours and resource-loader shadows the backend installs.
|---|---|
| Rawmap load | When rawmaps are on, load the map from `%LOCALAPPDATA%\snapmap-plus\rawmap.json` instead of the engine's own save. |
| Rawmap save | On every editor save, mirror the serialized map JSON to `%LOCALAPPDATA%\snapmap-plus\rawmap.json`. |
| Overrides file-shadow | Three-layer resource resolution: your file under `%LOCALAPPDATA%\snapmap-plus\overrides\` wins when the player-file layer is enabled, then the built-in default decls (served from memory — the "*Custom" tab set; never written to your folder, so they update with each release and deleting your file restores the default), then the game's packaged resource. At startup, `overrides.user_enabled` is captured as an immutable choice for the player-file first layer; a successful `sh_user_overrides 0` or `sh_user_overrides 1` save changes the next launch and requires a DOOM restart. On a save failure, the console reports it and this launch remains unchanged. Built-ins and packaged resources remain available. Install logs an audit of your active overrides and reclaims untouched default copies written by earlier releases. |
| Overrides file-shadow | Three-layer resource resolution: your file under `%LOCALAPPDATA%\snapmap-plus\overrides\` wins when the player-file layer is enabled, then the built-in default decls (served from memory — the "*Custom" tab set: **Timeline**, **Unknown** and **Lift**; never written to your folder, so they update with each release and deleting your file restores the default), then the game's packaged resource. At startup, `overrides.user_enabled` is captured as an immutable choice for the player-file first layer; a successful `sh_user_overrides 0` or `sh_user_overrides 1` save changes the next launch and requires a DOOM restart. On a save failure, the console reports it and this launch remains unchanged. Built-ins and packaged resources remain available. Install logs an audit of your active overrides and reclaims untouched default copies written by earlier releases. |
| Strids injector | Inject custom `#str_` strings from `%LOCALAPPDATA%\snapmap-plus\strings\strids.json` into the engine string table (your key wins over a built-in default for the same id). |
| Text-field clipboard | **Ctrl+C / Ctrl+V in the editor's text fields** — every free-text property (datapad and transmission message bodies) plus the text-backed int / float / vec3 / size inspectors. Copies the selection, or the whole field when nothing is selected; pastes over the selection, or at the caret. Multi-line text pasted into a single-line field collapses each break to a space; datapad bodies keep their newlines. Respects the field's own character cap. Vanilla has no text clipboard at all here (only the dev console accepts a paste) — and it is genuinely absent rather than disabled: the engine's SWF text-edit key handler has no Ctrl branch whatsoever. Entity-level Ctrl+C/Ctrl+V and Ctrl+X are untouched. Exceeds the original. |
| Fault handling | The clone replaces the original's two kill-detours (which terminate DOOM) with the recover-in-place fault-shield (see [`fidelity.md`](fidelity.md)). |
| SuperScript table | Merge the parked `cs_*` SuperScript objects into the engine's eventDef enumerate/lookup/dispatch paths. |
| Math acceleration | An optional SIMD/threading accelerator for engine math; not required for parity (a perf feature, not a user-facing one). |
Expand Down
Loading
Loading