Skip to content

fix(map): overlap between containers is resolved, not negotiated - #585

Merged
angel-manuel merged 3 commits into
devfrom
fix/live-map-container-overlap-projection
Aug 28, 2026
Merged

fix(map): overlap between containers is resolved, not negotiated#585
angel-manuel merged 3 commits into
devfrom
fix/live-map-container-overlap-projection

Conversation

@angel-manuel

@angel-manuel angel-manuel commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Reported against the merged #583 with a screenshot of three containers lying
across each other. Reproduced, and it turned out to be two separate things —
#584 (now merged) was the first, this is the second.

Why a force could not finish it

Measured on a fixture matching the report — three users whose clusters are made
of services rather than agents, one of them owning six, which is the shape
none of the earlier fixtures had:

overlap
before #584 52 × 25 px
#584 46 × 5 px
this PR none

The box-vs-box spring reaches the point where it balances whatever pulls the
other way, and every cluster is held to a slot on a ring — R_USER = 250
whose radius knows nothing about how big that cluster grew. Since #582 put
services inside containers, a fleet can be several times the size the ring was
spaced for. Two of them on adjacent slots settle a few units into each other and
stop. Raising the constant only moves where it draws.

The fix

The last word is positional. computeBoxes has just rebuilt the rectangles from
this frame's positions; any pair closer than BOX_GAP — touching reads no
better than overlapping — is separated by translating whole clusters, and draw
strokes the corrected result. A contact constraint cannot be out-pulled the way
a force can, so this stops being a tendency the spring is arguing for and
becomes true of every frame.

The spring still does all the smooth work; this only removes the residue it
cannot reach. Velocity is deliberately untouched — a correction, not a shove;
momentum here would overshoot and swing back. Targets move with the positions,
so the ring spring has nothing left to pull back to. SEPARATION_SLOP keeps a
pair already at the clearance from being nudged, and a cluster under the pointer
is never the one that moves.

Verification

Same fixture, three builds.

Before #584 — as reported

dev

#584 — a sliver left

584

This PR

after

It also settles: two frames captured five seconds apart on the finished layout
are pixel-identical, which they could not be if nearby clusters were being
nudged every frame.

Worth knowing

The reported screenshot has two containers both labelled
amanuelmartincanto@gmail.com
— two user identities sharing one email, which
is the condition #581 ("one actor per human per org") exists to prevent. That is
pre-existing data on dev rather than anything the map does, and it is not
addressed here, but it is worth a look: the map is showing it because it is true.

@vercel

vercel Bot commented Aug 28, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
overslash Ready Ready Preview Aug 28, 2026 11:40am

Request Review

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Code diff size

+117 / −0 across 1 file (net +117)

Area Files Added Removed Net
dashboard 1 +117 −0 +117

Source files under src/ only (.cjs, .go, .js, .jsx, .mjs, .py, .rs, .svelte, .ts, .tsx); test files and inline #[cfg(test)] modules excluded. 0 other changed files not counted.

Comment thread dashboard/src/lib/components/map/sim.ts
angel-manuel pushed a commit that referenced this pull request Aug 28, 2026
`translateCluster` moved positions and left targets where they were, so the ring
spring went on pulling towards where the cluster used to be. Whether that shows
depends on how far the correction moved it: the freeze — `sp < 5 && am < 30` —
absorbs the pull-back for a small correction, which is every correction the
fixtures produce, but past roughly 170 units of displacement the spring stays
above that bar and the cluster creeps back until it crosses the slop and is
corrected out again. A settled map that twitches, once a second, forever.

Targets move with the positions now, which is what the code already does for a
cluster a human drags. Not added to `manualTargets`, though: that set exists so
a *gesture* outlives a fleet refetch, and this is derived from the layout, so it
should be re-derived from the next one.

Found by Seer on #585.
angel-manuel pushed a commit that referenced this pull request Aug 28, 2026
`translateCluster` moved positions and left targets where they were, so the ring
spring went on pulling towards where the cluster used to be. Whether that shows
depends on how far the correction moved it: the freeze — `sp < 5 && am < 30` —
absorbs the pull-back for a small correction, which is every correction the
fixtures produce, but past roughly 170 units of displacement the spring stays
above that bar and the cluster creeps back until it crosses the slop and is
corrected out again. A settled map that twitches, once a second, forever.

Targets move with the positions now, which is what the code already does for a
cluster a human drags. Not added to `manualTargets`, though: that set exists so
a *gesture* outlives a fleet refetch, and this is derived from the layout, so it
should be re-derived from the next one.

Found by Seer on #585.
@angel-manuel
angel-manuel force-pushed the fix/live-map-container-overlap-projection branch from f0775a8 to d1b272e Compare August 28, 2026 09:52
angel-manuel pushed a commit that referenced this pull request Aug 28, 2026
`translateCluster` moved positions and left targets where they were, so the ring
spring went on pulling towards where the cluster used to be. Whether that shows
depends on how far the correction moved it: the freeze — `sp < 5 && am < 30` —
absorbs the pull-back for a small correction, which is every correction the
fixtures produce, but past roughly 170 units of displacement the spring stays
above that bar and the cluster creeps back until it crosses the slop and is
corrected out again. A settled map that twitches, once a second, forever.

Targets move with the positions now, which is what the code already does for a
cluster a human drags. Not added to `manualTargets`, though: that set exists so
a *gesture* outlives a fleet refetch, and this is derived from the layout, so it
should be re-derived from the next one.

Found by Seer on #585.
@angel-manuel
angel-manuel force-pushed the fix/live-map-container-overlap-projection branch from d1b272e to d063fc9 Compare August 28, 2026 10:10
Comment thread dashboard/src/lib/components/map/sim.ts Outdated
angel-manuel pushed a commit that referenced this pull request Aug 28, 2026
…rlap

`separateBoxes` adds `BOX_GAP` before testing, so it acts on any pair closer
than the clearance rather than only on a pair already overlapping — the same
convention the force uses, and deliberate, because two containers touching read
no better than two overlapping. The doc comment said "anything still
overlapping", which describes a stricter function than the one below it and
invites the reading that the `+ BOX_GAP` is a mistake.

`ox`/`oy` renamed to `needX`/`needY`: they are how far the pair has to move to
reach the clearance, which is also what makes `SEPARATION_SLOP` sit right next
to them — the slop is a floor on movement worth making, not on overlap worth
caring about.

No behaviour change. Raised by Seer on #585, which read the comment and reported
the code for disagreeing with it.
angel-manuel pushed a commit that referenced this pull request Aug 28, 2026
`translateCluster` moved positions and left targets where they were, so the ring
spring went on pulling towards where the cluster used to be. Whether that shows
depends on how far the correction moved it: the freeze — `sp < 5 && am < 30` —
absorbs the pull-back for a small correction, which is every correction the
fixtures produce, but past roughly 170 units of displacement the spring stays
above that bar and the cluster creeps back until it crosses the slop and is
corrected out again. A settled map that twitches, once a second, forever.

Targets move with the positions now, which is what the code already does for a
cluster a human drags. Not added to `manualTargets`, though: that set exists so
a *gesture* outlives a fleet refetch, and this is derived from the layout, so it
should be re-derived from the next one.

Found by Seer on #585.
angel-manuel pushed a commit that referenced this pull request Aug 28, 2026
…rlap

`separateBoxes` adds `BOX_GAP` before testing, so it acts on any pair closer
than the clearance rather than only on a pair already overlapping — the same
convention the force uses, and deliberate, because two containers touching read
no better than two overlapping. The doc comment said "anything still
overlapping", which describes a stricter function than the one below it and
invites the reading that the `+ BOX_GAP` is a mistake.

`ox`/`oy` renamed to `needX`/`needY`: they are how far the pair has to move to
reach the clearance, which is also what makes `SEPARATION_SLOP` sit right next
to them — the slop is a floor on movement worth making, not on overlap worth
caring about.

No behaviour change. Raised by Seer on #585, which read the comment and reported
the code for disagreeing with it.
@angel-manuel
angel-manuel force-pushed the fix/live-map-container-overlap-projection branch from 1de4cc6 to c46f6ce Compare August 28, 2026 10:54
Factory added 3 commits August 28, 2026 11:39
The box-vs-box spring could only ever reach the point where it balanced whatever
pulled the other way, and every cluster is held to a slot on a ring whose radius
knows nothing about how big that cluster grew. Since services joined containers
a fleet can be several times the size the ring was spaced for, so two of them on
adjacent slots settle with their boxes a few units into each other and stop. The
push was working. It had an opponent, and drew.

The last word is positional now. `computeBoxes` has just rebuilt the rectangles
from this frame's positions; anything still overlapping is separated by
translating whole clusters, and `draw` strokes the corrected result. A contact
constraint cannot be out-pulled the way a force can, so "containers do not
overlap" stops being a tendency and becomes true of every frame.

Velocity is deliberately untouched: this is a correction, not a shove, and
handing the cluster momentum would make it overshoot and swing back. The spring
still does all the smooth work — this only removes the residue it cannot.

`SEPARATION_SLOP` keeps that from becoming a per-frame shimmer, since the ring
spring pulls back a fraction of a unit every frame and would otherwise be
corrected straight back out forever. A cluster under the pointer is never the
one that moves; if both boxes are held, neither does.
`translateCluster` moved positions and left targets where they were, so the ring
spring went on pulling towards where the cluster used to be. Whether that shows
depends on how far the correction moved it: the freeze — `sp < 5 && am < 30` —
absorbs the pull-back for a small correction, which is every correction the
fixtures produce, but past roughly 170 units of displacement the spring stays
above that bar and the cluster creeps back until it crosses the slop and is
corrected out again. A settled map that twitches, once a second, forever.

Targets move with the positions now, which is what the code already does for a
cluster a human drags. Not added to `manualTargets`, though: that set exists so
a *gesture* outlives a fleet refetch, and this is derived from the layout, so it
should be re-derived from the next one.

Found by Seer on #585.
…rlap

`separateBoxes` adds `BOX_GAP` before testing, so it acts on any pair closer
than the clearance rather than only on a pair already overlapping — the same
convention the force uses, and deliberate, because two containers touching read
no better than two overlapping. The doc comment said "anything still
overlapping", which describes a stricter function than the one below it and
invites the reading that the `+ BOX_GAP` is a mistake.

`ox`/`oy` renamed to `needX`/`needY`: they are how far the pair has to move to
reach the clearance, which is also what makes `SEPARATION_SLOP` sit right next
to them — the slop is a floor on movement worth making, not on overlap worth
caring about.

No behaviour change. Raised by Seer on #585, which read the comment and reported
the code for disagreeing with it.
@angel-manuel
angel-manuel force-pushed the fix/live-map-container-overlap-projection branch from c46f6ce to 1be38fb Compare August 28, 2026 11:39
@angel-manuel
angel-manuel merged commit 576f716 into dev Aug 28, 2026
14 checks passed
@angel-manuel
angel-manuel deleted the fix/live-map-container-overlap-projection branch August 28, 2026 12:16
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