Skip to content

VC3D: link fibers in two gestures (Fiber Map "Link A and B", in-dialog link from a fiber-intersection X) - #2217

Merged
hendrikschilling merged 8 commits into
mainfrom
fiber-link-simplify
Oct 22, 2026
Merged

hendrikschilling merged 8 commits into
mainfrom
fiber-link-simplify

Conversation

@skyward7187

Copy link
Copy Markdown
Contributor

VC3D: link fibers in two gestures (Fiber Map "Link A and B", in-dialog link from a fiber-intersection X)

What

Linking two fibers used to take four menus and two dialog opens. This PR adds two short paths to the same automatic link, drawn in its own colour until approved.

From the Fiber Map

  • Shift+left click keeps the last two clicked fibers selected (plain click: one). The pair stays selected across rebuilds.
  • Ctrl+right-click either fiber → Link <A> and <B>. It finalizes, saves and closes any open annotation of the two fibers, finds where the two DRAWN lines meet nearest the click (else their closest approach within 1 mm), inverts that exactly to a position on each fiber's stored line, searches the closest 3D pass only within ±5 mm along each fiber (multi-winding safe), reuses an unlinked control within 1 mm or places a new one (clear of every control by the strip's spacing, not beyond a kollesis end, not in a gap, inside the focus bounds) with a synchronous span re-optimization and save, then links the two controls with a pending automatic link and waits for the pair write. If the first save made the map stale, the request waits for the automatic Update and runs itself once the map draws the pair unchanged.

From an open Line Annotation

  • Ctrl+right-click another fiber's yellow X (current cut, side cut or strip) → Link <this> and <that>, directly under "Go to nearby annotation". The open fiber's control is anchored at the pass of its drawn line nearest the X in that view, the other fiber's at the X; from there the same ±5 mm / 1 mm rules decide the controls. The open fiber's new control is placed like a manual placement (asynchronous re-optimization, autosave, the same placement limit) and the dialog stays open; the other fiber's open dialog is saved and closed first.

Look and format

  • Automatic pending links draw solid in cyan (#20f0ff; H/V) or light pink (#ffa8d8; same-H/V) everywhere a linked point is drawn (cut views and strips in both renderers, overview bar, slice-view rings, Fiber Map dots, the linked fiber's connector and X). A human-made pending link on the same control outranks. Approving makes an ordinary approved link.
  • Fiber JSON: "auto": true next to "pending": true on ordinary links, mirrored on both reciprocal refs; fiber_merge.py keeps it in lockstep; the loader downgrades a one-sided mark to plain pending on both sides (an older VC3D resave strips it). Format notes in docs/line_annotation_fibers.md and docs/adjacent-fiber-links.html.

Commits (8)

  1. Fiber Map auto-link (selection, menu, locator, controller flow, format, markers, tests, docs)
  2. One gesture when an annotation had to be saved first (participant-only finalize, deferred resume)
  3. Merge of main (conflict notes in the commit message)
  4. Post-merge fixes
  5. Automatic links in their own solid palette
  6. Drawn-line anchor and nearest-line-position helpers (pure, tested)
  7. Mechanical extractions of the shared placement, link, choice and withdrawal steps (no behaviour change for the map, the interactive placement or the candidate link beyond the stated lifetime hardening)
  8. The in-dialog link

Review and tests

Design and code went through iterated codex (gpt-6-astra) reviews until READY: 14 plan rounds + 4 full + 4 specialist + 14 final code rounds for the map link; 6 plan + 4 full + 4 specialist + 1 final for the in-dialog link; the extractions were checked statement by statement against the pre-extraction code. Known limitation: no controller/dialog test harness exists, so the three-phase flows are covered by the GUI protocol below; the pure geometry (locator, closest pass, control choice, drawn-line anchor, choice facts) and the format/loader/merge rules have unit tests (test_fiber_map_auto_link, test_fiber_global_layout, test_line_annotation_generated_views, test_line_annotation_link_validation, scripts/tests/test_fiber_merge.py).

ctest --test-dir build -R "fiber|line_annotation|atlas|vc_settings"   # 41 pass
pytest scripts/tests/test_fiber_merge.py                              # 155 pass

GUI protocol (PHerc0139 refinement project)

  1. Fiber Map: Shift+click two fibers, Ctrl+right-click, Link → status line "Linked … / CP n and … / CP m", map updates, cyan dots.
  2. Open either fiber: the linked control draws cyan in cut views, strips, overview bar and slice views; approve → ordinary approved colours.
  3. With a Line Annotation of one of the two fibers open, Link from the map → saved, closed, map updates, link made without a second gesture.
  4. In an open annotation, Ctrl+right-click a yellow X in a strip / the side cut / the current cut → Link; the new control appears with the usual re-optimization and the link draws cyan.
  5. With the other fiber open in a second dialog, Link from the first → the second is saved and closed first.

Proof

(screenshots)

🤖 Generated with Claude Code

https://claude.ai/code/session_01PrhaQ58LygRfA7y3Psp7uf

skyward7187 and others added 2 commits October 21, 2026 16:10
…omatic links)

Linking two fibers used to take four menus and two dialog opens. The Fiber
Map now does it in two gestures:

- Selection: Shift+left click keeps the last two clicked fibers selected
  (a plain click selects one); both draw bold with their control dots and
  the status line names the pair; a rebuild keeps the pair.
- Ctrl+right-click on either selected fiber adds "Link <A> and <B>" above
  Delete. It finalizes, saves and closes any open annotation of the two
  fibers (D8), locates the meeting on the drawn map (the crossing nearest
  the click, else the closest approach within 1 mm; D2), inverts it
  exactly to a position on each fiber's stored line through the unrolled
  arclength the map was drawn with (Run::arclengths), searches the fibers'
  closest 3D pass only within +-5 mm along each fiber (D3), reuses an
  UNLINKED control within 1 mm or places a new one clear of every control
  by the strip's minimum spacing, not beyond a kollesis termination, not
  in a gap span, inside the focus bounds, re-optimizing the two spans
  synchronously and saving (D4), never refusing on the 3D gap (D5), and
  links the two controls with a pending AUTOMATIC link whose pair save is
  drained before success is reported.
- Format (D6): "auto": true next to "pending": true on ordinary links,
  mirrored on both reciprocal refs and by scripts/fiber_merge.py; the
  loader downgrades a one-sided mark to plain pending on both sides;
  combining two refs (collapse, dedup, merge) is automatic only if every
  pending contributor was; approval clears it.
- Markers (D7): an automatic pending point draws with its own look
  everywhere a linked point is drawn (cut views and strips in both
  renderers, overview bar, slice-view rings, Fiber Map dots); a tagged
  point keeps its tag ring; a human-made pending link on the same point
  outranks.

Robustness, from the review rounds folded into this commit: the
controller's auto-link is guarded after every yield (package, load
sequence, volume, fiber generation, save failures, focus bounds, map
dependencies, open panes, running solves), re-resolves stored fibers
after each, validates control identity before and after each solve,
reads a saved control back before any verdict, tracks the installed
link's endpoints through moves (TrackedLinkEndpoints) so a failed pair
write withdraws every copy (stored records, queued snapshots, open
sessions and their rollback copies) and re-saves the withdrawn state,
holds the structural-edit guard for its duration, and keeps an operation
message on the map's status line across staleness refreshes. The
geometry is exact (accurate cross products, exact window clipping,
representable boundary candidates, strict spacing gate). A reload's
downgrade reaches open sessions. Tests: apps/VC3D/test/
test_fiber_map_auto_link.cpp, the global-layout and link-validation
tests, scripts/tests/test_fiber_merge.py. Docs: "Linking two fibers from
the Fiber Map" in docs/line_annotation_fibers.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PrhaQ58LygRfA7y3Psp7uf
Phase B finalized every open Line Annotation dialog and every save bumps
the fiber generation, so any open annotation made the map stale and
Link had to be chosen twice. Now only dialogs of the two fibers are
finalized (prepareForUserFacingLineAnnotationOpen takes a session
filter), and when a save did make the map stale the workspace keeps the
request (_autoLinkResume: the pair, the click, the two fibers' drawn
geometry captured before phase B) and runs it again by itself once the
automatic Update publishes, provided both fibers draw exactly as the
click saw them and the layout is fresh; otherwise the status line asks
for Link again. A cancellation token covers the queued callback and a
click inside phase B's own drains; cancelling clears the promise.

Also carried here (replaced by the palette commit after the merge): an
interim paler fill for automatic pending markers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PrhaQ58LygRfA7y3Psp7uf
@vercel

vercel Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated
scrollprize-org Ignored Ignored Preview Oct 22, 2026 2:53am UTC

Request Review

skyward7187 and others added 6 commits October 21, 2026 22:49
Conflict resolutions (all against the changes main carried since the
branch point; the later main commits touch none of this branch's files):
- FiberNetworkLayout: both sides' additions kept (branch: Run::arclengths,
  PlacedFiber::controlArclength/thetaScale/offsetRad; main: worldPoints,
  controlWorldPoints, trimDisplayRun).
- FiberMapWorkspace: the branch's endpoint-dot drawer kept, main's
  updateFocusBounds() call kept after it.
- LineAnnotationController.hpp: main's removal of scaleStoredFiber and its
  fiberToJson/saveFiberNow/makeFiberSaveSnapshot signatures taken; the
  branch's TrackedLinkEndpoints and stillValid parameters kept.
- handleGeneratedControlPoint: main's break tag for a control placed
  beyond a lone end break (appendedControlToTagAsBreak) now lives in the
  shared applyGeometricControlPointEdit, so the interactive handler and
  the auto-link's placement apply the same rule.
- finalizeSessionOptimizationSynchronously: main's single-control
  finalization (singleControlRayOnly, the Lasagna fallback for a single
  control without trace inputs) merged into ensureSessionSolveBackends
  (out-parameter solveAsLasagna) and runFiberModeSolveSynchronously
  (solveAsLasagna request override).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PrhaQ58LygRfA7y3Psp7uf
The merged single-control Lasagna fallback in ensureSessionSolveBackends
is taken only by a caller with the out-parameter (the finalizer); any
other caller, the auto-link's pre-placement check of a one-control fiber
included, ensures the trace inputs as it did before the merge. A fresh
verdict that reverts staleness retries a waiting resume (freshness can
return without a publication), and a resume cancelled while its callback
is queued clears the continuation promise.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PrhaQ58LygRfA7y3Psp7uf
…yan, pink)

Replaces the dashed outline and the paler fill: an unreviewed automatic
link draws solid like every other link state, bright cyan (#20f0ff) for
an H/V link and light pink (#ffa8d8) for a same-H/V link (D9),
everywhere a linked point is drawn and on the linked fiber's connector
and projected X. One QtCore-free hue table (linkStateRgb) feeds every
renderer, the Fiber Map palettes included; the separate rings, the
tagged variants and the pale-fill helper are gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PrhaQ58LygRfA7y3Psp7uf
…pers

Pure geometry for the in-dialog "Link <current> and <other>" entry:
generatedDrawnLineVertices (the renderer now builds its polyline runs
from it, so the anchor rule and the screen agree on which segments
exist), effectiveLineTailControlRange (the renderer's tail-range rule in
one place), nearestDrawnLinePosition (the foot of a scene point on the
nearest drawn, non-tail segment as a fractional line position) and
autolink::nearestLinePosition (the foot of a 3D point over ALL segments
of a polyline). Tests in the existing QtCore targets.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PrhaQ58LygRfA7y3Psp7uf
…withdrawal steps

Mechanical extractions for the in-dialog automatic link, no behaviour
change for the existing callers beyond the lifetime hardening noted:
handleGeneratedControlPoint = prepareSessionForControlPlacement (the
autosave flush and dataset yields, with lifetime, validity and the pane
re-checked after each; the handler owns its session across them) +
placeControlInSession (the tail over an edit whose collapse set the
caller decided); materializeGeneratedViews takes an optional validity
callback; handleGeneratedControlPointLinkWithCandidate's link body
becomes linkSessionControlToStoredControl (both refs removed on a
synchronous failure, the pane re-resolved after the publication);
prepareFiberMapAutoLink becomes prepareAutoLinkParticipants over any
participant set with one pane kept open; autoLinkFibers' choice step
runs over plain data (planAutoLinkChoices), its placement over a
prepared session is placePreparedControl, its withdrawal is
withdrawTrackedLink; the map's preparation order is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PrhaQ58LygRfA7y3Psp7uf
…section X

The automatic link made from inside an open Line Annotation: Ctrl+right-
click on another fiber's yellow X (current cut, side cut or strip) offers
"Link A and B" directly under "Go to nearby annotation". The open fiber's
control point is anchored at the pass of its DRAWN line nearest the X in
that view (strip: the X's column; side cut: the nearest drawn non-tail
segment of the windowed projection; current cut: the cut position), the
other fiber's at the X; from those anchors the map's +-5 mm closest-pass
search and 1 mm reuse rule decide the two controls (D10). The open
fiber's new control is placed like a manual placement through the
interactive tail with an empty collapse set (asynchronous span
re-optimization, autosave, the same placement limit; the dialog stays
open; D11); the other fiber's open dialog and dialog-less sessions are
saved and closed first (D12); the link is a pending automatic link made
right after the placement.

Phase A in the slot verifies the identity captured when the menu opened
(package, load sequence, volume, fiber generation), the anchor's line
revision, the pair's eligibility and the placement limit, converts the X
into base voxels only then, reserves the structural guard and defers the
rest on a clean stack. Phases B-D retire the other fiber's sessions, run
the choices over the live session and the stored record (one shared
QtCore-free facts builder, autoLinkChoiceFacts, tested for parity),
place the other fiber's control on a prepared detached session, place
this fiber's through the shared tail, install the link through the
shared session-link body, await its pair write (attributed by fiber
generation, the link required still present in memory) and withdraw it
on a failed write with the state re-saved. The guard distinguishes the
stages (revision until placement, the placed control's identity until
the install, the live-tracked link after); every yield - modals inside
materialization and the metadata save included - is followed by its
check. The disabled entry explains itself in a tooltip. Docs: "Linking
from a fiber-intersection X".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PrhaQ58LygRfA7y3Psp7uf
@hendrikschilling
hendrikschilling enabled auto-merge (squash) October 22, 2026 12:12
@hendrikschilling
hendrikschilling merged commit 131d9ac into main Oct 22, 2026
33 of 34 checks passed
@hendrikschilling
hendrikschilling deleted the fiber-link-simplify branch October 22, 2026 12:14
bruniss added a commit that referenced this pull request Oct 24, 2026
Upstream's fiber work is ported onto the object store instead of the
resident fiber list and file save queue this branch removed.

- "Link A and B" automatic links (#2217): the Fiber Map and in-dialog
  flows read fibers from the store after every yield, place controls
  through checked commits and make the link in one checked commit, so
  the tracked-link withdrawal and corrective re-save are gone. The
  automatic mark is `auto` in the fiber_branch edge metadata, only while
  the link is pending and ordinary; approval clears it. The store
  overlay draws the shared link palette (automatic cyan/pink, same-H/V
  warning, adjacent triangles). Fiber JSON transfer carries the mark.
- vc_fiber_constraints reads AFV collections from an object store
  imported with import_afv.py (exact region selection, AFV ids kept);
  the vesuvius launcher imports each AFV once and passes the store.
- fiber_merge.py and FiberOverlayController stay removed; upstream
  tests are adapted to the version-4 fiber contract and the dropped
  adjacentExplicit field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants