Repository navigation
ios: log meter-anchor corrections so marks can be checked against drift - #119
Conversation
Marks already move with the anchor's correction (37729cc); the 4.1 build moved only the meter. This logs every applied correction with the total move and turn since the meter was anchored, so a device walk away and back can tell a corrected map from uncorrected drift (#73). The tracking is cleared on a spatial reset and on Start over. Tests pin that marks keep their anchor-local place and their s, height and out. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
AidenJohnston
left a comment
There was a problem hiding this comment.
Adversarial review of f141a62 (U10, Part of #73), base t3/41-ground-and-meter-tap.
I checked the PR's main premise and it holds. 37729cc is on t3/ios-mvf, beta and this base, but not in 45017ba (the 4.1 build). On this branch, refreshMeterFromAnchor already moves the wall, the kept cameras and every mark's points with each yaw-only correction. Each correction maps the last pose's origin onto the new one, so the applied corrections add up to exactly YawCorrection(from: anchoredPose, to: pose). Storing a second, anchor-local copy on MarkedFeature would only give the two copies a chance to disagree, so dropping that part of the brief is justified, and the body says so. What this PR adds is the diagnostic, and the diagnostic has two gaps that could lead the U16 device check to the wrong conclusion.
Must fix
-
The log level makes the "no line" test unreliable (
ios/HouseScan/Runtime/ScanEngine.swift:478). The line goes out withRuntimeLog.capture.info. By default, unified logging keeps.infomessages only in memory buffers and doesn't write them to the log store. The body's Risk section says a field test can pick the line up withlog collect, but an archive collected after the walk may not contain it. Console.app also hides info messages unless Action > Include Info Messages is on. Either way, the tester sees nometer anchor correctedlines. The PR's own rule then reads that as "drift that ARKit never corrected", which is the wrong answer to the exact question #73 needs settled. Fix: log these lines at.notice(RuntimeLog.capture.notice, which is persisted), or at least fix the body andios/README.mdto say "live Console stream with Include Info Messages on" and droplog collect. -
A missing line has several causes besides uncorrected drift (
ScanEngine.swift:453,LiveCapture.swift:401). A line is written only if all of these hold: the frame hasmeterAnchor,frame.meterAnchorID == meterAnchorID,coverage != nil, and the correction is over 2 cm or 0.4 degrees.LiveCapturesetsmeterAnchorfromframe.anchors.first { $0.identifier == id }, so if ARKit drops the anchor (the 4.1 analysis's hypothesis (a) for the AR result drawing nothing), there is no line, and the marks also stop following the anchor. On the device, that looks exactly like "no line, marks off their objects = uncorrected drift". Fix: add a line that is always written. For example, when the scan is sent or the result opens (or on each phase change after the meter tap), log the tracking totals andcorrections, even when the count is 0, plus whether the last frame had the meter anchor. Also log one line when the anchor goes from present to missing in frames, and one when it comes back. The check then rests on lines that are there, not on a line being absent.
Should fix
- The line can't be matched to the packet or the video (
ScanEngine.swift:478). The 4.1 analyses lined events up using the manifestt(video time =tminus a constant). This line has only the unified log's wall-clock time. Adding the capture clock (frame.timestamporcaptureClock) and the camera's distance from the meter lets U16 put each correction on the video timeline and see whether it came at the walk back. - No test runs the engine's mark move (
AnchoredMarksTests.swift:42vsScanEngine.swift:458). The test appliescorrection.pointto the points itself, the same one-liner the engine uses, so deleting the features loop inrefreshMeterFromAnchorwould still pass. The app target isn't reachable from HouseScanKit tests. Moving "move these points with the correction" into a HouseScanKit helper that the engine calls, or adding an app-target test, would make it a real regression guard. The math in the current test is right: the stale corner comes out at s ≈ 1.803 against 2.0, so> 0.05passes with room to spare. - iOS CI hasn't run on this head.
iOS buildwas skipped because the PR is a draft; the only checks were PR Size (pass) and CodeRabbit (skipped review). Before merging, run the iOS workflow on the branch withworkflow_dispatchor mark the PR ready, so the build,AnchoredMarksTestsandMeterAnchorCorrectionTestsactually run.
Notes
- Mental compile, with nothing found:
OSLogInterpolationtakesFloatwithformat: .fixed(precision:), andIntinterpolates.self.is explicit onstate.phaseinside the autoclosure, matching line 1078.CoverageMap.wallis a non-optionalWallFrame, socoverage.map { $0.wall.meter }type-checks. The newpublic let anchoredPoseis set in the onlyinit, and the synthesizedEquatableandSendablestill hold. No code comparesMeterAnchorTrackingvalues. The test helpers (MeterAnchorCorrectionTests.turned/wallHitPose,standardWall, thenearlyEqualoverloads,WallFrame.world(_:)/wallPoint(_:)) exist and are visible to the test target. I also tracedcorrectionsSinceAnchoredAddUpby hand: the totals come out to (0.2, 0, 0.05) and 3 degrees. - Clearing
meterTrackinginresetSpatialStateandresetAllis safe. EverysetMeterAnchorcall site (markMeter,refitWallToDetectedPlane) sets it again. It also stopsshowResultInCamerafrom using a pose from the old world frame when a result survives a spatial reset. - A re-fit (
setMeterAnchorfromrefitWallToDetectedPlane) restarts the totals from the re-fitted pose. The doc comment says so. U16 should know this if a re-fit happened in the run. - Rate: every applied correction writes a line, including on pose-only frames. If a non-LiDAR anchor jitters by more than 2 cm while ARKit optimises its map, that's up to one line per pose frame. That's acceptable for a diagnostic.
- Scope: three files. No
project.ymlorproject.pbxprojchanges, and nothing touchesfeature/project,markMeter,ingest,refineGroundorshowResultInCamera. KeepingWallFrame.along/outwardturning with the anchor comes from 37729cc, and the body says so; this PR doesn't decide it. - Public-repo check of the diff, body and commit message: no names, meter numbers or serials, addresses, emails, private content or media.
- The PR body has Part of #73, the base, the stacking on #108, CI-only versus device-only items, the files touched, the dependencies and the attribution line. "Part of" is right: whether #73 can close depends on the U16 walk.
Verdict: blocked (two small logging changes: the log level, and a line that is always written so a missing line isn't the evidence)
🤖 Generated with Claude Code
AidenJohnston
left a comment
There was a problem hiding this comment.
Fresh adversarial review of f141a62 (unit U10, Part of #73), stacked on #108.
I checked the premise first. The PR says the 4.1 build (beta 45017ba) moved only the meter, and that 37729cc, which already moves the wall, kept cameras and marks with the anchor's full yaw correction, landed after that build. Both are true: git merge-base --is-ancestor 37729cc 45017ba fails, and 45017ba's refreshMeterFromAnchor copies only the translation and then reprojects the fixed world points. So the placement half of the #73 hypothesis is already fixed on t3/ios-mvf. This PR adds the diagnostic the issue asked for ("log the anchor's transform over time and how far it has moved since the tap") and clears the stale tracking on resets.
Must fix
None found.
Should fix
- The drift log may not reach the person running the U16 walk, and a missing line reads as the wrong answer.
ios/HouseScan/Runtime/ScanEngine.swift:478logs at.info. The unified log keeps info messages in memory only, and Console.app hides them unless Action > Include Info Messages is on. The PR body's decision rule is "no line, with the marks off their objects, means drift ARKit never corrected". Scenario: the tester followsios/README.md(open Console, filter by subsystem), leaves info messages off, or collects the log after the buffer has turned over. They see nometer anchor correctedlines and conclude it was uncorrected VIO drift, when ARKit did correct the map. Fix: log this one line at.notice(persisted, shown by default), or add an explicit U16 step to turn on info messages and first confirm that the tap-timemeter marked on ...line is visible. Then a missing correction line means something. The line holds only floats, ints and the phase enum, so.noticeraises no privacy concern.
Notes
- Deviation from the brief is documented and sound. The brief asked for anchor-local copies on
MarkedFeatureplusAnchoredPointshelpers. The incremental approach is equivalent here. Each appliedYawCorrection(from: pose, to: anchor)maps the old anchor frame onto the new one, sub-threshold nudges aren't dropped (poseisn't advanced until the threshold is reached), and under.gravityalignment the dropped tilt is noise. So the composed corrections keep every mark's anchor-local coordinates, which is whatAnchoredMarksTests.swift:44-45pins. A second anchor-local copy would be redundant state. This is not a silent design decision: the PR body explains it. - Mental compile passes.
MeterAnchorTrackingkeeps its explicitinit(pose:), so no memberwise init appears andpose.map(MeterAnchorTracking.init)(ScanEngine.swift:1677) still resolves. The synthesizedEquatablecoversanchoredPose(simd_float4x4isEquatable). The unlabeled tuple return atMeterAnchorTracking.swift:98converts to(moved:yaw:). Every public member used across the module boundary is public:anchoredPose,corrections,sinceAnchored,YawCorrection. The privatestatic func origindoesn't clash with the localorigininupdate(to:). The OSLog interpolations (Floatwith.fixed(precision:),Int,self.state.phase.rawValuewithprivacy: .public) match patterns already in the file (ScanEngine.swift:1072,:1078). The helpers the tests use all exist with those signatures:MeterAnchorCorrectionTests.turned(_:degrees:),wallHitPose(meter:outward:),standardWall(),WallFrame.world(_:)/wallPoint(_:), and thenearlyEqualoverloads. - I checked the test arithmetic by hand. On
standardWall(), the stale point at s = 2.0 lands at s ~ 1.803 after the 3 degree turn and (0.2, 0, 0.1) move, about 20 cm off, soAnchoredMarksTests.swift:54-55(> 0.05) holds. IncorrectionsSinceAnchoredAddUp, the 0.1 degree turn about the anchor's own origin has zero move and is below 0.4 degrees, so it returns nil. The 5 cm move then gives the expected (0.2, 0, 0.05).wallPointclamping doesn't bite, because a single-piece wall's span is unbounded. - The tests run on HouseScanKit, not the engine.
marksKeepTheirPlaceRelativeToTheMeterrepeats the engine'spoints.map(correction.point)and checksWallFrame.wallPoint, which is whatScanEngine.projectreads (ScanEngine+Actions.swift:287). That's the best possible without an app-target test. It partly overlapsMeterAnchorCorrectionTests.rotationBeforeDisplay, but it adds the s, height and out checks and the 4.1 contrast. - Rate limit. The brief said "rate-limited". The PR relies on the 2 cm / 0.4 degree threshold. Plain
ARAnchors only move on map corrections, so volume should be low. A correction also runscoverage.applyand republishes, which costs far more than the log line. Acceptable. - Totals reset on re-fit.
setMeterAnchorfromrefitWallToDetectedPlane(ScanEngine.swift:430) starts a newMeterAnchorTracking, so "since anchored" means since the last re-fit. The doc comment atMeterAnchorTracking.swift:73-74says so, and re-fit happens only during the close-up, before any mark exists. - Resets.
resetSpatialStateandresetAll(ScanEngine.swift:1162,:1610) now clearmeterTracking. Side effect:showResultInCamera(:1244) can no longer attach a result to a stale pose from a dropped world frame after a spatial reset. That's an improvement, and it doesn't touch U1's code. ScanEngine.swift:455:?? 0is dead after thecoverage != nilguard. Harmless.- Scope. 3 files, no
project.yml/project.pbxprojedits, andmarkMeter,refineGround,feature,projectandshowResultInCameraare untouched. Trial merges (git merge-tree) against #100, #117, #109, #104, #102, #99, #52, #87, #56, #120 and #123 are all clean. - Public-repo check. The diff, PR body and commit message contain no names, meter numbers or serials, addresses, emails, private material or media.
- PR body. Has Part of #73, base and stacking order (#108 first), what was verified vs what needs CI or a device, files touched, dependencies and the attribution line.
- CI.
gh pr checks 119: PR Size passed, CodeRabbit skipped (draft), and iOS build skipped because the workflow skips drafts. No iOS run exists on this head (gh run list --branch t3/41-anchored-marks). Nothing here has been compiled. Before ready, dispatch the iOS workflow on this branch, or mark it ready, so the build andAnchoredMarksTestsactually run. - #73 stays open. Whether the 4.1 symptom was a corrected map or uncorrected drift is still for the U16 device walk, as the PR says (see Should fix 1).
Verdict: ready-with-notes
🤖 Generated with Claude Code
|
Note Review skipped Codemetrics is installed on this repository, but it hasn't been added to your team yet. To enable AI code reviews for this repo, go to your dashboard and add house-scanning-master to your team's repositories. |
|
Note Review skipped Codemetrics is installed on this repository, but it hasn't been added to your team yet. To enable AI code reviews for this repo, go to your dashboard and add house-scanning-master to your team's repositories. |
|
| let moved = points.map(correction.point) | ||
|
|
||
| #expect(nearlyEqual(wall.meter, SIMD3(newPose.columns.3.x, newPose.columns.3.y, newPose.columns.3.z))) | ||
| for (point, after) in zip(points, moved) { |
There was a problem hiding this comment.
This test moves the wall and points directly, without running ScanEngine.refreshMeterFromAnchor. It would still pass if the engine stopped updating stored feature points, so it does not catch the regression it is meant to prevent. An engine-level check would cover that path.
Prompt To Fix With AI
This is a comment left during a code review.
Path: ios/HouseScanKit/Tests/HouseScanKitTests/AnchoredMarksTests.swift
Line: 42-45
Comment:
**Engine path remains untested**
This test moves the wall and points directly, without running `ScanEngine.refreshMeterFromAnchor`. It would still pass if the engine stopped updating stored feature points, so it does not catch the regression it is meant to prevent. An engine-level check would cover that path.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
#108 now carries t3/ios-mvf's anchor repair, where SpatialUpdate applies the anchor correction and refreshMeterFromAnchor is gone. The drift log moves with it: - applySpatialUpdate writes the correction line once the correction has moved the wall and the marks; the step is how far the meter moved. - MeterAnchorTracking keeps both parts: the base's correction log (for poses captured earlier) and this PR's totals since anchored. A re-fit's anchorAgain starts the totals from the re-fitted pose, so the re-fit's own move is not counted as a correction, and keeps the log. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… on a missing line Review of #119 (Part of #73); the same change as beta's 1b3229a, on the SpatialUpdate structure: - The correction lines are .notice, so `log collect` and Console without "Include Info Messages" both show them, and they carry the capture clock (t) and the phone's distance from the meter, to line them up with the packet and the video. - One line when the frames stop carrying the meter's anchor, and one when they carry it again (MeterAnchorPresence). - A summary on every phase change once the meter is anchored, with zero corrections too, and always on sending the scan and opening the AR result: corrections since anchored, frames with and without the anchor, the latest frame's sighting. A re-fit starts the counts again with the totals. Test: AnchoredMarksTests.anchorPresenceReportsOnlyChanges. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex Review: Didn't find any major issues. Breezy! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
Updated for review. Head is now Base merge
Sam's #120 note (stored positions and anchor corrections)I checked whether the same problem applies here. The drift totals compare raw anchor poses with each other ( Review items
CIThe previous run (36305052363) failed with "screen.gapRequest never appeared" in On I also ran it on the Mac mini with Sam's review-clear is still needed. Nothing here has been run on a device: the drift lines need the U16 walk. 🤖 Generated with Claude Code |
Part of #73
Base:
t3/41-ground-and-meter-tap. Stacked on #108 and must merge after it; when this goes ontobeta, merge #108 first.What changed
In build 4.1 (run 2), a window outline stayed where it was tapped while the meter pin followed ARKit's corrections to the meter's anchor, and the outline ended up on the panel boxes. In that build
refreshMeterFromAnchorcopied only the anchor's translation into the wall, and the marks kept their world points.That part is already fixed on
t3/ios-mvf. Commit 37729cc (the #10 review's anchor-turn fix, merged after 4.1 was built) applies each anchor correction, turn included, to the wall, the kept cameras and every mark's tapped points as one rigid move. A mark's position relative to the meter, and so its exported s, no longer changes when ARKit corrects its map. This PR adds what that fix left out:MeterAnchorTrackingnow keeps the anchor's pose from when the meter was anchored and counts the corrections applied since. Each applied correction writes oneRuntimeLog.captureline with how far that correction moved the meter and turned the wall, the total move (x, y, z) and turn since the tap, how many marks moved with it, and the phase. On a walk away and back, a line means ARKit corrected its map and the marks followed. No line, with the marks off their objects, means drift that ARKit never corrected, which an anchor can't fix. The rate is already limited: a correction applies only once the anchor has moved 2 cm or turned 0.4 degrees since the last one.resetSpatialStateandresetAllnow setmeterTrackingto nil alongside the anchor id, so no tap-time pose survives into a new world frame. The next meter tap sets it again (setMeterAnchor). Replays have no anchor, so their marks keep their world points as before.AnchoredMarksTests). The anchor moves (0.2, 0, 0.1) m and turns 3 degrees about gravity. A window's two corners and a fence tap keep their place in the anchor's frame, and their s, height and distance out from the moved wall (whatScanEngine.projectreads) match the tap. The test also checks that a point left at its tapped world position, as in 4.1, comes out about 20 cm off in s. A second test checks the totals after two corrections, with a sub-threshold nudge in between.Why this differs from the brief
The brief asked for anchor-local points stored on
MarkedFeatureand rebuilt withanchor.transform * local, plus newAnchoredPointshelpers. Since 37729cc,refreshMeterFromAnchordoes the same thing incrementally: it appliescorrection.pointto each mark, and the correction is the anchor's move and turn about gravity. A second copy of each mark in anchor coordinates would be redundant state that could disagree with the first. So this PR leavesMarkedFeature,ScanContract.swiftandScanEngine+Actions.swiftunchanged, and it doesn't conflict with #52's edits tofeature/project.The brief also said not to turn
WallFrame.along/outwardafter the tap. 37729cc already turns them with the anchor, throughWallFrame.apply, andCoverageMap.applymoves the kept cameras with them, so coverage stays consistent. This PR doesn't change that.Verification
MeterAnchorTracking(the engine'ssetMeterAnchorandrefreshMeterFromAnchor, and the tests inSpatialSourceTests.swift). The new stored property is set in the only initializer, so the synthesizedEquatablestill covers it.AnchoredMarksTestsand the existingMeterAnchorCorrectionTests.Needs CI or a device
OSLogfloat formatting (format: .fixed(precision:)onFloat).capturelog'smeter anchor correctedlines to tell a corrected map from drift, and check whether the outline stays on the window. That check decides whether AR overlays drift off their objects after walking away (window outline drawn over the panel) #73 can close.Risk
log collect. Putting it in the packet would be a contract change (unit U3).Files touched
ios/HouseScanKit/Sources/HouseScanKit/Geometry/MeterAnchorTracking.swift:anchoredPose,corrections,sinceAnchored.ios/HouseScan/Runtime/ScanEngine.swift:refreshMeterFromAnchorlogs through the newlogAnchorCorrection;resetSpatialStateandresetAllclearmeterTracking;reprojectFeaturesdoc comment.ios/HouseScanKit/Tests/HouseScanKitTests/AnchoredMarksTests.swift(new).Dependencies
t3/41-ground-and-meter-tap, unit U2), which changesingestand the meter tap next to this code. Merge it first.featureorproject.Checklist
🤖 Generated with Claude Code