Skip to content

Pick a brick up and bring what it was holding - #8

Merged
thebuilder merged 2 commits into
mainfrom
feat/free-build-brick-selection-97f453
Aug 28, 2026
Merged

thebuilder merged 2 commits into
mainfrom
feat/free-build-brick-selection-97f453

Conversation

@thebuilder

Copy link
Copy Markdown
Owner

Taking a brick out of the middle of a free build left everything standing on it hanging in the air. Every placement was independent, so the one thing a person knows about a build, that the roof is on the wall, was the one thing the build did not know.

What a click does now

  • Plain click on a placed brick takes that brick plus everything it alone was holding up. Pull the bottom brick out of a four-brick tower and the tower comes with it, still stacked. Pull one leg out from under a gate and the deck stays, because it is still standing on the other leg.
  • Shift-click takes the entire connected subassembly, reaching down as well as up. Shift-click the top of that tower and all four come; shift-click one leg of the gate and the deck and the far leg come too.
  • A loose brick off the floor is still just a brick.

How connections are worked out

LDraw carries no connectivity data, so subassembly.ts reuses the same trick the snapping uses: A rests on B when the height B would hold A at is the height A is actually at. Only vertical contact counts, which is also what a real brick does, so two bricks side by side share an edge and nothing else and pulling one out leaves the other where it was.

loadBearing walks upward from the clicked part, taking only parts that lose all their support. connectedTo walks both directions.

Carrying a group

Carried now holds a list of parts with offsets from the one under the pointer. The group is merged into a single column profile (mergeProfiles in heightField.ts), so resting, snapping and the "will not fit" check cost what one part costs however many are in hand, rather than every member against everything still built.

Offsets turn as integers, so a gate turned four times is the gate it was. Putting a group down writes ordinary independent placements: a subassembly is a way of moving parts, not something the finished build knows about.

Known limitation, deliberately

A group turns about the upright only, no tip. A pose here is a yaw and a tip, sixteen of the twenty-four ways a part can sit square to the grid, and that set is not closed under tipping a group, so members would land in poses the save format cannot express. The HUD drops the T key when more than one part is in hand, and shows +N attached instead.

Two things that came along

  • Saves taken mid-carry. Anything in hand has already left placements, so a save while somebody is holding something, and one is taken the moment a tab goes to the background, had that hole in it. One brick short of a build is an annoyance; a subassembly short of one is a loss. The save now includes what is being carried at the pose it would land at.
  • FakeRenderer never walked the scene graph, so raycast tests were reading stale world matrices and passing by luck. It now updates them the way the real renderer does, which meant pointing two poured-brick tests at where the brick actually landed rather than at the origin.

Verification

541 tests pass, tsc --noEmit and ultracite check are clean.

Also driven in a real browser against the actual LDraw parts, not just the test fixtures: a four-brick tower (plain click on the bottom lifts all four; shift-click on the top lifts all four) and a gate of two 1x1 legs under a 2x4 deck (plain click on a leg leaves the deck; shift-click on a leg lifts all three; R turns the gate as a unit and it lands back on exact grid coordinates).

Taking a brick out of the middle of a build left everything standing on it
hanging in the air. Every placement was independent, so the one thing a
person knows about a build, that the roof is on the wall, was the one thing
the build did not know.

LDraw carries no connectivity data, so the same trick the snapping uses
works here: a part rests on another when the height that other part would
hold it at is the height it is actually at. Only vertical contact counts,
which is also what a real brick does, so two bricks side by side share an
edge and nothing else and pulling one out leaves the other where it was.

From that one relation come the two ways to pick something up. A plain
click brings the part and whatever it alone was holding, because taking it
out is what would bring that down anyway; a part with another leg still
under it stays. Shift-click brings the whole piece of the build the part
belongs to, reaching down as well as up, for when the thing you want to
move is the turret rather than the brick you clicked on.

Carrying is now a list of parts with offsets from the one under the
pointer, merged into a single column profile so resting, snapping and the
"will not fit" check cost what one part costs however many are in hand.
Offsets turn as integers, so a gate turned four times is the gate it was,
and putting a group down writes ordinary independent placements: a
subassembly is a way of moving parts, not something the build knows about.

A group turns about the upright only. A pose here is a yaw and a tip, which
is sixteen of the twenty-four ways a part can sit square to the grid, and
that set is not closed under tipping a group; the HUD drops the T key when
more than one part is in hand and shows how many came along instead.

Anything in hand has already left the placements, so a save taken while
somebody is holding something, and one is taken the moment a tab goes to
the background, had that hole in it. One brick short of a build is an
annoyance and a subassembly short of one is a loss, so the save now
includes what is being carried at the pose it would land at.

FakeRenderer never walked the scene graph, so raycast tests were reading
stale world matrices and passing by luck. It now updates them the way the
real renderer does, which meant pointing two poured-brick tests at where
the brick actually landed rather than at the origin.
@vercel

vercel Bot commented Aug 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ldraw-builder Ready Ready Preview Aug 28, 2026 8:07am

fallow put fold at cognitive 18, over the threshold, and it earned it:
four nested loops and three guards in one function, where two of the
loops walk the member being folded in and two walk the grid it is being
folded into. Those are separate jobs, so they are now separate functions
and the span a column covers has a name. No behaviour changes.
@thebuilder
thebuilder marked this pull request as ready for review August 28, 2026 08:34
@thebuilder
thebuilder merged commit 8d60e50 into main Aug 28, 2026
3 checks passed

This branch was successfully deployed

1 active deployment
Preview — 2902e4d1 Deployed Aug 28, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant