0.7.0 — 2026-07-19
Fixed
- Distorted-camera pose was silently wrong (correctness). The production
single-tag pose path (the weighted LM
refine_pose_lm_weighted) and the three board pose solvers minimized a pinhole reprojection error even when theCameraIntrinsicscarried a Brown–Conrady / Kannala–Brandt distortion model — only the image-less corner-LM fallback was distortion-aware. On a distorted camera this biased every emitted pose (in a synthetic Brown–Conrady test the weighted LM converged 8.6° off ground truth; the distortion-aware fix recovers it exactly). Both the normal-equations builder and the Nielsen step-evaluation cost (huber_mahalanobis_cost, which was still pinhole and made the gain ratio inconsistent) now route through the shared distortion-awarepose::projection_and_gradient. The board path is made distortion-consistent end-to-end for the same reason (its three LM sites, RANSAC branch scoring, inlier evaluation, and per-observation d² all reprojected pinhole; all now usedistort_normalized, and the DLT/IPPE seed now undistorts observations to ideal normalized coords before homography fitting).Pose::projectis likewise made distortion-aware (it was pinhole despite its doc, and biased the detector's reprojection-error telemetry on distorted cameras). Undistorted (DistortionCoeffs::None) results are unchanged — all pinhole regression snapshots (render-tag, ICRA, board-hub, ROC, distortion-hub) pass byte-for-byte (the projection differs from the removed pinhole arithmetic only by floating-point reassociation of the pixel scaling, which moves no fixture). Covered byweighted_lm_minimises_distorted_reprojection(theregression_distortion_hubsuite only exercised routing/decode, never pose accuracy). - Board pose LM convergence (accuracy). The three
board.rspose solvers previously paired a camera-frame (Convention-1) rotation Jacobian with a non-rotated translation update (Convention-2), an inconsistency that let the LM take suboptimal steps and stop short of the optimum. The right-perturbation migration makes Jacobian and update consistent, substantially improving board rotation accuracy on the hub golden set — e.g. ChArUco board rotation error mean 0.107°→0.053° (−51%) and p99 0.651°→0.218° (−67%); AprilGrid mean 0.073°→0.037° (−50%) — with board translation neutral and no new singular covariances. Board pose snapshots re-baselined accordingly. refine_pose_lmrotation-Jacobian sign bug. The unweighted corner-pose LM built its rotation Jacobian rows with the wrong sign (the negative of the canonical∂proj/∂ωused by the weighted LM andprojection_jacobian), makingJᵀran ascent direction for rotation. Its trust region therefore rejected every rotation step, so the solver refined only translation and returned IPPE's rotation unrefined. This path is reachable only via the image-lessestimate_tag_pose(corners, None)API — the full detection pipeline always supplies an image and so uses the weighted (already-correct) LM, which is why the bug never affected pipeline detection and shipped undetected. Fixed to the canonical sign; the corner normal-equations assembly was extracted to a sharedcorner_normal_equationswith a committed finite-difference gradient check (corner_normal_equations_gradient_is_descent) and a rotation-recovery test. Behavior change for API consumers: callers of the image-lessestimate_tag_pose(corners, None)/estimate_tag_pose_with_diagnostics(..)path now receive a rotation-refined pose (previously the returned rotation was IPPE's, unrefined); translation and every image-backed pipeline result are unchanged. The shipped-profile detection snapshots (render-tag, ICRA, board-hub) and all 253 default-feature tests are byte-identical. Onebench-internals-gated regression snapshot does move as the direct, intended consequence of the fix:regression_pose_consistency_roc(synthetic-isotropic) — with rotation now actually refined on the fallback path, the χ²(2) pose-consistency gate's realized false-positive rate tracks the modeled FPR far more closely (e.g. 0.169 → 0.101 at a modeled 0.1), so the recorded snapshot was regenerated to the better-calibrated values.
Changed
-
high_accuracyprofile now ships with model-edge pose refinement on. Thehigh_accuracyprofile setspose.pose_edge_refinement_enabled = true, turning the opt-in stage (added below) on by default for the metrology profile. On the render-tag suite (Accurate pose, intrinsics +tag_size) this tightens the whole rotation distribution — p99 −47 % to −76 % at every resolution (1080p 0.561° → 0.290°, 2160p 1.387° → 0.277° on the regression fixtures) — and reprojection RMSE improves at every resolution (mean −13 % to −22 %), with recall, precision and 2D corner RMSE unchanged (the stage only refines an already-accepted pose; it never writesbatch.corners) at ~+1 ms/frame. The win is larger still on the degraded-imagery robustness sets (rot p99 −56 % to −84 %). Theregression_render_tagaccuracy_baselinesnapshots are re-baselined accordingly;board-huband ICRA snapshots are byte-identical (the stage is confined to the single-tag Accurate pose path).standardandgridstay off — onstandard's corner baseline the stage is a bulk win but does not fix its catastrophic rotation-p99 tail and slightly regresses the high-resolution reproj/translation tail, becausestandardlacks the pose-consistency χ² gate that re-validates each refined pose (it would trade the render-tag tail); seedocs/engineering/benchmarking/model_edge_refinement_20260715.md. -
Pose LM convergence: unit-sound function-tolerance gate. The shared
nielsen_lmsecondary convergence gate is now a relative-cost-reduction test ((cost_prev − cost_new) / cost_prev < func_tol,func_tol = 1e-6, the Ceresfunction_tolerancedefault) in place of the mixed-unit‖δ‖ < 1e-7step gate. The old gate summed translation-m² and rotation-rad² into one scalar — a dimensionally-unsound quantity whose binding point drifts with the arbitrary choice of length unit. Two committed diagnostics quantify the change:step_gate_unit_rescale_invarianceposes the identical pixel problem across a 10⁴× translation-scale range and shows the old gate drifts 15→20 mean iterations (and on far/small tags fails to bind, running tomax_iters), whilefunc_tolis exactly scale-invariant at ~7.8 iterations;step_gate_board_batteryasserts the new gate reaches the converged rotation to <5e-3° on a 36-point board. The switch is accuracy-neutral: every gate — old, new, and a fully-converged reference — yields identical rotation p99 (the reprojection objective is flat in rotation long before either gate binds, so the earlier "loosening risks the rotation tail" concern was empirically falsified — the old gate's real cost was unbounded, unit-dependent iteration counts on the AV long-range regime). Single-tag snapshots (render-tag, ICRA, ROC) stay byte-identical; the board hub snapshots move only at the convergence-point-noise floor (mixed-sign, sub-1 % on p99 aggregates, shippedhigh_accuracyChArUco rotation essentially unchanged) and are re-baselined.grad_tolremains the primary stationarity gate. - Shared Nielsen trust-region LM core. The three pose LM loops (corner
unweighted/weighted and the board damped LM) are unified onto one
pose::nielsen_lmcore parameterised by aNielsenConfig. This removes three hand-rolled λ/ν loops and fixes a latent board inconsistency (it previously used a plainλδpredicted reduction that did not match its own Marquardt damping); the canonical form usesD = max(diag(JᵀWJ), floor)for both the damping and the Madsen gain-ratio predicted reduction. The weighted corner LM and the board LM also now share onemahalanobis_huberrobust kernel and oneMAHALANOBIS_HUBER_Kconstant (previously a third hand-copy of the Huber math + a duplicated literal). Single-tag results (render-tag, ICRA, ROC) are byte-identical; the board snapshots move only at the FP-reorder floor (max 8.5e-7 relative, net neutral / slightly better medians) and are re-baselined. Anielsen_config_tuning_sweepdiagnostic evidences the constant choices (the step gate was revisited in the function-tolerance entry above). - Pose LMs migrated to right (body-frame) SE(3) perturbation. All four pose
solvers —
pose::refine_pose_lm(unweighted corner),pose_weighted(weighted production), and the threeboard.rsLM sites — now parameterise the update asT' = T·exp(δ)(Pose::retract) with the matching body-frame Jacobian, unified onto shared accumulators. Because the object points are symmetric about the body origin this decouples rotation from translation inJᵀWJ(~3.4× better conditioning; rot↔translation coupling ~0.999→0.06) and converges in fewer iterations. Breaking: the emitted 6×6 pose covariance is now expressed in the body frame (previously camera/left). It remains ordered[t, ω]; consumers that need the old camera-frame covariance can convert it with the newPose::covariance_body_to_camera. Single-tag accuracy is unchanged — render-tag and ICRA regression snapshots hold within bounds; seedocs/explanation/coordinates.md §5.1. Supersedes the earlier "Pose::retractextracted (left-perturbation)" note below. Pose::retract. Extracted the SE(3) left-perturbation updateexp([t|ω])·poseinto one method, replacing four inline copies across the pose LMs (byte-identical). First step of the LM-consolidation series, completed by the body-frame perturbation, distortion-aware, shared-Nielsen-core, and function-tolerance-gate changes above.- Unweighted corner LM cost caching.
corner_normal_equationsnow returns the Huber cost at the linearization point alongside(JᵀWJ, JᵀWr)(bit-identical to a standalonehuber_cost), andrefine_pose_lmadopts the weighted LM'sneeds_rebuildcaching so it re-projects only after an accepted step instead of every iteration. Pure efficiency/consistency refactor — byte-identical results, and brings the unweighted loop structurally in line withpose_weightedahead of the shared-core phase.
Added
- Model-based edge pose refinement (opt-in, Accurate mode). A new post-decode
stage (
pose.pose_edge_refinement_enabled, default off) aligns a decoded tag's ~40 internal bit-grid edges + border to the image and refines the 6-DoF pose against them (rotation from the distributed interior edges; translation re-anchored to the 4 corners). It adds information — the decoded interior — rather than reshaping the 4 corners, so unlike the trade-bound corner-level levers in the 2026-07-14 study it improves rotation without giving up Locus's best-in-class translation. The stage rides the shared body-frame Nielsen trust-region core (nielsen_lm+BodyFrameNormalEquations): each 1-D edge-normal residual enters the same distortion-aware accumulator as a corner residual weighted by the rank-1 projectorn·nᵀ, under a robust Huber loss so the clean interior edges (not a few latched/biased ones) control the rotation. Edge scans that leave the image or project behind the camera are rejected/penalised, and the refined pose is re-validated against the pose-consistency χ² gate so it can never weaken false-positive suppression (recall and precision stay 100 %/100 %; mean corner RMSE is untouched — the stage only refines the pose). On render-tag it tightens the whole rotation distribution — mean −39…−55 %, p99 −47…−76 % at every resolution (1080p 0.600° → 0.249°, p95 0.385° → 0.180° — well under OpenCVapriltag's 0.376°) — keeping translation best-in-class in mean and p99, against a small bounded p95 trade at high resolution (the gated edges→rotation / corners→translation split). Reprojection RMSE improves at every resolution (mean −13…−22 %), confirming a genuine image-space accuracy gain; 2D corner RMSE is unchanged. +~1 ms/frame. Requires camera intrinsics +tag_size; shipped profiles leave it off, so detection snapshots stay byte-identical. Seedocs/engineering/benchmarking/model_edge_refinement_20260715.mdandtools/bench/model_edge_eval.py. Pose::adjoint+ SE(3) covariance reframing.Pose::adjointreturns the 6×6 SE(3) adjoint ([t, ω]ordering);Pose::covariance_camera_to_bodyandPose::covariance_body_to_cameraconvert a pose covariance between the camera (left) and body (right) tangent frames exactly (Σ_cam = Ad_T Σ_body Ad_Tᵀ). PlusPose::inverse. Validated byadjoint_relates_body_and_camera_tangentsandcovariance_reframe_roundtrips.- Corner-refinement variant benchmark + rotation-tail study. New
tools/bench/refine_variants_eval.pyruns the shippedhigh_accuracydetector with only its large-marker refinement route swapped (config-only; shipped profiles and snapshots untouched). The 2026-07-14 study (docs/engineering/benchmarking/refine_variants_20260714.md) shows apriltag-style edge-line refit (GWLF) reaches the rotation p99 target but regresses translation 3.4× (not a Pareto win), and that the residual rotation-p99 gap is 2–3 EdLines arc-partition failures rather than distributed refinement noise. No library behavior change — the study'sEdgeLineGatedprototype was reverted. - Config field-set parity tripwire. A
locus-coreunit test (config::schema_parity_tests) now asserts the serde profile shim (ProfileJson+ nested*Jsonstructs) exposes the exact same JSON key set as the refereeschemas/profile.schema.json. Because the schema is already CI-locked to the Pydantic model, this transitively pins Rust ↔ Python config field-set agreement — the one seam none of the value-oriented tripwires (profile_loading.rs,test_profiles.py, the schema-diff job) asserted. Adding/removing a knob on only one side is now a loud red build. The*Jsonshim structs gainedSerialize(andProfileJsona derivedDefault) to support the reflection; no behavior change. - Generated
locus/locus.pyitype stub + drift gate. The hand-maintained stub (whose silent drift motivated this pass) is now generated from the annotated pyo3 surface viapyo3-stub-gen:cargo run --bin stub_gen(re)writes it, and a CI step (stub_gen --check, in the Profile Schema & Stub Parity job) fails on drift. Together with the field-set tripwire and the schema-diff job this closes the loop Rust ↔ Pydantic ↔ schema ↔ .pyi.pyo3-stub-genis an optional dependency behind a non-defaultstub-genfeature, so the shipped wheel and thecargo denygraph never pull it (or its parser / unmaintained-unicode transitive deps) — the production build is byte-identical to before, as the#[gen_stub_*]annotations compile out under default features.pyo3'sextension-modulebecame a toggleable default feature so the generator binary can link libpython and run standalone.
Changed
- Detector config crosses the Rust↔Python FFI as JSON, not a 40-field struct.
The Pydantic
DetectorConfigis now shipped to Rust as itsmodel_dump_json()string (parsed byDetectorConfig::from_profile_json) and read back the same way (DetectorConfig::to_profile_json), making the shipped JSON profile format the single contract on both sides of the boundary. This deletes thePyDetectorConfigpyclass, its 40-argument constructor, bothFromimpls, Python's_to_ffi_config, thequad.extraction_policyenum-flattening discriminator hack, and the hand-written.pyiblock — ~300 lines of pure transcription, and the FFI seam that had no runtime coverage. Behavior-neutral: render-tag / ICRA snapshots byte-identical.PyDetectorConfigis removed from thelocus.locusextension surface (internal FFI glue; it was never in__all__). - OpenCV benchmark baseline bumped to 5.0.0 and re-tuned. Bumped
opencv-python-headless4.13 → 5.0.0 (fixing adetectMarkersids-shape change in the comparison adapter) and re-tuned thecv2.arucobaseline for its best 5.0 config. The reported OpenCV numbers now show two operating points: fastsubpix(tuned detection thresholds: render-tag trans p99 141 → 66.6 mm for free) and best-accuracyapriltagrefinement (55.3 mm / best rotation tail, ~2× slower).apriltagis now the shippedOpenCVWrapper._DEFAULTS. On ICRA its recall rises to 52.6 % (from 33.2 %) with the recall-tunedsubpixconfig. All render-tag latencies re-measured single-threaded on verified hardware. Seedocs/engineering/benchmarking/render_tag_sota_20260713.md; the OpenCV version is now captured in benchmark provenance.
Removed
- Dead / unused detector config knobs. Pruned three knobs across all
representations (flat Rust
DetectorConfig, serde profile shim, Pydantic model, PyO3 FFI,.pyistub, JSON schema): threshold.enable_adaptive_window— plumbed through every layer but read by no algorithm code (genuinely dead). Removed from the 3 shipped profiles and the test fixtures that carried it.quad.edlines_phase3_erf— set by no shipped profile (parabolic-vertex is the only production path). Removing the knob let us delete the dead per-micro-ray ERF branch inedlines.rs, thefit_erf_step_lmkernel and its unit tests, and the enum's dual-format legacybool/string deserializer.extended-testsCargo feature — redundant withextended-bench; collapsed.
Breaking (Python config API): the threshold.enable_adaptive_window and
quad.edlines_phase3_erf fields and the EdLinesPhase3ErfPolicy enum are gone.
No shipped profile relied on either, so detection output is unchanged
(render-tag / ICRA snapshots byte-identical).
- max_recall_adaptive profile (shipped presets 4 → 3). The opt-in
"Phase 1 sentinel" carried a placeholder PPB threshold, minimal coverage, and
was excluded from the SOTA eval; the shipped set is now standard, grid,
high_accuracy — matching what the docs already described. Breaking (Python
config API): "max_recall_adaptive" is no longer a valid profile= /
DetectorConfig.from_profile() argument. The AdaptivePpb machinery it shared
with high_accuracy is untouched; its distortion-routing regression test was
retargeted to build the policy inline so that dispatch coverage survives.
- Phase C.5 post-decode corner re-refit (post_decode_refinement). Removed
its only consumer (max_recall_adaptive), so the feature — the
post_decode_refinement.rs module, the decoder.post_decode_refinement config
field across all representations, and the now-orphaned
RefineConfig::post_decode_style / ErfEdgeFitter::line_jtj helpers — is
deleted. Breaking (Python config API): the decoder.post_decode_refinement
field is gone. No shipped profile set it, so detection output is unchanged.
- Phase 0 rotation-tail diagnostic harness (bench-internals-only, always
marked throwaway). Its stated revert precondition — the Phase 0 memo landing —
is met, and the single-frame rotation line of inquiry is closed. Deleted the
dual-branch LM / leave-one-out refit / per-iteration IRLS telemetry entry
points in locus-core (pose.rs, pose_weighted.rs), the mod bench FFI
surface and locus.bench Python sub-module in locus-py, and the
tools/bench/rotation_tail_diag/ + tools/bench/pose_cov_audit.py tooling
built on it. The permanent foundation is untouched: the bench-internals
feature itself, compute_image_noise_floor, the branch_chosen field on
PoseDiagnostics, bench_compute_corner_covariance, and the shipped
pose-consistency / outlier-drop telemetry columns on DetectionBatch.
Fixed
Detector.config()now round-trips every field. The Python readback previously reconstructed only ~29 of 39 config fields by hand, silently substituting Pydantic defaults for thepose_consistency_*/outlier_drop_d2_thresholdknobs and the adaptivequad.extraction_policyon any detector whose config set them.config()now reparses Rust's serialized effective config (see the JSON-boundary change above), so the round-trip is total. Covered by the rewrittentest_profiles.pyfull-config comparison.pose_consistency_min_decisive_ratio = math.infsurvives the JSON FFI boundary. The documentedf64::INFINITYvalue (which disables the χ² escape clause) has no JSON number form; it now round-trips asnullin both directions — Rust's serde shim maps∞↔nulland Pydantic parsesnullback tomath.inf— instead of raisingValueErroratDetectorconstruction. Other non-finite handling is unchanged.
Added
- Per-instance comparative benchmarking + visualization (
tools/bench/compare/, Python-only). Compares locus-tag against tuned OpenCV ArUco / pupil_apriltags image-by-image and per tag instance to find which library wins on which strata and which specific images are Locus's improvement levers. New CLI: bench compare-instances— end-to-end: runs four series (locus:tuned,locus:shipped,opencv_aruco:tuned,apriltag:tuned) from abench tunepareto/dir across the render-tag datasets → per-instance report → rerun deep-dive. Split intobench compare-generate(the combined Tier-1 parquet) andbench compare-report-instancesfor iteration.- Analysis is polars-native (
compare/analysis.py): per-instance winners, per-stratum win-rates, and a worst-Locus lever table. Figures are matplotlib→SVG (plots/{winrate_heatmap,error_ecdf,error_violin, paired_scatter,delta_hist}.py) with a two-section HTML bundle + a docs markdown report. A rerun deep-dive (compare/deepdive.py) overlays each library's corners (separate, toggleable entity paths) + GT + per-library error scalars on a scrubbable timeline over the worst-Locus cases. - Reuses the tuning wrappers (
from_params),Collector/records,pareto/*.jsonand stratification; the tune executor's frame loop was extracted todrive_cell+run_search_records(same crash-isolation) rather than duplicated. - Fixed a cross-library measurement bug: pupil_apriltags labels corners in a
different order than the GT/Locus/OpenCV convention (a fixed
[1,0,3,2]relabel, verified sub-pixel), so the collector's corner RMSE was ~200 px for apriltag and incomparable.AprilTagWrappernow normalises corners with that fixed adapter; the metric stays order-preserving (a genuine wrong-orientation detection must still surface as a large error), only the deterministic convention offset is fixed. - Parallel tuning + comparative-benchmarking harness (
tools/bench/tune/, Python-only, no Rust or runtime change). New CLI subcommands drive it: bench sweep— fan a declarative search space (tune/spaces/*.json) for Locus and competitors (OpenCV ArUco, pupil_apriltags) across cores; each worker is pinned to a single thread (RAYON_NUM_THREADS=1/cv2.setNumThreads(0)) so N workers never oversubscribe, and the accuracy sweep is byte-identical regardless of worker count. A native crash in a competitor detector is isolated to one errored cell instead of failing the sweep.bench tune— select the Pareto frontier over (maximize recall, minimize p99 pose error) under hard precision/latency gates, then verify latency serially (each frontier config in its own un-pinned subprocess: production threading for trustworthy timing, plus native-crash isolation so a corrupting competitor config can't take down the tune) and guard against regressing the render-tag tail/mean vs shipped Locus profiles. Writespareto/<library>.json.bench compare-report— knob-sensitivity heatmap (which lever moves which metric) + per-stratum tuned-Locus-vs-best-competitor deltas + HTML bundle.- Competitor detector wrappers are now fully parameterizable (
from_params), so comparisons tune all libraries fairly. Optional Bayesian search via the[tune]extra (pip install -e '.[tune]'); grid/random need no extra deps. - Results persist as tidy
tune_results.parquet/tune_configs.parquetreusing the existing Tier-1ObservationRecordsubstrate and stratification.
Changed
- Bench metric plumbing consolidated (behaviour-preserving, verified
byte-identical): the detection↔GT matcher (
tools/bench/matching.py), the percentile helper (metrics.percentiles), and the Pareto-frontier primitive (tools/bench/pareto_core.py) are now single implementations shared by the headline aggregator, the Tier-1 collector, and the plot layer.
Tests
- Numerical test hardening (tests-only, no production-code change): replaced
weak/smoke assertions with real numerical invariants and pruned redundant
tests across the Rust and Python suites. Highlights: the homography SoA test
now checks the canonical-square→quad reprojection instead of
data[0] != 0.0;test_decimation_accuracygates full-res and decimated corner error at 0.01 px (was an unassertederr1plus a looseerr2 < 1.5) and asserts decimation invariance (the observable +0.5-pixel rule); the GWLF synthetic test gates a measured 0.2 px cross-seed bound (was 1.0 px) and gains an idempotence check; tworobustness/geometry.rsno-op (prop_assert!(true)) degenerate-quad tests now assert the solvers returnNoneor finite (never NaN/Inf) output, verified over thousands of pathological quads; a new cross-thread/run-to-run detection determinism test and an image-edge robustness test were added; the tautologicalDetectorConfigdefault/builder tests were replaced by a builder-isolation invariant; and the duplicatedtest_vectorized_poseswas de-duplicated and tightened from atz > 0sign check to the full pose (tz = 0.80 m, near-identity quaternion). No public API or runtime-behavior change.
Changed
- Lint-suppression modernization: migrated production
#[allow(...)]lint suppressions to#[expect(..., reason = "...")](stable since Rust 1.81) acrosslocus-core/locus-py, so a suppression that stops being needed now fails the build (unfulfilled_lint_expectations) instead of lingering silently. The migration surfaced and removed ~20 stale or redundant suppressions — item allows duplicating the workspace lint table, andtoo_many_lines/too_many_arguments/similar_namesallows on functions that no longer trip the lint — and deleted the deadapply_adaptive_threshold_fast/apply_adaptive_threshold_with_paramsintegral-image path. Exported-API-sensitive lints (unused_self,struct_field_names) intentionally stay#[allow]because their firing is Cargo-feature-dependent. Comment/doc touch-ups inpose.rs,threshold.rs, andsegmentation.rsfavour why-over-what. No public API or runtime-behavior change.
Documentation
- Pose-covariance Phase 4 postmortem (
docs/engineering/pose_covariance_phase4_postmortem_2026-05-31.md): records the empirical falsification of Phase 4 (per-corner ERF residual-MSE covariance inflation), the last covariance-calibration lever the 2026-05-21 sweep had not ruled out. Measured end-to-end on branchfeat/activate-per-corner-empirical-noise(PR #290): KL stays at the baseline 13.93 against the §4 gate of < 0.5, unchanged even at the theoretical upper bound, because a per-corner diagonalσ_n²rescale cannot fix the off-diagonal structure of(JᵀWJ)⁻¹; the empirical column is also all-zero in production (no low-PPB tags route through ERF on the hub corpus). The ~700 lines of SIMD/FFI/SoA plumbing are not shipped (recoverable on the branch per the §6 precedent); the audit-harness knobs and empirical bound are the salvageable artifacts.pose_covariance_followup_2026-05-22.md§4/§5 amended with status banners. All four covariance phases are now closed. - EdLines SOTA follow-up postmortem (
docs/engineering/edlines_sota_followup_postmortem_2026-05-25.md): records two empirical falsifications of EdLines-architecture changes attempted on 2026-05-25 — a continuous arc-balance Phase-1 mode selector (replacing the static>40 %/<16 %axis-imbalance gate) and the design memo's Improvement #2 (Phase-5 8-DoF line parameterisation). Both reverted on the same day afterregression_render_tagrejected them. The postmortem catalogues the failure mechanisms (decoder-rejection invisibility for the selector; variance amplification through the line-intersection Jacobian for Phase 5) and the re-attempt conditions that would have to be resolved before either bet is worth re-opening.edlines_sota_design_2026-05-03.mdamended with status notes on F4, F6, and Improvement #2.