Skip to content

Record a displacement edit as the brush it edits - #762

Merged
saworbit merged 3 commits into
mainfrom
fix/scoped-displacement-undo
Sep 18, 2026
Merged

saworbit merged 3 commits into
mainfrom
fix/scoped-displacement-undo

Conversation

@saworbit

Copy link
Copy Markdown
Owner

Part of #761. The displacement family, which was the biggest group of commands still recording the whole level on every edit.

What changed:

  • dock._try_undoable_action() was a hand-rolled capture/call/register-a-restore-pair. It now goes through HFUndoHelper.commit_completed() and takes an optional brush scope. Its five displacement callers pass one.
  • The elevation spin and the sew group spin already went through HFUndoHelper.commit() and only lacked the scope argument.
  • The sculpt drag took a whole-level snapshot on mouse-down and another on mouse-up, on a gesture that is held down. It was already holding the brush id it is sculpting. It now records that brush.
  • Painting a material onto a brush now scopes too. Only four commands can claim a brush scope, and the rest never reach the helper that offers it #761 left it open because a paint that wrote the palette could not claim a brush scope. It does not: the paint sets material_override on the node, and materials is unchanged even for a material no palette slot holds. There is a test for exactly that.
  • commit_completed() and register_action() now share one _after_state() for the do side, so the fallback when a scope did not survive the command cannot drift between them.

Sewing keeps the whole snapshot. sew_all() pairs displacements by sew group across the level, so it changes brushes the caller never named. There is a test that pins that too, so a later change cannot quietly scope it.

One bug this found in itself: a stroke's pre-state is a brush scope now, and the Escape-cancels-a-sculpt path was handing it to restore_state(). A scope holds two keys and a level state holds twenty-five, and restore_state() clears what it is not given, so that would have wiped every entity, material and visgroup in the level. The stroke now carries the scope ids beside the state and one function owns which restore matches, used by the cancel, the recovery and the normal end.

Tests: tests/test_scoped_undo_step.gd runs each newly scoped command between two whole-level captures and asserts brushes is the only key that differs, plus a round trip per command, plus the Escape regression in tests/test_plugin_shortcuts.gd.

Still open on #761: bevel and inset are the same hand-rolled shape and are scopeable; the decal, path, polygon, drop and prefab sites change which brushes exist, so most of them cannot scope at all; and the entity scope is still the design question the issue parked.

The displacement commands were the biggest group still taking a whole-level
undo snapshot. The sculpt drag was the worst of them: one snapshot on
mouse-down and another on mouse-up, on a gesture that is held down.

Route dock._try_undoable_action() through HFUndoHelper.commit_completed()
and give it an optional brush scope. The five dock buttons, the elevation
spin, the sew group spin and the sculpt drag all pass one. Painting a
material passes one too: the paint writes material_override on the node
and never reaches the palette.

Sewing keeps the whole snapshot. It pairs displacements by sew group across
the level, so it changes brushes the caller never named.

A stroke's pre-state is a scope now, so the Escape-cancels-a-sculpt path can
no longer hand it to restore_state(), which would read it as a level with no
entities, no materials and no visgroups and clear all three. One function
owns ending a stroke and picks the restore that matches.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@github-actions github-actions Bot added area: build Brushes, drawing, vertex/edge editing (Build tab) area: paint Surface paint, terrain, heightmaps, materials (Paint tab) area: architecture Subsystem structure, decomposition, code quality area: docs README, guides, spec, roadmap, changelog tests The GUT suite and its fixtures labels Sep 18, 2026
@saworbit
saworbit merged commit 1b1e341 into main Sep 18, 2026
9 checks passed
@saworbit
saworbit deleted the fix/scoped-displacement-undo branch September 18, 2026 12:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: architecture Subsystem structure, decomposition, code quality area: build Brushes, drawing, vertex/edge editing (Build tab) area: docs README, guides, spec, roadmap, changelog area: paint Surface paint, terrain, heightmaps, materials (Paint tab) tests The GUT suite and its fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant