Commit Graph
15 Commits
Author SHA1 Message Date
Codexandclaude[bot] ac077d9b6d fix: a mixed-thickness legacy apex converges honestly (#339)
isDegenerateApexCorner measured the inner-face convergence as
max(h1,h2)/tan(theta/2) — for a 10-degree apex between a 15 cm and a 30 cm
wall that overstates the distance (171.5 cm against the true 128.3/128.9 on
160 cm edges), the corner failed the "inside both edges" test and rendered
as the #329 trident again. Worse, the verdict depended on which neighbouring
edge carried the thicker wall.

The check now intersects the two actual face lines: the meeting point lands
at (hOther + hOwn*cos(theta))/sin(theta) along each edge, degenerate only
when inside both. With equal halves this reduces algebraically to the old
h/tan(theta/2), so equal-thickness verdicts are unchanged by construction —
pinned by the untouched section-4 units and the full golden matrix (136
scenes verified). New units cover both traversal orders of the mixed apex,
the one-point outset tip, the 30-degree ordinary pair and the zero-thickness
guard.

The write path is untouched: P1 forbids new sub-15-degree corners since
issue 329, this is purely how a legacy document renders.

Issue: #339
User-Visible: yes
2026-08-28 10:46:27 +00:00
Matyshandclaude[bot] 2c65fcec03 perf: lazy-load editor runtime
Issue: #337
User-Visible: yes
2026-08-28 05:40:28 +00:00
Codex 508945c088 fix: a 0° pair is the shared-wall model, not a duplicate — field revert of #331 §2.2
Red dev caught it ninety minutes after the merge: smoke_plan_drawing_repairs
and smoke_resize_pointer_real_plan went red because the new "a 0° wedge is
always a duplicate" rule refused two ordinary edits — creating a room over
an existing partition ring (#308's legal overlay) and resizing a wall until
it lands on a neighbour's. The premise was wrong at the model level: a
shared wall of two adjacent rooms IS two co-located owner atoms on one line,
so every shared-wall node carries a legitimate 0° pair by construction.
Bisection pinned the exact cut: with only the 0° rule reverted, both smokes
are green again; keys, incidence, the iterative walk and fail-closed stay.

Spec revision 4 records the revert and returns "an exact duplicate wall is
invisible to П1" to the status of a KNOWN LIMITATION — an honest detector
needs owner identity, which is a separate decision for the owner to make.
The zero-wedge mutant is removed with its rule; the .5-tick parity unit now
observes quantisation through valence instead of the retired duplicate
visibility; changelogs drop the over-promise.

Issue: #331
User-Visible: yes
2026-08-28 05:20:45 +03:00
Codex 0cdf85d9e2 test: the key-precision and dropped-branch mutants actually bite (#331)
Running the mutants exposed two toothless guards before review did:
- reverting the keys to toFixed(6) no longer produced false П4 refusals
  because the new 2e-7 incidence absorbed the debris pair — the REAL harm of
  coarse keys is the opposite direction: nodes 4e-7 apart merged into one
  key and П4 went blind to a genuine near-miss. AC1 now pins that case.
- the `break` patch failed to reproduce the old first-branch-only loss (the
  frontier re-visits the node through the pushed endpoints); the patch now
  truncates the node's candidate list to one entry, which loses forks the
  way `.find` did — both the fork unit and the 10 000-atom run turn red.

Issue: #331
User-Visible: no
2026-08-28 04:42:07 +03:00
Codex 58f3acbbde fix: junction limits are honest at the boundaries (#331)
Six normative cuts, both mirrors symmetric (spec revision 3):

- §2.1 node keys quantise to 1e-7 with the repository's canonicalisation
  formula (sign·floor(|v|·1e7+0.5)/1e7, -0 normalised) — toFixed(6) keys
  split one node into two on floating debris and produced two false П4
  refusals on a legitimate resize (reproduced: -1e-8 vs 0). Node pairs
  within 2e-7 of each other (raw coordinates) are ONE node, and the
  node-to-wall incidence uses the same quantum.
- §2.2 a ~0° wedge IS a violation: two rays leaving a node the same way are
  a duplicated or overlaid wall (a butt joint yields 180°, never 0°) — the
  worst degenerate case was invisible while 0.5° was refused.
- §2.3/§2.4 the wall run is an iterative edge walk over the collinear
  component: no recursion (10 000 atoms answered, not RangeError), no
  silently dropped fork (the old .find lost every branch but the first),
  O(E) by construction, and collinearity is measured against the BASE
  segment's axis so an arc of 0.9°-per-atom pieces cannot pose as one wall.
- §2.5 an exception while judging the CANDIDATE refuses the write with the
  junction.limit_check_failed toast (fail-closed, as the #278 guard); the
  baseline branch stays fail-open by design and the smoke proves the
  asymmetry by breaking only the second call of the deterministic pair.
- §2.6 the python mirror narrows its except on the candidate side only:
  a genuine migration bug (TypeError) surfaces as an honest WS error, while
  a previous-side bug keeps the wide "no baseline" fallback — the two AC6
  cases pin the asymmetry so swapped sides turn a unit red.

Parity fixtures gain the new boundary classes (debris node, duplicate wall,
collinear fork); four new mutants pin the filter, the key precision, the
dropped branch and the fail-open hole.

Issue: #331
User-Visible: yes
2026-08-28 04:39:46 +03:00
Codex 04bb54aef3 fix: the baseline cache keys on geometry, and the smoke tells all three worlds apart (#330 r2-M1)
The reviewer proved my behavioural claim false by running the stale-cache
mutant against the smoke: 11 vs 12 total calls — indistinguishable. Two real
defects hid behind that finding:

1. The cache keyed on _cfgEpoch, which ticks on every ACCEPTED PREVIEW —
   the cache missed on every pointermove and the baseline was recomputed
   ~4 times per gesture (measured). The key is now the document identity
   plus spacePhysicalGeometryFingerprint of its space: content, not a
   counter. A preview overlay leaves the fingerprint alone; an in-place
   structural commit changes it and honestly invalidates.

2. The smoke now counts BASELINE computations only (calls whose document is
   _serverCfg) across two gestures with a commit in between, expecting
   exactly 1 then exactly 2. A disabled cache lands near 20, an eternal
   cache stays at 1 — every mutant class turns the smoke red, and the smoke
   is now the mutant's guard alongside the source-contract unit.

Issue: #330
User-Visible: no
2026-08-28 03:35:40 +03:00
Codex ddfca3a865 fix: close code-review 330-r1 — budgets from the slowest machine, the bench in Validate, AC1 through the execution thread (#330)
H2: the benchmark budgets were calibrated on the author's sandbox with a
1.14x margin — the review runner measured tsFullCandidateMs at 169-171 ms
against a 100 ms ceiling. Budgets now keep the spec's 2-3x allowance over
the SLOWEST observed machine, and the benchmark runs as a step of the
Validate perf job on every push (it needs no browser and no bundle), not
only inside the weekly mutation gate.

M1: the promised AC1 backend test exists now and does what AC1 means: it
patches validate_junction_limits with a thread-recording wrapper inside the
real HA harness — on the event loop that would be MainThread — and proves
the verdicts survived the move (a clean write is accepted, a write adding a
spike is refused with junction_limit_angle). Spec revision 4 rewrites AC1
around this invariant instead of a fragile millisecond assertion.

M2: §4.6 equivalence is now behavioural on both sides (three boundary
fixtures each: as-is counts equal through-migration counts, TS and python),
and the parity suite gained the §7 boundary fixtures (exact 15°, exact
20 cm, the thickness-step filler run, exact 5 cm).

H1 was already closed by 7513f93d (the review ran on the previous HEAD):
check-docs is green on this tree — the screenshots and their manifest come
from one capture run.

Issue: #330
User-Visible: no
2026-08-28 03:07:44 +03:00
Codex 7aaf5a72b0 test: the baseline-cache contract is pinned against the real method (#330)
The first AC4 unit exercised a re-implementation of the cache algorithm, so
the stale-cache mutant patched houseplan-card.ts and the unit stayed green —
the exact "looks like protection" failure the mutation gate exists to catch,
and it caught mine. The monolith is not compiled into test-build, so the
contract is pinned by source (the #293 technique): the epoch check, the
document-identity key and the §4.6 as-is branch must be present in
_junctionLimitsIntroduced. The behavioural half of AC4 lives in the smoke's
real pointer gesture (resizeBaselineCachedPerGesture).

Issue: #330
User-Visible: no
2026-08-28 03:07:37 +03:00
Codex c90f5bf052 perf: junction limits scale — executor, rev cache, linear П3/П4, shared masonry pass (#330)
Six cuts, zero verdict changes (spec §3; equivalence pinned by units, the
parity suite and the smokes):

- §4.1 the CPU chain of ws_config_set and ws_plan_optimize runs in the
  executor; write_lock still serialises writes, only the HA event loop is
  freed (2.8 s of blocking per 576-atom write before).
- §4.2 the stored document's violation counts are cached on the runtime by
  rev (store.py junction_baseline); a repeated write never re-judges
  `previous`. validate_junction_limits takes baseline_counts and returns the
  candidate's counts to cache after a successful save.
- §4.3 П3 builds its node index once per check in both mirrors
  (289→11 ms TS, 285→~50 ms py).
- §4.5 П4 uses a bucket grid with the threshold as cell size in both
  mirrors (104→19 ms TS, 372→44 ms py); pair enumeration switches to
  lexicographic order — same verdict set, equivalence pinned against a
  brute-force oracle on cell borders.
- §4.6 a document already carrying the current catalogue is judged as-is:
  a no-op re-migration cost 815 ms py / 69 ms TS. Legacy documents migrate
  exactly as before (the #329 H1 test stays green).
- §4.7 П5 shares one junction-topology pass per check and pays the masonry
  union only when multi-wall nodes exist — and the resize path hands over
  the preflight's own artifact, so a pointermove never builds the union
  twice (4.2 s → 88 ms full candidate on the benchmark grid).

The frontend baseline is cached per (document identity, config epoch): ten
pointermoves make N+1 limit computations, not 2N — pinned by the smoke on a
real pointer gesture.

demo/benchmark_junction_limits.mjs (npm run benchmark:junction-limits) pins
the budgets for both mirrors: TS full candidate ≤100 ms (measured 88), py
warm validate ≤250 ms (measured 45), cold legacy ≤3.5 s — that path is
one-off and lives in the executor.

Issue: #330
User-Visible: yes
2026-08-28 03:07:11 +03:00
Codex 0824e51014 test: prove AC10 — Optimize adds no junction violation (#329 M4)
AC10 was asserted, never shown. Optimize runs alignAllToGrid and
repairNearAxisRoomWalls, which move nodes by fractions of a centimetre, and
none of П1-П5 carries a margin wider than the grid step in general — so
"obviously true by construction" was not available.

Two units, both counting violations the way the write barrier does (each side
through commitWallSegmentModel first):
- the owner's fixture in legacy storage — the inherited apex is there before
  Optimize, and no rule's count grows after;
- the П4 boundary — two rooms exactly 5 cm apart, where snapping could have
  pulled a node under the limit, stay clean.

The first test asserts the baseline actually carries a violation, so it cannot
pass by measuring an empty plan; violationsByRule fails loudly if the space or
its catalogue goes missing, for the same reason. Spec revision 7 records the
proof and the other three review answers.

Issue: #329
User-Visible: no
2026-08-28 00:24:34 +03:00
Codex f514fb27fe feat: junction limits refuse the write in every editing surface (#329)
П3 measures the WALL, not the catalogue atom: a short filler segment that
compensates a thickness step (owner's fixture, 5 cm = (30-20)/2) is a legal
continuation of a long same-thickness wall, so the rule walks the maximal
collinear run through the shared nodes before judging the length.

Resize stops at the last allowed position and names the broken rule instead
of the generic "geometry cannot be saved"; the Thickness dialog refuses
through its own toast. Both channels are pinned by demo/smoke_junction_limits
plus three mutants (angle threshold, write barrier, degenerate apex bevel).

Issue: #329
User-Visible: yes
2026-08-27 23:31:42 +03:00
Codex 002795f7c8 fix: no jags on the edges of a degenerate apex (#329 §4)
Owner report: small serrations remained on the outer edges between the inner
and the outer vertex. Measured on the fixture ring: two ~4 cm steps plus four
micro-vertices at the tip. Their source was the inset contour's two-point
bevel folding into a bow-tie, and the earlier half-plane clip of that fold,
which left a 0.2 cm sliver the boolean union turned into steps. The inset now
ends in ITS own mitre point at a degenerate apex — mirroring the sharp outer
tip — so there is no fold to clip and no sliver to smear: the room ring is
exactly three vertices, every side longer than the half depth. The clip
helper and its cap plumbing are gone. The user-visible wording of this work
already stands in both changelogs from the #329 entry.

Issue: #329
User-Visible: no
2026-08-27 23:31:17 +03:00
Codex 4758767e0c fix: junction limits judge both sides after the same migration (#329)
The baseline for inheritance was the raw previous document, which for a
legacy space carries no wall catalogue at all — so every inherited short
segment of a real plan looked new and the resize smoke's legitimate write was
refused (executed: two 5 cm segments against their own 30 cm thickness).
Both sides now cross commitWallSegmentModel first, and inheritance is counted
per rule rather than per subject, because a structural write re-keys the
carriers it re-atomises.

Issue: #329
User-Visible: no
2026-08-27 23:30:52 +03:00
Codex 214355f424 fix: a degenerate sharp corner renders as a normal sharp apex (#329 §4)
Owner correction (chat, 2026-08-27): no flat chamfer at the tip — a plain
sharp apex. Proven by execution on the issue fixture: the outset contour fell
back to a two-point bevel (the 4·h mitre limit against an 87 cm reach) while
the inset contour folded into a bow-tie, and subtracting that fold carved the
V-notches — together they made the trident. Now a degenerate corner (below 15
degrees, inner faces meeting inside both walls) contributes ONE outset point
at the plan's own vertex — no bevel, no metres-long mitre needle — and its
inset is clipped at the convergence line so no fold is subtracted. Plain and
merely sharp pairs keep the full mitre of #310. The write-side limits of
П1-П5 stop new plans from creating such corners at all.

Issue: #329
User-Visible: yes
2026-08-27 23:30:26 +03:00
Codex 6fc57f2ae8 feat: pure wall-junction limit checks (#329 П1-П5)
The owner's five limits as pure functions: minimum 15 degrees between
neighbouring rays of a node (a straight wall through the node is a 180 pair,
not a violation), at most 6 walls per node, a segment at least
max(20 cm, its own thickness), 5 cm clearance between non-incident nodes and
between a node and a foreign wall (a T-joint sitting exactly on that wall is
incidence, not a near miss), and a room interior of at least 25 cm2 after the
masonry is subtracted. Thresholds are absolute and do not scale with cell_cm.
newViolations() implements the spec's inheritance boundary: only violations
introduced by the write are reported.

Issue: #329
User-Visible: no
2026-08-27 23:29:49 +03:00