Skip to content

ios: guided wall scan from the meter to a battery spot - #10

Open
SamGu-NRX wants to merge 312 commits into
mainfrom
t3/ios-mvf
Open

SamGu-NRX wants to merge 312 commits into
mainfrom
t3/ios-mvf

Conversation

@SamGu-NRX

@SamGu-NRX SamGu-NRX commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

What changed

The iOS app runs the homeowner's whole scan, from finding the electric meter to seeing where a Base Power battery could go. It sends the server the measurements it needs to decide, and says what it didn't see.

  1. Meter. The homeowner points at the meter and taps. The scan anchors on the wall surface there: a plane ARKit detected, or ARKit's estimated vertical plane with a wider meter error in the export. The close-up photo takes itself once the picture has been sharp, lit and centred for 0.6 s. Vision's accurate mode reads the meter number, with the focus and size checks from the close-up eval (experiments/meter-closeup on t3/meter-closeup). Then the app asks "Which number is on your meter?" with up to three candidates, barcode-confirmed first, and "None of these". It never fills the number in. A close-up that passes the focus check also counts as one camera position toward coverage.

  2. Wall walk. The homeowner walks left, then right, along the wall. The phone keeps a photo every 0.5 m or 15° of turn, or when the frame shows wall it hasn't seen. It keeps photos only while tracking is normal and the picture is sharp.

    • A haze covers wall and ground the phone hasn't seen, and a strip at the bottom shows the wall unrolled with the meter at zero.
    • A dotted path and an aim ring show where to stand and where to point. Coaching replaces the instruction while something is wrong.
    • The first request is the ground in front of the meter, the stretch every check needs. "Wall ends here" is offered whenever the homeowner is walking a side, and "Can't get there" ends the wall where the phone is, never beyond the path already walked. Before either, the strip shows a dashed line where the end would land and dims what it would leave out.
    • After ending, the homeowner says whether the wall turns a corner or something blocks it. At a corner they mark the next wall and keep walking, and it becomes the next entry in walls[].
    • Before leaving, they tilt up over the meter and say whether anything is overhead.
  3. Marking. They can pin a gas meter, AC unit, door, window, driveway edge or fence as they pass. Marks past an end still export, because the server places them on the wall's continued line and decides which it can use.

  4. The ground question (new). The feature review opens with "What's on the ground along this wall?": grass, mulch, gravel, concrete, driveway, deck, or "Not sure". The camera can't tell mulch from soil, and without a ground type every real scan ended with the server's "Ground under the battery" check unsure. An answer is exported as a ground patch of that type covering only the ground the coverage saw. "Not sure" sends no patch. The question doesn't block "Looks complete".

  5. What the server needs. coverage.observed follows "What settles each check" in server/README.md (t3/server at 9e42970):

    • wall entries with out_ft, the height up the wall the phone saw from two positions;
    • ground entries with how far out the ground was seen;
    • the clear space in front of the wall that the walked path proves, less the wall's position error for its source;
    • the overhead band from the tilt-up;
    • ground past a limit end.
      Each wall says how its line was found in walls[].source. A wall through a plane ARKit detected is plane, and a tap on an estimated plane or a replayed wall is tap. Nothing yet builds a wall line from the LiDAR mesh. Ground entries are split at corners, and every span and reach rounds inward, so no entry claims ground the camera didn't sample (see Why it works this way).
  6. Depth on LiDAR phones. The session runs scene depth and mesh reconstruction. A cell counts only where depth at its projection matches its distance. A nearer reading marks it hidden (a bush or bin in front of the wall), and a later view without depth can't clear that. The walk then asks the homeowner to look past it.

  7. One more view. After the walk, the phone asks for the most useful stretch it hasn't seen from two positions, in one sentence, and moves on once the camera has covered it. It does the same for evidence the server lists in missing_evidence, including a wall seen too low: those requests carry the height the view must exceed. "I can't get there" leaves the gap for an installer.

  8. Result. The phone posts scene.json to POST /v1/placements on the hosted server (Config/Shared.xcconfig) and shows the answer two ways. The first is a 3D model of the wall with the battery, the cable and the clearances. The second is the battery on the live camera, placed on the meter's anchor, where people and objects in front of it hide it. The cable now bends at every corner of the wall chain it passes, where before it was projected onto the battery's own wall. Unknown missing_evidence kinds from a newer server go to installer review; the whole answer no longer fails on them.

  9. Getting a scan off the phone. "Share scan" shares a capture packet 1.1 (S4's spec, packet/README.md on t3/packet at d5439cf). New since 1.0:

    • top-level planes with boundary_m;
    • confidence on all ARKit depth;
    • session.device.mesh_classification_enabled;
    • depth_frames between photos at 2 Hz. They are written to disk as they arrive, stop at 300 frames, and are recorded only during the close-up, the walk and "one more view".
    • a writer entry point for S6's on-device estimated depth with sigma.

    Photos leave the phone only through this share sheet, and no location or heading is recorded.

The capture logic is a Swift package, ios/HouseScanKit. It imports only Foundation, simd, CryptoKit (packet hashes) and ImageIO, so its 366 tests run on macOS. HouseScan/Runtime feeds it ARKit or a replay and runs the flow; HouseScan/UI draws the screens. ScanContract.swift is the whole interface between the two.

For the Simulator (contract C4):

  • -replay <folder> plays a Measure Lab v2 session instead of the camera.
  • -autopilot drives every step, including a gap request, and answers the ground question with mulch.
  • -serverURL <url> picks the server; -sampleResult answers with a bundled sample that every screen labels as an example.
  • -uiDemo… arguments open single states. -uiDemoGroundQuestion is new.

Each screen change is logged as STATE=<name>. ios/README.md lists every argument and the steps to run on an iPhone.

Aiden's PRs into this branch are part of it:

#37, #47, #48 and #51 were merged before their reviews cleared. Their open findings are listed under Not done. #49, #52, #53, #55, #56 and #57 are open against this branch.

Why it works this way

  • Coverage counts a cell only when two positions saw it. Each 6-inch cell is covered once each of its rows has been seen from two camera positions at least 0.25 m apart with normal tracking. The coverage map keeps the cameras and rebuilds itself when the wall moves (a corner, a better meter anchor).
  • The phone reports the height it saw; the walk asks for less. The server counts a wall stretch as seen for a check only when out_ft exceeds that check's height: 6.5 ft for gas and openings, 3.29 ft for the battery's backing, 1 ft for the cable.
    • What gets reported: per stretch, the highest row seen from two positions with no gap below it.
    • While the ground is still a chest-height guess: the guess's 0.3 m error comes off the reported height, and the rows down to that error must be seen too.
    • What the walk asks for: the wall up to 4.5 ft (wallWalkHeight). That is the most a view from the 2 m stand-off can show while it also shows the ground in front of the wall; the long-side half angle of a phone's image is about 30° to 31°.
    • Above 4.5 ft: where a check needs more, the server's answer asks for that stretch at the height it needs, and the gap loop raises it as the next view. Rows are sampled up to 7.5 ft (wallCaptureHeight), so a tilt-up can show up to 7.5 ft.
    • Neither number is measured: no calibration run chose 4.5 ft or 7.5 ft.
  • The ground patch stops where coverage stops. The server treats unrecorded ground within its error margin of the footprint as unsure, so a patch drawn wider than what was seen would pass ground nobody looked at. The patch is built from the ground entries as written. It leaves out the wedge outside a convex corner, and it is cut at the neighbouring wall at an inside corner. Its wall-side edge starts 0.001 ft behind the wall line. That is inside the house, where the server models no ground: on the hosted server a rotated wall whose patch started exactly on the line left ground_surface unsure.
  • Ground entries are split at corners. When one ground entry crosses a corner, the server's band_polygon includes the sector outside it, and no camera sample lies there. The export splits every ground entry at the chain's corners after merging.
  • Wall source says how the line was found, and the phone subtracts that source's error. The server gives a detected-plane wall 0.75 ft of default error and a tap 0.3 ft. The walked clearance the phone reports subtracts the larger error of the two wall pieces at each cell. The phone mirrors those defaults from rules.yaml in ServerErrorDefaults.swift; they go stale if the rules change.
  • Capture overlays are projected from the camera pose. The haze, path and ring are world points projected through each frame's pose and intrinsics. The same code runs on a live frame and a replayed keyframe, so the Simulator tests the real overlay geometry. Nothing occludes them. The AR result is the exception since ios: AR result is hidden by people and objects in front of it (#41) #51: on a live camera it is RealityKit content on the meter's anchor, and over a replay it is still projected.
  • An end goes where the homeowner is. In the first phone run, "Can't get there" put each end at the unbroken covered stretch from the meter. That stretch was 0 because nothing had asked for the ground by the meter, so every photo past the ends was dropped. An end now goes at the phone's position along the wall, kept within the path it walked on that side (WalkedEnd). Unseen stretches in between stay unseen, and the server asks for them. A photo taken past an unexplored end that sees nothing between the ends isn't kept, and the card says so.
  • Losing tracking late doesn't cost the scan. If ARKit can't relocalize within 20 s, the find-meter, close-up and walk steps start again from the meter. On the feature review the scan now stays: "Add something" waits for tracking, and "Looks complete" uploads, because the upload needs no live frame. During "one more view" the timeout counts as "I can't get there".

UI changes

New since the last description, all from the scripted demo engine at dd44fc3. The ground question is new. The number picker shipped earlier but had no capture here. These three images still need uploading to GitHub from a signed-in browser. Until then they are local files on the build Mac:

Ground question Answered Meter number picker
/tmp/hs-pr-shots/19-ground-question.png /tmp/hs-pr-shots/20-ground-answered.png /tmp/hs-pr-shots/21-number-picker.png
Six ground types in two columns, "Not sure" across the width, one line on why Folds to one row with "Change"; the review below moves up with it Up to three candidates, the barcode match first; the numbers are made up by the demo engine

The rest are from earlier commits and still show the current screens. The replay rows are the real engine on the synthetic replay at beede15 with the bundled sample result. Row 9 is a real server answer from 0876e03. "Demo" rows come from the scripted demo engine. No image shows a real home or dataset photos.

Onboarding: what the scan does and how long it takes Find the meter with a reticle and one button Demo: the close-up after two failed tries offers Can't get a clear shot
1. Onboarding 2. Find the meter 3. Close-up, "Can't get a clear shot" (demo)
Wall walk with haze, arrow and the wall strip Demo: what is at the end of the wall, two full-width answers Review of marked features with the window question
4. Wall walk 5. End of the wall (demo) 6. Feature review, before the ground question
The gap request satisfied Uploading with no server: the sample is labelled Result from the server: an installer will take a look
7. One more view, satisfied 8. No server: "Making a sample result" 9. A server's answer (0876e03)
The example spot drawn on the wall Demo: Slow down coaching Demo: relocalizing shows the saved meter photo
10. The spot on the wall 11. Coaching (demo) 12. Lost place (demo)
Demo: a refused window mark Demo: offline, scan saved on the phone Demo: camera access off
13. A refused mark (demo) 14. Offline (demo) 15. Camera denied (demo)
Demo: a passing result under approved rules Demo: wall walk at the largest text size Demo: result at the largest text size
16. A pass (demo) 17. Walk at AX5 (demo) 18. Result at AX5 (demo)

The autopilot on the synthetic replay at cbe0440, from onboarding to the AR view, at 3× replay speed:

house-scan-flow.mp4

Verification

  • swift test --package-path ios/HouseScanKit -Xswiftc -warnings-as-errors: 366 tests in 58 suites pass at d02cc11. They include:

    • every exported scene validated against the vendored scene.schema.json;
    • drift tests comparing the vendored schemas byte for byte with the heads of t3/server (9e42970) and t3/packet (d5439cf). CI now fetches both branches, so these compare instead of skipping; a server schema change fails every iOS run until the copies are refreshed.
    • a port of the server's settlement rules, checked against server/scene.py;
    • ground patches round convex, concave and sharp corners and at chain ends;
    • NoUnseenClaimsTests, one case per overclaim path the review found.
  • CI (Xcode 26.6) runs those tests, the XcodeGen drift check, the device build and every UI test with each screen's accessibility audit enforced. Green at d02cc11 (run 36279804539).

  • Local, Xcode 27, iPhone 17 Simulator (iOS 26.5), at e9e405d, before the field fix:

    • testFullFlowFromReplay and testFullFlowFromLidarReplay pass.
    • S4's validator (uv run python -m packet validate from packet/ at d5439cf) accepts both exported packets as 1.1: 32 photos each, and 38 depth frames from the LiDAR replay.
    • A package-built packet with ARKit and estimated depth, a mesh and two planes (one with an L-shaped boundary) also passes. The same packet with its boundary left in anchor coordinates fails, as it should.
    • The exported scene.json validates against the server schema. It carries source: tap, a mulch patch and out_ft on every wall entry.
  • The hosted server (redeployed at 930e8e5) answers that scene manual_review. Its three requests are correct for the fixture:

    • a wall view with out_ft 6.500001 over −3.5 to 6.5 ft, for the gas, opening and wall-equipment checks, because the synthetic camera sees the wall only 6.5 ft up;
    • ground out to 12.6 ft;
    • the unexplored left end.

    With the mulch patch, ground_surface is unsure only because the footprint sits within measurement error of the patch edge (margin); before, no surface was recorded at all. wall_backing, facing_gap, headroom and both route checks pass.

  • Two fresh reviews at 35ac3f2: an Opus review of the whole diff and an Astra review of one question, whether any path exports coverage, a ground patch or a wall the camera didn't see. Fixed from them:

    • a relocalization timeout on the review screen wiped the scan;
    • a spatial reset left marks, skipped gaps, the result and the old close-up behind;
    • live frames could reach the engine out of order;
    • depth frames and sensor streams were recorded in the wrong phases and after a failed upload;
    • ground entries crossing a corner claimed the unseen outside sector;
    • a view without depth could cover a cell depth had marked hidden;
    • a blurry close-up, or one being retaken, could count toward coverage;
    • facing clearance used the tap error on plane walls;
    • a late photo save could join the walked path across a tracking break;
    • spans and patch edges rounded outward;
    • two drift tests passed without comparing anything.

    The Astra reviewer then re-checked at e9e405d. Its fixes held, and it found four more paths, fixed in b7f51ba:

    • the server works out each corner from the rounded baseline, so a split entry could still cross the corner by 3e-5 ft;
    • a view without depth kept its credit after a depth view proved the row hidden;
    • a guessed ground that sits too high left the real wall foot unseen;
    • a pending overhead photo joined the walked path across a tracking break.

    The caretaker's Astra review of the field fix found two more, fixed in 5c8c739: a nearly parallel aim could put an end hundreds of metres away, and an object past an end before a corner named a wall the scene leaves out.

Not done, and what could go wrong

  • One real-iPhone run so far, and it lost the walk. Aiden ran TestFlight build 657ab28 at a mock wall (experiments: device field test runbook and run 1 of the TestFlight build #23, experiments/device-field-test/README.md on t3/device-field-test). "Can't get there" set each end at the unbroken covered stretch from the meter, which was 0 because the walk never asked for the ground in front of the meter. The scene therefore reported no wall and no ground. The fix (3c7b83a, merged at 75dea41) is described under Why it works this way; issues "Can't get there" during the walk can put the wall's end at the meter, and the rest of the walk is dropped #24, Tilt-down prompts never ask for the ground in front of the meter #28 and The "Is this the end of the wall?" prompt never appeared on a 19 ft walk #29 carry the details for Aiden's retest. His marks against a tape, from the meter's centre line: window right edge −0.5 in, sill +1.9 in, left edge +5.4 in, gas stand-in 4.8 in nearer the meter. All four are inside the server's allowance for AR taps (0.3 ft plus 0.16 ft per ft from the meter). These are the first device measurements.

  • Most of the live path hasn't run on a device. Four things have only built and run in the Simulator: the live ARKit path (meter raycast, anchor refresh, ground refinement, relocalization), the plane pose and boundary_m math, depth-frame recording and the 7.5 ft capture height. Apple's documentation matches the plane convention the writer uses: since iOS 16 the anchor transform isn't rotated, and the extent's rotationOnYAxis is applied to the geometry. No device has confirmed its direction. A wrong sign would drop boundaries (logged), not break the packet. Every capture threshold is a guess, stated where it is defined.

  • The 4.5 ft walking height rests on an assumed field of view. If a phone's view is narrower than the 30° to 31° half angle assumed, the walk will still ask for some tilt-ups. The camera intrinsics recorded in runs 2 and 3 can settle this.

  • The fixture never settles the 6.5 ft checks. The synthetic camera sees the wall 6.5 ft up at most, and its three tilt-up frames share one position. So every replay answer asks for one more wall view, and the autopilot says "I can't get there". A second tilt-up position in Tools/make-synthetic-replay.swift would fix that.

  • Coverage still stands in for visibility in three places:

    • A cell is judged by points on its row edges, not its whole area, so a small obstruction between sampled rows can go unnoticed.
    • The walked path counts as clear space between the wall and the phone even if a hedge stands in it.
    • The overhead answer covers the upper wall, not the volume above the battery; the homeowner's "nothing overhead" carries that.

    S6's Map3D (ios: live 3D map as the coverage model where depth exists #21) replaces this coverage model. This PR stops adding occlusion to the old one.

  • One ground answer for the whole wall. A mixed wall can't be described. Answering "Driveway" or "Deck" fails the ground check at every spot, which the rules intend. Bare soil has no answer of its own, so a homeowner with a dirt bed will likely pick "Not sure".

  • Wall source and error. A wall on a detected plane gets 0.75 ft of default error on the server, while a tap on ARKit's estimated plane gets 0.3 ft. The estimated tap is likely the less accurate of the two. The phone reports the source truthfully and leaves the error values to the server's rules.

  • The UI tests never see a real server's request. They run on the sample result. The real-answer path is covered by package tests on a recorded server answer.

  • Timing flakes. ScreenStatesUITests/testAddSomethingFromTheReviewMarksOnTheCamera failed once locally, tapping a chip while the review fades back in, and then passed 3 of 3. The full-flow audit now waits 6 s before its second pass, after a system notification banner failed a contrast check on CI (run 36264789032).

  • Main-thread copy at upload. Copying the mesh and planes for the packet runs on the main actor. Nobody has measured how long that stalls on a large mesh.

  • The ADVIO walk can't close its gap loop. Under the two-position rule it never covers the ground beside any assumed meter, so the autopilot skips the gap. That replay looks along a path, not at a wall.

  • Open findings on merged PRs, posted to Aiden as follow-ups:

  • The 3D view frames the whole marked wall. On a long wall the battery looks small.

Checklist

  • The scope is focused; related changes belong together. This is one feature, the capture app.
  • I explained what changed and why
  • I included before/after images for visible changes: the three new captures above still need uploading
  • I included a video for motion or interaction changes

🤖 Generated with Claude Code

@vercel

vercel Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated
house-scanning-web Ignored Ignored Preview Sep 26, 2026 11:12am UTC

@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 330 files, which is 230 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

Usage-priced reviews support at most 300 files.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 47bb261d-0380-4e02-8cc5-70ad458154d2

📥 Commits

Reviewing files that changed from the base of the PR and between f852207 and c820cf1.

⛔ Files ignored due to path filters (123)
  • ios/HouseScan/Assets.xcassets/AppIcon.appiconset/AppIcon.png is excluded by !**/*.png
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00001.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00002.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00003.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00004.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00005.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00006.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00007.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00008.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00009.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00010.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00011.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00012.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00013.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00014.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00015.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00016.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00017.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00018.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00019.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00020.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00021.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00022.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00023.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00024.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00025.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00026.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00027.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00028.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00029.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00030.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00031.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00032.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00033.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00034.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00035.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00036.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00037.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00038.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00039.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00040.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00041.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00042.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00043.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00044.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00045.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00046.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00047.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00048.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00049.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00050.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00051.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00052.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00053.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00054.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00055.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00056.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00057.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00058.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00059.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00060.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00061.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00001.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00002.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00003.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00004.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00005.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00006.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00007.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00008.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00009.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00010.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00011.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00012.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00013.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00014.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00015.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00016.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00017.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00018.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00019.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00020.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00021.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00022.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00023.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00024.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00025.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00026.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00027.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00028.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00029.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00030.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00031.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00032.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00033.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00034.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00035.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00036.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00037.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00038.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00039.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00040.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00041.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00042.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00043.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00044.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00045.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00046.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00047.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00048.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00049.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00050.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00051.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00052.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00053.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00054.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00055.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00056.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00057.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00058.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00059.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00060.jpg is excluded by !**/*.jpg
  • ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00061.jpg is excluded by !**/*.jpg
📒 Files selected for processing (330)
  • .github/labels.json
  • .github/workflows/ios.yml
  • CONTRIBUTING.md
  • Makefile
  • ios/.gitignore
  • ios/Config/Info.plist
  • ios/Config/Shared.xcconfig
  • ios/HouseScan.xcodeproj/project.pbxproj
  • ios/HouseScan.xcodeproj/xcshareddata/xcschemes/HouseScan.xcscheme
  • ios/HouseScan/ARCaptureView.swift
  • ios/HouseScan/Assets.xcassets/AccentColor.colorset/Contents.json
  • ios/HouseScan/Assets.xcassets/AppIcon.appiconset/Contents.json
  • ios/HouseScan/Assets.xcassets/Canvas.colorset/Contents.json
  • ios/HouseScan/Assets.xcassets/Caution.colorset/Contents.json
  • ios/HouseScan/Assets.xcassets/Chalk.colorset/Contents.json
  • ios/HouseScan/Assets.xcassets/Contents.json
  • ios/HouseScan/Assets.xcassets/CoverageCovered.colorset/Contents.json
  • ios/HouseScan/Assets.xcassets/CoverageHidden.colorset/Contents.json
  • ios/HouseScan/Assets.xcassets/CoverageSeen.colorset/Contents.json
  • ios/HouseScan/Assets.xcassets/CoverageSkipped.colorset/Contents.json
  • ios/HouseScan/Assets.xcassets/CoverageUnseen.colorset/Contents.json
  • ios/HouseScan/Assets.xcassets/Danger.colorset/Contents.json
  • ios/HouseScan/Assets.xcassets/Ink.colorset/Contents.json
  • ios/HouseScan/Assets.xcassets/Muted.colorset/Contents.json
  • ios/HouseScan/Assets.xcassets/Signal.colorset/Contents.json
  • ios/HouseScan/Assets.xcassets/SignalText.colorset/Contents.json
  • ios/HouseScan/Assets.xcassets/Surface.colorset/Contents.json
  • ios/HouseScan/CaptureScreen.swift
  • ios/HouseScan/CaptureSessionModel.swift
  • ios/HouseScan/Contract/ResultPresentation+Reading.swift
  • ios/HouseScan/Contract/ScanContract.swift
  • ios/HouseScan/HouseScanApp.swift
  • ios/HouseScan/Runtime/AppRuntime.swift
  • ios/HouseScan/Runtime/Autopilot.swift
  • ios/HouseScan/Runtime/CaptureRecorder.swift
  • ios/HouseScan/Runtime/GuidanceLog.swift
  • ios/HouseScan/Runtime/KeyframeStore.swift
  • ios/HouseScan/Runtime/LaunchOptions.swift
  • ios/HouseScan/Runtime/LiveCapture.swift
  • ios/HouseScan/Runtime/MeterNumberReading.swift
  • ios/HouseScan/Runtime/MeterReader/VisionMeterNumberReader.swift
  • ios/HouseScan/Runtime/ReplayPlayer.swift
  • ios/HouseScan/Runtime/ResultClient.swift
  • ios/HouseScan/Runtime/SampleResult.json
  • ios/HouseScan/Runtime/ScanEngine+Actions.swift
  • ios/HouseScan/Runtime/ScanEngine+Confirm.swift
  • ios/HouseScan/Runtime/ScanEngine+Ends.swift
  • ios/HouseScan/Runtime/ScanEngine+Export.swift
  • ios/HouseScan/Runtime/ScanEngine+GuidanceLog.swift
  • ios/HouseScan/Runtime/ScanEngine+Packet.swift
  • ios/HouseScan/Runtime/ScanEngine+Snapshot.swift
  • ios/HouseScan/Runtime/ScanEngine+Stamp.swift
  • ios/HouseScan/Runtime/ScanEngine.swift
  • ios/HouseScan/Runtime/SourceFrame.swift
  • ios/HouseScan/SessionDelegate.swift
  • ios/HouseScan/UI/Camera/CameraBackdrop.swift
  • ios/HouseScan/UI/Camera/CameraOverlays.swift
  • ios/HouseScan/UI/Camera/FogOverlay.swift
  • ios/HouseScan/UI/Camera/WallMarksOverlay.swift
  • ios/HouseScan/UI/Camera/WallProjection.swift
  • ios/HouseScan/UI/Camera/WallTape.swift
  • ios/HouseScan/UI/Camera/WayfindingOverlay.swift
  • ios/HouseScan/UI/Components/AnswerButton.swift
  • ios/HouseScan/UI/Components/Buttons.swift
  • ios/HouseScan/UI/Components/CameraChrome.swift
  • ios/HouseScan/UI/Components/CenteredScroll.swift
  • ios/HouseScan/UI/Components/GroundAnswers.swift
  • ios/HouseScan/UI/Components/InstructionCard.swift
  • ios/HouseScan/UI/Components/OverheadAnswers.swift
  • ios/HouseScan/UI/Components/PhotoCounter.swift
  • ios/HouseScan/UI/Components/SavedMeterPhoto.swift
  • ios/HouseScan/UI/Components/Scrim.swift
  • ios/HouseScan/UI/Components/ShareScanButton.swift
  • ios/HouseScan/UI/Components/TapRipple.swift
  • ios/HouseScan/UI/Copy/Distance.swift
  • ios/HouseScan/UI/Copy/ScanCopy.swift
  • ios/HouseScan/UI/Copy/SpotCheckCopy.swift
  • ios/HouseScan/UI/Preview/DemoEngine.swift
  • ios/HouseScan/UI/Preview/DemoScene.swift
  • ios/HouseScan/UI/Preview/UIDemo.swift
  • ios/HouseScan/UI/Result/ResultScene3D.swift
  • ios/HouseScan/UI/ScanRootView.swift
  • ios/HouseScan/UI/Screens/FindMeterScreen.swift
  • ios/HouseScan/UI/Screens/GapRequestScreen.swift
  • ios/HouseScan/UI/Screens/MarkFeaturesScreen.swift
  • ios/HouseScan/UI/Screens/MeterCloseUpScreen.swift
  • ios/HouseScan/UI/Screens/OnboardingScreen.swift
  • ios/HouseScan/UI/Screens/ResultARScreen.swift
  • ios/HouseScan/UI/Screens/ResultScreen.swift
  • ios/HouseScan/UI/Screens/SpotConfirmScreen.swift
  • ios/HouseScan/UI/Screens/UnsupportedScreen.swift
  • ios/HouseScan/UI/Screens/UploadingScreen.swift
  • ios/HouseScan/UI/Screens/WallWalkScreen.swift
  • ios/HouseScan/UI/Theme/Theme.swift
  • ios/HouseScanKit/Package.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Capture/AutoCapture.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Capture/CaptureSourceState.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Capture/CloseUpCredit.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Capture/CloseUpGate.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Capture/FrameQuality.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Confirm/SpotConfirmation.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Coverage/CoverageMap.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Depth/DepthImage.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Geometry/Camera.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Geometry/GroundPlaneChoice.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Geometry/MeterAnchorTracking.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Geometry/SpatialUpdate.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Geometry/WallFrame.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Guidance/GapPlanner.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Guidance/GuidancePlanner.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Guidance/WalkedEnd.swift
  • ios/HouseScanKit/Sources/HouseScanKit/HouseScanKit.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Mesh/MeshProbe.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Mesh/TriangleMesh.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Meter/MeterBrand.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Meter/MeterNumberRanking.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Meter/MeterPhotoChecks.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Packet/MeterFrame.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Packet/PacketDepthFrames.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Packet/PacketFiles.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Packet/PacketManifest.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Packet/PacketMarks.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Packet/PacketPlane.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Packet/PacketSharpness.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Packet/PacketStreams.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Packet/PacketWriter.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Replay/ReplayPlanning.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Replay/ReplaySession.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Scene/AnswerFreshness.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Scene/GroundPatches.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Scene/PlacementResult.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Scene/ResultReading.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Scene/ScanFolderCleanup.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Scene/ScanStamp.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Scene/SceneExport.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Scene/ServerErrorDefaults.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Scene/ZipWriter.swift
  • ios/HouseScanKit/Sources/HouseScanKit/Upload/UploadFailureKind.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/AnchorRepairTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/AutoCaptureTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/CloseUpCoverageTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/CloseUpGateTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/CornerTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/CornerWalkedPathTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/CoverageDepthTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/CoverageMapTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/DepthTestSupport.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/FacingTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/FenceAcrossCornerTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/FrameQualityTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/GapPlannerTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/GeometryTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/GroundDepthTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/GroundPatchTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/GroundSettlementTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/GuidancePlannerTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/JSONSchemaValidator.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/JSONSchemaValidatorTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/KeptScansTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/MeshProbeTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/MeterBrandTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/MeterNumberRankingTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/MeterPhotoChecksTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/NoUnseenClaimsTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/OverheadTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/PacketFileTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/PacketFrameTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/PacketWriterTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/PlacementResultTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/RealReplayTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/ReplayPlanningTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/ReplaySessionTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/ResultReadingTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/SceneExportTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/Schemas/example-scene.json
  • ios/HouseScanKit/Tests/HouseScanKitTests/Schemas/manifest.schema.json
  • ios/HouseScanKit/Tests/HouseScanKitTests/Schemas/result.schema.json
  • ios/HouseScanKit/Tests/HouseScanKitTests/Schemas/scene.schema.json
  • ios/HouseScanKit/Tests/HouseScanKitTests/Schemas/server-answer-synthetic-wall.json
  • ios/HouseScanKit/Tests/HouseScanKitTests/ServerErrorDefaultsTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/ServerGapTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/ServerRequestSettlementTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/SpatialSourceTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/SpotConfirmationTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/TestSupport.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/TwoCornerObjectTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/UploadFailureKindTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/UploadSnapshotTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/UpperWallFixtureTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/VersionTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/WalkedEndTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/WalkingHeightTests.swift
  • ios/HouseScanKit/Tests/HouseScanKitTests/ZipWriterTests.swift
  • ios/HouseScanUITests/AccessibilityAudit.swift
  • ios/HouseScanUITests/Fixtures/results/no-clean-spot.json
  • ios/HouseScanUITests/Fixtures/results/pass.json
  • ios/HouseScanUITests/Fixtures/results/reject-nearest.json
  • ios/HouseScanUITests/Fixtures/results/unsure-view.json
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00001.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00001.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00002.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00002.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00003.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00003.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00004.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00004.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00005.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00005.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00006.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00006.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00007.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00007.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00008.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00008.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00009.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00009.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00010.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00010.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00011.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00011.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00012.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00012.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00013.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00013.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00014.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00014.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00015.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00015.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00016.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00016.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00017.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00017.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00018.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00018.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00019.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00019.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00020.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00020.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00021.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00021.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00022.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00022.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00023.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00023.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00024.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00024.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00025.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00025.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00026.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00026.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00027.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00027.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00028.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00028.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00029.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00029.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00030.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00030.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00031.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00031.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00032.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00032.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00033.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00033.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00034.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00034.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00035.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00035.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00036.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00036.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00037.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00037.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00038.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00038.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00039.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00039.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00040.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00040.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00041.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00041.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00042.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00042.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00043.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00043.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00044.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00044.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00045.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00045.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00046.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00046.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00047.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00047.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00048.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00048.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00049.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00049.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00050.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00050.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00051.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00051.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00052.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00052.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00053.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00053.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00054.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00054.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00055.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00055.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00056.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00056.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00057.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00057.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00058.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00058.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00059.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00059.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00060.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00060.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00061.confidence.u8
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00061.depth.f32
  • ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/session.json
  • ios/HouseScanUITests/Fixtures/synthetic-wall/session.json
  • ios/HouseScanUITests/FullFlowUITests.swift
  • ios/HouseScanUITests/ScreenStatesUITests.swift
  • ios/HouseScanUITests/SourceRestartUITests.swift
  • ios/README.md
  • ios/Tools/check-app-scene.sh
  • ios/Tools/make-app-icon.swift
  • ios/Tools/make-synthetic-replay.swift
  • ios/project.yml

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added size:XL 500-999 effective changed lines. size:XXL 1,000+ effective changed lines. and removed size:XL 500-999 effective changed lines. labels Sep 26, 2026
SamGu-NRX and others added 8 commits September 26, 2026 05:22
The ground under the wall was looked up once, when the meter was tapped, and fell back to
camera height minus 1.4 m for the rest of the scan. The lookup now re-runs whenever ARKit's
horizontal planes change, replacing the guess (or an earlier plane) and republishing the
wall. While the ground is still a guess, scene.json carries plus_minus_ft of 0.3 m on the
meter and every object: the guess's error enters every height, and the schema has no other
place to say the ground was estimated. A replay's recorded wall counts as measured; an
assumed one does not.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
After 20 s of relocalizing the engine threw away the scan in every phase, including an
upload in flight (its answer was then dropped) and the result. The spatial reset now runs
only from findMeter through gapRequest. Uploading and the result keep the scan and the
answer; the AR result already hides its overlay while tracking is not normal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
"Capture it now" on a past_end item cleared that end and started a gap step, but
markWallEnd only worked in the walk, so the end could never be marked again. During a
past_end request, markWallEnd now accepts that side; answering the end question settles the
request and goes on to the upload. If the request closes by coverage instead, the end stays
cleared and exports as unexplored.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Sweep zones: start_ft is a range of battery left edges, so a zone runs to the last start
  plus the spot's width.
- Keyframe writes in flight are counted per scan generation; resetAll no longer zeroes a
  counter that a finishing write then drove negative.
- Entering the close-up clears a pending shot from an earlier visit.
- A refined meter anchor or measured ground moves the coverage cells and marked ends with
  the meter (whole cells; the sub-cell remainder carries over) and re-projects every marked
  feature from its tapped world points.
- An UNSURE check without unsure_cause needs a person.
- Missing evidence is capturable only when a gap request can be built from it.
- The unseen side ignores an unexplored end beyond cable reach.
- A keyframe write that finishes after its world frame was discarded is dropped.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…calization, past-end, fence depth, guidance arrow

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… one

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…time

The planner's dwell ran on frame timestamps. On a replay at 3x speed that let the card change
every second or so (walk left, tilt down, walk left), which the accessibility audit caught
mid-transition and the verify lane's I6 check reads on the wall clock. Live, both clocks agree.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ks inside and scalable

CI's Simulator showed a system banner over the photo count during an audit, and once the
accessibility tree changed while an audit read it. The screen-state audit now fails on issues
found again six seconds later, and a failed snapshot is retried once in both UI tests.

On the ADVIO walk the meter sits at the strip's end, and its mark hung half outside the strip,
which the audit reported as clipped text. Marks now stay inside the strip, and their glyphs
follow the text size up to 16 pt instead of fixed 9 and 10 pt. The walk's distance hint
counts down in 5 ft steps above 5 ft, so the card no longer changes every stride.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SamGu-NRX SamGu-NRX changed the title ios: guided wall scan, from meter to battery spot ios: guided wall scan from the meter to a battery spot Sep 26, 2026
@SamGu-NRX
SamGu-NRX marked this pull request as ready for review September 26, 2026 11:57
@codemetrics-ai

Copy link
Copy Markdown

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 to your team's repositories.

SamGu-NRX and others added 14 commits September 26, 2026 07:07
… picker

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…es to replace

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… reader

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
updateWall only shifted cells along s, so after the ground height was
corrected the export claimed wall and ground rows sampled in the air.
The map now keeps the camera of every normally tracked keyframe and
replays them against the new frame, keeping skipped cells and ends.
refineGround republishes the strip after the rebuild.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…vives tap geometry

POST {serverURL}/v1/placements now carries scene.json as application/json;
the solver reads no photos, so keyframes stay on the phone and scan.zip is
still written for replay and debugging but never uploaded or awaited.
Network failures and 5xx become failed (Try again); 4xx, an unreadable
answer and a scene that can't be exported become rejected with homeowner
wording, the technical detail going to the log. backToReview returns a
rejected upload to the feature review with the scan kept. The export clamps
opening heights, moves ground taps behind the wall line to just in front of
it, and drops a zero-length driveway edge instead of throwing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s as strings

The app never reads them, so a new server value there should not fail
every decode. Decision, outcome and unsure_cause stay strict.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
HouseScanServerURL = $(HOUSESCAN_SERVER_URL), set in Shared.xcconfig to the
deployed server. -serverURL overrides it and -sampleResult still forces the
bundled sample, which the full-flow UI test passes so it stays offline. An
empty or non-http(s) value means no server and falls back to the sample.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
scene.schema.json now closes attrs to operable and well; the export only
writes operable, and a test checks another key is rejected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A denied camera, a failed AR session and an unreadable replay set
state.failure but left the flow where it was; only an unsupported phone
reached .unsupported. All four now go through one fail(_:) that sets the
failure and the phase. Checked with -replay /nonexistent: STATE=onboarding,
then STATE=unsupported.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every new KeyframeStore (at launch, and on Start over) deletes the other
folders under Caches/Scans off the main actor. Before, each Start over left
its previous Caches/Scans/<UUID> behind and nothing ever removed one.
Checked on the Simulator: 30 folders before launch, 1 after.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Coverage marks what the camera pointed at. The server does not read
images, so nothing downstream corrects a bush or bin counted as seen.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A failed try needed one problem lasting 4 s with no good frame between,
so frames alternating sharp and blurry never produced a photo or a
failed try, and "Can't get a clear shot" never appeared.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…otos stay on the phone

The second page said only "Your phone takes the photos". Uploads now carry the wall's
measurements, so the page says what leaves the phone and that the photos are deleted on
Start over, before the camera is asked for.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e recording

Both failures showed the engine's message, which is system error text (ARKit's
localizedDescription, or String(describing:) of the replay loader's error). They now say what
happened in the homeowner's words, and Start over, the only way on, is the screen's primary
button. Open Settings uses the primary style too, so it gets the pressed state a Link lacked.
The demo can open on either failure (-uiDemoFailure sessionFailed|replayUnreadable).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	ios/HouseScanUITests/ScreenStatesUITests.swift
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 29ebe846a3

ℹ️ 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".

SamGu-NRX and others added 4 commits September 27, 2026 06:10
The photo was chosen with its raw camera against the corrected wall, and the
outline drawn through the raw pose: after a 0.30 m anchor correction the
outline sat tens of pixels off the spot while the whole-area check passed.
Each candidate is now judged with its camera corrected by the anchor
corrections applied since it was taken (SpotPhotoCandidate.camera(correctedBy:)),
and the choice carries that camera for the outline. The photo and camera
are fixed together after the decode, and judged and drawn again whenever
the wall moves while the question is up (refreshSpotPhoto, from
publishWall); a photo that no longer shows the whole area turns the
question into leaving the area out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The planner joined ground spans to the 125-entry band budget, but the export
joins ground to 124 on a chain with one corner, to leave room for cutting it
there. At the boundary the export merged two neighbours (2.5 ft and 3 ft) that
the planner kept, and a 3 ft request settled on the phone while the scene sent
2.5 ft (caretaker review, GapPlanner P2).

SceneExport.groundEntries is the one normalization; the export and the
planner's settlement both read ground through it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@codex review

@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: 77d820bb69

ℹ️ 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 ios/HouseScan/UI/Result/ResultScene3D.swift
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@greptileai

The LiDAR replay at 9b4c57d failed contrast on action.cannotAccess. Its
recording shows "Can't get there" and "Can't see past it" drawn over
each other half-transparent as the walk moved to the see-behind step:
the reply stayed one button, so its new title cross-faded inside the
card's instruction animation. It now takes a new identity per title and
swaps at once, as the message above it does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread ios/HouseScanKit/Sources/HouseScanKit/Scene/ScanFolderCleanup.swift Outdated
SamGu-NRX and others added 3 commits September 27, 2026 06:50
A scan's first bundle write creates scan.zip.partial; if the app died before
scan.zip existed, the next launch counted that folder as completed and newest,
so it took one of the two slots and evicted a real completed scan
(Greptile 4115191959).

Now only scan.zip marks and dates a completed scan; a folder with both counts
by its scan.zip. A partial-only folder is kept apart, uncounted: the newest
one, while it is newer than every completed scan, so its photos last until the
next scan completes. Older ones go.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
scene.json, the mesh measurement and the packet read the live wall, ground,
marks and ARKit's current mesh at different moments, so a correction or ground
change between reads made them describe different walls.

- UploadSnapshot (ScanEngine+Snapshot.swift) is taken on the main actor in
  ingest, right after a frame's anchor correction and ground update. It holds
  the coverage map, ground, marks as world points, raw keyframes with the pose
  corrections, stills, end kinds, spot ground patches, and that frame's mesh and
  planes.
- LiveCapture reads the mesh and planes from the delivered ARFrame's own
  anchors when asked, stamped with its timestamp. The snapshot keeps them only
  when that time is the frame's, and goes without them if no such frame
  arrives within 2 s. There is no more currentFrame read and no retry loop.
- sceneJSON, packetInputs and the mesh measurement take the snapshot; nothing
  after it reads live state.
- Pending taps are kept as world points and moved by corrections, so a mark's
  heights come from the snapshot's ground.
- spatialRevision counts corrections, ground changes, new walls and followed
  corners. A stale answer is dropped and the scan sent once more; a second
  stale answer is not shown and the upload fails with "still adjusting, try
  again" (AnswerFreshness).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
680e4b7: only a real scan.zip makes a completed scan; a partial-only folder
takes no slot. 4ef41b3: one upload snapshot from a frame-stamped input; the
mesh only from the matching frame; stale answers resent once; mark heights
from world points against the snapshot's ground.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SamGu-NRX added a commit that referenced this pull request Sep 27, 2026
Start over sits under the result card's collapsed Details (ResultScreen),
as ScreenStatesUITests already reaches it; 1c496fe stopped at the result but
still tapped a hidden button. The test also lets the spot question go on.
Found by the #10 caretaker from the b33e214 run. Not rerun.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ply title

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@codex review

@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: 4f1fb8ebec

ℹ️ 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 ios/HouseScan/Runtime/ScanEngine.swift
Comment on lines +247 to +250
// A fence's feet on two pieces of the wall would be sent as one depth that misses how
// close its line comes to the wall by the corner: refused, and asked for per side.
if marking.kind == .fence, let first = pendingTaps.first, !wall.onSamePiece(first, wall.world(hit)) {
marking.refusal = .fenceAcrossCorner

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 openings whose taps cross a wall corner

When the homeowner places a door or window's two taps on different pieces of a followed corner, this same-piece validation runs only for fences, so the opening is accepted with one continuous span. SceneExport then assigns that span a single wall_id, and the result renderer likewise draws the entire opening on the midpoint's wall, misplacing or losing part of the marked obstacle for clearance checks. Apply the same corner validation to two-tap wall openings, or split them into per-wall features.

Useful? React with 👍 / 👎.

Comment thread ios/HouseScan/Runtime/ScanEngine.swift
SamGu-NRX and others added 2 commits September 27, 2026 07:26
…ey measure

A gas meter or AC unit was one tap, exported as a 0.3 m square standing
0.3 m off the wall: a guessed width and depth.

- Gas meter: two taps where it meets the wall, bottom-left then top-right, as a
  window or #52's box. Exported as a wall object with its span, bottom_ft and
  top_ft and no footprint, since a wall-plane tap says nothing about how far it
  stands out. The server measures to its stretch of wall line.
- AC unit: two taps on the ground where its front meets it (a ray aimed at a
  unit meets the ground in front of the wall, #163). Exported with its span
  and a footprint from the wall line to that tapped front edge, so its depth
  is measured and the space behind it counts as taken. Height is not measured
  and is left out.
- Refused and marked again: taps closer than 1 cm (SceneExport.minObjectSize),
  a single tap, and AC corners on two pieces of the wall.
- The packet manifest (1.1) gives these kinds one point, so each mark carries
  the middle of its two corners; the packet's scene.json has the span and
  footprint.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A gas meter takes two taps on the wall (span and heights, no footprint);
an AC unit two taps on the ground at its front (footprint to the wall line,
no heights). The old 0.3 m square is gone, and a one-tap or cross-corner
mark is refused, never exported as a sized object.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SamGu-NRX and others added 2 commits September 27, 2026 08:14
…lt and AR

The server's answer was checked against the spatial revision only when it
arrived. A ground plane revoked or refined while the spot check is open
raised the revision, but finishing on a path that adds no patch ("It's
clear" then "Not sure") still went straight to the result with the old
answer.

Every step that would present the accepted answer as current (the spot
check, the result after it, opening and closing the AR view) now asks
AnswerFreshness.step: an unchanged revision shows it; a changed one drops
the answer and sends the scan again from a fresh snapshot, once, then shows
the stale-answer failure, as at the POST. A step that changes the scan
uploads anyway.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An accepted answer is shown only while the spatial revision it was built
on still holds; otherwise the scan is sent again once, then fails as stale.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

SamGu-NRX added a commit that referenced this pull request Sep 27, 2026
…ft are rounding

- make sim-app at c820cf1 on A4EE0B20, synthetic replay, hosted public server: 17 states, a
  server gap request answered "can't get there", three spot checks, the result and AR. S4-2 is
  met there and S4-1 partial (the real ADVIO replay is blocked by its data).
- The scan's less-coverage resend flagged wall_equipment_above for a 0.0037 ft sliver past the
  seen wall. The server's missing() drops 1D gaps under 0.01 ft (COVERAGE_TOLERANCE_FT); the
  wall, route, facing and overhead checks now do too. Tested at 0.0037 and 0.011 ft.

e2e at 3baa338 with that scan: 49 of 50, the ETH3D server issue. #11 moved to 6c7bea1 during
the run, so S2-6 and S2-8 read stale until the turn-2 re-pin.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@greptileai review

Please review the current head c820cf150a73cb97527428d580c330f631253f9a against this PR's actual base. Catch-up review requested by Sam after enabling BaseScanning in Greptile.

Comment on lines +54 to +56
heading
featureList
addSomething

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 Wall-wide ground question is missing. The review shows the feature list and “Looks complete” without asking what is on the ground along the wall. Ground type is asked only later for a server-selected spot, and its answer covers only that footprint. If the server returns no spot, or no kept photo shows the whole spot-check area, the homeowner cannot provide a ground type, leaving the ground-surface check unsure even when they know the answer.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ios/HouseScan/UI/Screens/MarkFeaturesScreen.swift
Line: 54-56

Comment:
**Wall-wide ground question is missing.** The review shows the feature list and “Looks complete” without asking what is on the ground along the wall. Ground type is asked only later for a server-selected spot, and its answer covers only that footprint. If the server returns no spot, or no kept photo shows the whole spot-check area, the homeowner cannot provide a ground type, leaving the ground-surface check unsure even when they know the answer.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Caretaker check at c820cf1: not a defect in the code. The wall-wide ground question was removed on purpose after Codex 4114010727 showed one answer could label lawn as concrete (Sam, 4114020413 and 4114023377). With no spot, ScanEngine+Confirm.swift:94-100 shows the result without a ground question, and SpotConfirmation exports only answered footprints. The scene contract makes ground optional, and an unrecorded surface stays UNSURE, which is the intended outcome for unseen ground.

What is stale: the PR body and the header comment at MarkFeaturesScreen.swift:3 still describe a wall-wide question. That is description drift, recorded for #10's owner; #10 is frozen, so no change from here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You're right — this is not a defect in MarkFeaturesScreen or the export flow. The wall-wide ground question was intentionally removed because a single answer could overgeneralize across the wall, and leaving unseen ground as UNSURE is the intended contract behavior. The stale parts are the PR description and the screen's header comment, so I’m recording this as description drift for the frozen PR rather than requesting a code change.

Comment thread .github/workflows/ios.yml
Comment on lines +13 to 17
types: [opened, synchronize, reopened, ready_for_review, labeled]
paths:
- "ios/**"
- "!ios/**/*.md"
- ".github/workflows/ios.yml"

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 Server schema changes skip drift check. The new iOS contract comparison runs only when an iOS non-Markdown file or this workflow changes. A pull request that changes only a server schema will not trigger it, so an incompatible vendored iOS schema can go undetected by this check. Include the server contract paths in the workflow triggers.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/ios.yml
Line: 13-17

Comment:
**Server schema changes skip drift check.** The new iOS contract comparison runs only when an iOS non-Markdown file or this workflow changes. A pull request that changes only a server schema will not trigger it, so an incompatible vendored iOS schema can go undetected by this check. Include the server contract paths in the workflow triggers.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Caretaker check at c820cf1: confirmed, and already true on main. A ready PR that changes only server/schemas/*.schema.json matches none of ios.yml:14-17, and server.yml runs only lint and pytest, so vendoredCopiesMatchServer never runs. Adding server/schemas/** and server/tests/fixtures/example-scene.json to both path lists closes the gap, but it also puts a 45-minute macOS job on every server-schema PR. That cost trade-off belongs to #10's owner, and #10 is frozen, so it is recorded for them rather than changed here. A cheaper alternative is a separate Linux job that only diffs the vendored copies.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

That’s a fair resolution. The finding remains valid, but since #10 is frozen, I won’t recommend changing this workflow here. Recording the two concrete options for the owner is sufficient: add the server contract paths and accept the macOS cost, or add a cheaper standalone Linux vendored-schema drift job.

SamGu-NRX pushed a commit that referenced this pull request Oct 15, 2026
* Sync c3-vlm breadth work from the Project store (result file C3-vlm-baseline.md)

* Sync c3-vlm breadth work from the Project store (result file C3-vlm-baseline.md)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

full-ui iOS CI also runs the every-state accessibility audit (about 10 more minutes). size:XXL 1,000+ effective changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants