Skip to content

Agent Bridge: a crossover engine for a finished tune, a probe that reads without changing anything, and a guide that asks before it answers - #172

Merged
DIMOSUS merged 11 commits into
mainfrom
worktree-tune-junction
Sep 3, 2026
Merged

Agent Bridge: a crossover engine for a finished tune, a probe that reads without changing anything, and a guide that asks before it answers#172
DIMOSUS merged 11 commits into
mainfrom
worktree-tune-junction

Conversation

@DIMOSUS

@DIMOSUS DIMOSUS commented Sep 3, 2026

Copy link
Copy Markdown
Owner

From the field run of the assistant on a hand-tuned car (the mid/tweeter dip that ended in the wizard putting BW48 back to BW24, moving two corners, levelling the gains and losing 20 dB).

tuneJunction — the crossover engine for a finished tune

The wizard is built for a blank tune: magnitude only, ideal alignment assumed, anchored on 24 dB/oct, gains levelled. There was no engine for one junction of a system that already works, where the phase the two drivers put into the junction at their current delays is the whole question and a steeper slope is a legitimate answer the magnitude cannot see.

tuneJunction takes a junction by the package's id (left:C-D), searches the lower block's low-pass and the upper block's high-pass — corner on the wizard's lattice, family, slopes — and scores each candidate on the pair's coherent sum at the current delays and polarity, through the whole current chains (PEQ included), on every side the pair is measured on: summation loss, dip (MeasureJunctionSpectrum, the alignment score's own gated bins) and the ripple of the sum. Ranking reads every candidate on one shared band (an octave outside the window); each is read again on its own octave-each-side band, the package's, for the report. No slope is preferred; the current crossover keeps its place unless a candidate beats it by 0.5 dB on the shared-band score and reads no worse on its own. Gains, delays, polarity, PEQ and other junctions stay; one crossover is written to both sides of both blocks; Undo AI import puts it back. The summary reports the edges before/after, per side the loss, dip and ripple before/after, and what the best delay of the upper block would still leave — the figure that says whether runAutoDelay should follow (same reply, runs after).

CrossoverJunctionTuner (dsp/) is pure, pinned on synthetic pairs; the headless run on the panel is pinned end-to-end with Undo. The review resolves the junction as the panel's read-outs would (both blocks measured, in the sum, not bypassed, one group, spectral neighbours that hand over; the session snapshot now carries zone/enabled/bypass), holds window/families/slopes to what the tuner can use, refuses a tune beside the wizard and a hand-written setCrossover on either block, and counts once-per-import per junction.

probe — a reply may ask what a setting would do

The only way for the assistant to learn what a change would measure used to be to have the user apply it and undo it; the guide's diagnostic pass was three replies long for that reason. probe reads and writes nothing: a reply names a junction and up to ten variants, each a set of changes to that junction's own two channels stated exactly as the settings operations state them (gainDb, delayMs, invertPolarity, crossover, peq — any combination, everything left out kept, every value held to the same limits through the same CheckValue path). Each variant is measured on copies and reported per side: summation loss, dip, ripple, the pair's phase block, and what the alignment would leave after re-running for it — beside the junction as it stands (current). An empty peq bank is the bank cleared, so the whole diagnostic pass is one row. junctionDelay reports what a delay search would find and pick here; excessGroupDelay is the curve, requested in the reply instead of the menu.

The answer arrives as one clipboard text (resonalyze.agent-probe), one paste. Probes run before anything else in the import, are the one row offered plain rather than with a warning, overwrite nothing, and survive a package the session cannot vouch for (the document carries sessionMatchesPackage). Each entry is read on its own junction band and on one shared band, since two corners compare only on the shared one. The reply JSON depth limit goes 8 → 12: a variant's PEQ bank sits two levels deeper than an operation's.

On the reference Passat session, a probe of left:C-D answered in 488 ms with the session fingerprint unchanged: clearing C's bank takes the junction from −0.5 dB loss / −1.8 dB dip to −1.2 / −5.4 — the bank is holding the sum together, which is exactly what the pass exists to ask.

The first reply asks what the user wants

The guide put the assistant on rails: read the package, then work steps 1–8 whatever the user pasted it for. Now the first reply reads the two steps that gate every answer (is the measurement trustworthy, what does it support), says what they found — including anything that blocks work — and asks: tune from scratch, advice on the crossovers, on the stage, on the tonal balance, a look over a tune they already made, or something they hear. The answer names a route through the same steps, and the guide tabulates them. Notes that already say what the user is after are the answer; a second package in a conversation is not a first one. Rule 2 of the rules that travel inside every package says the same for chats that cannot fetch the guide — and rule 7 (ask a probe rather than have the user apply and undo) was in the guide's mirror but missing from the rules themselves, which a mirror check caught.

Guide 1.5: driver limits, a looser leash, half the size

Asked about 4-inch door midranges, the assistant called a 120 Hz high-pass fine — the measurement reaches there. Excursion rises as the square of falling frequency, so the same SPL an octave lower costs four times the displacement, and a sweep at a polite level says nothing about the level the user listens at. The guide now judges a low corner on Xmax and rated power rather than Fs, cone size or the measured band's edge; names the case (a 4-inch door mid below roughly 150–200 Hz is out of excursion, not bandwidth); and looks at the chain first — where the system has an underseat woofer or a midbass for that band, the band is handed to it and the small driver's corner belongs where the bigger one takes over. Auto crossover's row says plainly that it knows no driver's limits.

The rails are gone: §2 asks what the user wants and the routes say which readings that answer needs, in whatever order suits. §1 says the guide is a map, not a script. Hard rules are marked "never" and cover only damage and over-claiming. 42 KB → 22 KB, 609 → 327 lines, with the schema left to PROTOCOL.md and nothing load-bearing dropped. Guide version 1.5.

What else the guide gained

The judgment the field run was missing: the wizard is for a tune with no crossovers yet and the junction tune for a finished one; on a tune that already works the user's tune is the baseline every step is judged against, a step that reads worse is undone before the next, one engine per reply, the win and the risk stated in dB before asking, one metric never leads, a budget for experiments, and "this is where it should be" as a right reply; after any engine, every change it made is named, asked for or not; the gain balance and a tweeter trim are the user's stage, not a number to level; a channel already within 1.5 dB of the target is tuned. The excess group delay reads the measurement and not the chain, so it is the same with any bank in place — it was being compared before and after a PEQ change, which it cannot show. Rules 2, 3 and 7 of the rules that travel inside every package carry the same, for chats that cannot fetch the guide, and a mirror check now confirms the guide's copy of them is word for word (it caught one that had drifted).

Checks

  • Dsp tests: 1453 passed; App tests: 2261 passed, 11 skipped (Debug).
  • Harness on the reference sessions: Passat B/C (LR36 350) and C/D (LR36 4000) kept; v6 B/C (BW48 200): the best of 735 candidates over three families is the current crossover.

🤖 Generated with Claude Code

…ads without changing anything, and a guide that asks before it answers

Four things a field run of the assistant on a hand-tuned car asked for. Asked
about a 3 dB dip between the mid and the tweeter on one side, it cleared the
PEQ, ran Auto crossover — which put the user's 48 dB/oct back to 24, moved two
corners, levelled the gains cut-only and undid the timing the delays were set
for — and the sum lost 20 dB. Every part below comes out of that run.

tuneJunction, the crossover engine for a tune that already works. The wizard is
built for a blank one: magnitude only under an ideal-alignment assumption,
anchored on the conventional slope, gains levelled. This takes ONE junction by
the package's own id, searches the lower block's low-pass and the upper block's
high-pass — corner on the wizard's lattice, family, slopes — and scores each
candidate on the pair's coherent sum at the CURRENT delays and polarity,
through the whole current chains: the summation loss and its dip
(MeasureJunctionSpectrum, the alignment score's own gated bins) plus the ripple
of the sum, which the loss alone cannot see. The ranking reads every candidate
on ONE band, an octave outside the window and the current corner — two
candidates read on their own octave-each-side bands are not comparable, the
car's own ripple differs between the bands by more than a corner decides, and
on the reference session that artefact was a 0.6 dB phantom win — while each is
read again on its own band, the package's, for the report. No slope is
preferred; the current crossover stands unless a candidate beats it by 0.5 dB
on the shared band AND reads no worse on its own, since a tuned junction is a
decision the user made. Gains, delays, polarity, PEQ and every other junction
stay; one crossover is written to both sides of both blocks, as the wizard
writes one, and Undo AI import puts it back. The summary reports the edges
before and after, per side the loss, dip and ripple, and what the best delay of
the upper block would still take back — the figure that says whether
runAutoDelay should follow, which the same reply may ask for.

probe, the operation that changes nothing. Until now the only way to learn what
a change would measure was to have the user apply it and undo it; the guide's
diagnostic pass was three replies long for that reason. A reply now names a
junction and up to ten variants, each a set of changes to that junction's own
two channels stated exactly as the settings operations state them — gainDb,
delayMs, invertPolarity, crossover, peq, in any combination, everything left
out kept, every value held to the very limit the operation that writes it is
held to, through the same validator path. Resonalyze measures each variant on
COPIES and reports, per side: the loss, the dip, the ripple, the pair's
cross-phase block and what the alignment would leave after re-running for that
variant, beside the same figures for the junction as it stands. An empty peq
bank is the bank cleared, so the whole diagnostic pass is one row. junctionDelay
reports what a delay search would find and pick here; excessGroupDelay is the
curve, requested in the reply rather than found in a menu. The answer comes back
as one clipboard text, one paste. Probes run before anything else in an import,
are the one row the review offers plain rather than with a warning, overwrite
nothing and are overwritten by nothing, and survive a package the session can no
longer vouch for — where every engine request is refused — because what they
read is the session as it is now; the document says whether that session still
matches. On the reference session a probe of one junction answered in 488 ms
with the fingerprint unchanged: clearing that channel's bank takes the junction
from −0.5 dB loss and a −1.8 dB dip to −1.2 and −5.4, so the bank is holding
the sum together — the pass's question, answered with nothing applied.

The guide asks before it answers, and it is half the size. It had grown into a
rail — read the package, then work steps 1 through 8 — and a rail is what makes
an assistant stop thinking. The first reply now reads only what gates every
answer, says what it found including anything that BLOCKS work, and asks what
the user wants: the system tuned from scratch, advice on the crossovers, the
stage or the tonal balance, a look over a tune they already made, or something
they hear in the car. The answer names a route through the same readings.
Section 1 says outright that the guide is a map and not a script. The hard
rules are marked "never" and cover only damage and claiming more than the
measurement supports. 42 KB to 22 KB, 609 lines to 327: the schema is left to
PROTOCOL.md, the engine mechanics likewise, and what stays is what a chat
cannot derive.

And the corner that kills drivers. Asked about a pair of 4-inch door midranges,
the assistant called a 120 Hz high-pass fine — the measurement reaches there,
so the number looked defensible. Excursion rises as the square of falling
frequency, so the same SPL an octave lower costs four times the displacement,
and a sweep at a polite level says nothing about the level the user listens at.
A low corner is now judged on Xmax and rated power rather than Fs, cone size or
where the measured band happens to reach; and the chain is read first, because
where the system has a driver whose job that band is — an underseat woofer, a
midbass — the band is handed to it, and the small driver's corner belongs where
the bigger one takes over cleanly rather than as low as the small one can still
be heard. Auto crossover's row says plainly that it knows no driver's limits.

Guide 1.5. The reply JSON depth limit goes from 8 to 12: a probe variant's PEQ
bank sits two levels deeper than a settings operation's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 78a575cb8c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread dsp/CrossoverJunctionTuner.cs Outdated
/// Where a tune reports how long its phases took, for a harness; null in
/// production.
/// </summary>
public static Action<string>? Trace { get; set; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove the unused public trace hook

A repo-wide symbol search finds no caller for CrossoverJunctionTuner.Trace, including the tests, and it lacks the required reserve-API remark and coverage. Remove it rather than exposing mutable process-wide state solely for a hypothetical harness.

AGENTS.md reference: AGENTS.md:L60-L66

Useful? React with 👍 / 👎.

Comment on lines +612 to +614
.Select(candidate => new JunctionDelayProbeCandidate(
candidate.DelayMs, candidate.InvertPolarity, candidate.ScoreDb,
candidate.LossDb, candidate.DipDb,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Report the upper channel's final polarity

When the upper chain is already inverted, FindAlignmentCandidates returns InvertPolarity relative to the processed IR, so forwarding it directly as InvertUpper reports the opposite absolute state: false means keep the existing inversion, not make the channel normal. The probe document and junction-tune summary describe this field as the polarity the search would pick, so assistants can recommend the wrong polarity; XOR the relative result with upperChain.InvertPolarity, or explicitly expose it as a relative flip everywhere.

Useful? React with 👍 / 👎.

Comment on lines +536 to +539
wire.Variants?.Select(variant => new AgentProbeVariant(
variant.Label,
(variant.Changes ?? []).Select(change => new AgentProbeChange(
change.ChannelId,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject null probe collection elements before mapping

For malformed but valid JSON such as "variants":[null] or "changes":[null], this mapping dereferences the null element before CheckStrings can reject it. Because ReadOperation catches only JsonException, a single bad probe escapes AgentProposalParser.Parse as NullReferenceException and aborts the whole import instead of producing the intended rejected row; null-check each nested element through NotNull while mapping (the same issue also applies to null PEQ-band elements below).

Useful? React with 👍 / 👎.

Comment thread docs/agent/PROTOCOL.md
Comment on lines +470 to +471
operation while the rest of the import carries on. `tuneJunction` runs
without a dialog, on one junction, and is described after the table.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the engine-request count after adding tuneJunction

Adding tuneJunction here leaves the introduction at lines 438–440 saying there are four value-free operations, three of which open engines, even though this section now describes the additional junction engine (and its table also includes probes). Update that introductory count and classification so protocol readers are not given internally contradictory instructions.

AGENTS.md reference: AGENTS.md:L155-L160

Useful? React with 👍 / 👎.

DIMOSUS and others added 10 commits September 3, 2026 04:54
Ten variants per probe was a number, not a bound. Measured on a reference
session a variant costs about 65 ms and 0.8 KB, so ten is a second and 10 KB —
nowhere near anything that needed protecting, while what the limit actually
protects is real: the readings run while the user waits, with no progress and
nothing to cancel, and the answer is a text they have to paste.

Worse, the cap did not bound that. It counted per probe, and a reply could send
two probes — except that it could not, because probes inherited the engines'
once-per-import rule and a second probe on the same junction was refused as a
repeat. So a reply wanting fourteen variants of one junction had to drop four,
and one wanting them of two junctions could have twenty.

Both halves are fixed. A probe writes nothing, so a second one on the same
junction is another QUESTION about it rather than a second run of an engine:
the repeat rule now skips probes. And the count becomes a budget for the whole
import — 24 variants however the reply splits them, about a second and a half
and a text the size of a package — which is what the user actually spends. A
reply that wants more searched is asking for the junction tune, and the refusal
says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ide a probe refuses one row, and the trace hook goes

Three from the automated review of the branch, all of them real.

The polarity was relative and read as absolute. The alignment search works on
the response the chain has ALREADY inverted, so its answer means "flip what I
was given". The probe document and the junction tune's summary call the field
the polarity the search would pick, which a reader takes for the channel's
resulting state — and for a channel already running inverted the two are
opposites, so a reply could have proposed exactly the wrong polarity. Both
reports now XOR the search's answer with the chain's own inversion and say in
the conventions that the figure is what the channel ENDS UP as.

A null inside a probe took the whole import down. `"variants": [null]` and
`"changes": [null]` are valid JSON that `required` does not catch, and the
mapper dereferenced them; the parser catches JsonException, not
NullReferenceException, so one malformed probe aborted the import instead of
becoming one rejected row. Every nested element now goes through NotNull, whose
JsonException is what the parser is built to turn into a refusal — the PEQ band
elements too.

And the trace hook is gone. It was a mutable public static that nothing in the
solution called: it earned its keep while the panel's own timings were being
measured, and AGENTS.md is right that a member with no caller is dead code, not
a reserve. The protocol's engine-request introduction, which still said there
were four value-free operations of which three open engines, now counts the
junction tune and the probe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…at says only "busy"

Everything the bridge does slowly used to happen behind an hourglass and a
greyed-out button: gathering a package is a second or two on a large session,
the excess group delay is a gated FFT and a minimum-phase reconstruction per
channel, and an import can probe a junction, tune its crossover, search delays
and fit a bank one after another. A cursor cannot say "still going" or "at
what", so a run of ten seconds reads as a hang.

AgentProgressDialog names the step under way, lists the last few behind it and
moves a marquee bar. Copy for AI shows it while it reads the tune (and says so
if the tune moved and it is reading again), Copy diagnostics names the channel
each curve is being taken on, and an import reports every probe and every
engine as it starts, in the words the summary will use afterwards.

It is deliberately informational rather than modal. The steps that must not
have the tune changed under them — Auto delay, Auto-tune — already disable the
panel themselves, and one of them opens a window of its own that a modal
progress box would have to fight for the foreground. And there is no Cancel:
every step behind it is a single computation the panel cannot interrupt without
leaving a half-written tune, and a button that cannot keep its word is worse
than none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… them

The commit-time check had a hole this branch opened. Prepare re-reviews the
ticked rows against the session as it is and refuses when it has moved, and
Apply then writes without checking anything — that division was safe while the
only thing between them was a modal message box. Then the probes went in front
of the writes: they take seconds, the progress window over them deliberately
takes nothing away from the panel, and the rows being written had been judged
before they started. A user who moved a gain while a ten-variant probe read the
junction would have had the old values written over it.

The writing half is now its own method, and it opens with the same Prepare —
against a snapshot taken at that moment. When the tune moved, nothing is
written, no undo is armed, and the summary says so; the probes stand, because
they only ever read and their answer describes what they read. A test pins both
halves: the guard refuses and leaves the edited gain alone, and the rows
prepared before the probes — the list the import used to carry across them —
still write straight over that gain when applied, which is what the guard is
between.

The lesser half of the same report: several readings of one document could
straddle an edit. They are not refused (each is true of what it read), but the
document now carries sessionChangedWhileReading, and the summary says it, so a
reader never compares figures from two states without being told.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nd the batch

The flag that says a document holds readings from more than one state was
computed from one fingerprint before the batch and one after it, and the
protocol claimed its absence meant one session throughout. It did not: a tune
changed and changed back — the reading in between taken off the other state —
matches at both ends and passed as steady.

The comparison now happens at every reading's boundary and accumulates. Each
reading is taken off snapshots of its own, so an edit made and undone entirely
within one changes nothing that reading holds and is rightly not flagged; an
edit that outlives a reading is caught at the boundary it crossed. The
protocol and the document's own conventions say exactly that now, instead of
the stronger claim they could not keep. A fingerprint costs nothing beside a
junction reading, so the extra samples are free.

Not covered by an automated test: the case needs an edit to land between two
readings while the loop is running, which nothing in the panel offers a seam
for, and the flag is informational — no probe writes anything, so the worst a
missed flag can do is let a reader compare two figures without being told to
be careful.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d an invented number

Four corrections from a reading of 1.5, all of them places where the guide told
an assistant two different things.

The junction tune's row said to ask for runAutoDelay in the same reply when the
after-delay reading is far below the loss at the current timing; the paragraph
below says one engine per reply, and that a step which reads worse than the
baseline is undone before the next. Both cannot be obeyed, and the second is
the one this guide is built on: the tune is asked for, the package is read, and
the realignment goes in the NEXT reply. The import can still carry both — the
protocol says so, and now says why the guide separates them anyway: the second
step cannot be judged while the first is unread.

"Request each engine once — autoTunePeq per channel, tuneJunction and probe per
junction" contradicted the paragraph above it, the validator and its tests: a
probe writes nothing and the once-per-import rule deliberately skips it. The
rule now names what it is about — the engines that CHANGE something, per their
own scope — and says probes may repeat as the question needs, inside the
reply's variant budget.

The excursion rule said not to judge a corner by cone size and then judged one
by cone size: "a 4-inch door midrange asked for real output below roughly
150–200 Hz runs out of excursion". It now reads as what it is — a reason to
CHECK a driver of that class, not a corner to propose from its size — and sends
the reader to Xmax, power, enclosure and the level the user listens at.

And "say in dB what an engine would win" asks for a number the assistant cannot
have before a search runs, which invites exactly the invention the guide
forbids elsewhere. It now asks to quantify the PROBLEM and what the data
supports, says never to invent a predicted gain, and points at the probe, which
is where a measured one comes from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A junction's two edges need not share a frequency — the reference v6 car crosses
its mid and tweeter at 1610 and 1800 Hz — and a probe variant states the two
channels separately, so it could always ask for corners apart. What it could not
do was read them properly: the band an entry was measured on came from the lower
channel's low-pass alone, and the shared band every entry is compared on was
built from those same lower corners, so an upper edge sitting above them fell
outside the reading that was supposed to cover the pair.

A variant's handover is now the geometric middle of its two edges when both are
there (the lower one alone when it is the only one), and the shared band is
drawn from every edge of every variant rather than one per side. With equal
corners — the common case — nothing changes; with corners apart, the entry is
read around the pair instead of under half of it.

Pinned on a synthetic pair 2 kHz against 8 kHz: the entry keeps both edges,
reads at 4 kHz between them, the shared band reaches past the upper one, and the
gap shows up as ripple. On the v6 mid/tweeter junction (BW48 1610/1800) a probe
of three variants — corners apart each way, and two different families and
slopes on the one junction — answered in 432 ms with the session unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… the guide forbids

The correction went into §6 and stopped there. Rule 3 of the rules that travel
inside every package still said to "say in dB what an engine would win before
asking for it" — a number an assistant cannot have before a search has run, and
the one rule a chat that cannot fetch the guide reads instead of the guide. So
the shortest, most-read text asked for exactly the invention the long text
forbids.

Both copies now say the same: quantify the problem and any available
improvement in dB where the data supports it, never invent a predicted engine
gain, and judge every step against the user's tune rather than the step before.
The mirror check confirms the guide's copy of the rules is still word for word
what the package carries.

The protocol's description of a probe entry's own band also spells out what
follows from reading around the handover: with two edges more than two octaves
apart — a hole rather than a handover — the shared band is the only one that
contains them both, which is where the comparison already lives.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A variant may give a midrange a new crossover, bank or gain, and the
reading covers only the junction the reply named: a change that wins at
C-D can spoil B-C, and nothing in the answer said so. Leaving that to the
assistant's memory is the weakest place to leave it.

The report now names those junctions itself. NeighbourJunctionIds walks
every adjacency of every side and group and keeps the ones a changed
channel takes part in, each confirmed through ResolveJunction, so the
field never points at a junction a probe would refuse; the guide and the
conventions say to probe them under the same variant before proposing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…robe

Pooled over the probe, affectedJunctions said something no entry meant: a
probe of C-D whose one variant rewrites C and whose other trims D named
B-C and D-E together, and whichever won, the assistant was sent to both.
The guide's answer to that list — probe them under the same variant — was
not even executable: a variant states the named junction's two channels,
and C is not one of D-E's.

Each entry now carries the junctions ITS OWN changes reach, the baseline
none; the guide and the protocol say to carry the change to the channel
the two junctions share, which is the part of the entry that travels. The
test now sends two variants that touch opposite ends and asserts the one
neighbour sits on the entry that earned it — it fails on the pooled list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@DIMOSUS
DIMOSUS merged commit 56b07c8 into main Sep 3, 2026
1 check passed
@DIMOSUS
DIMOSUS deleted the worktree-tune-junction branch September 3, 2026 07:30
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