Skip to content

0.16 ft/ft drift, added per feature, makes any spot more than a few feet from the meter unresolvable #79

Open

Description

@AidenJohnston

The server grows every AR position's error by 0.16 ft per foot from the meter, then adds the object's error to the wall's. So no clearance check a few feet from the meter can pass, and every scan without a spot beside the meter ends in manual_review.

Seen in. Device field test of TestFlight build 4.1 (beta at 45017ba), iPhone 14 without LiDAR, iOS 26.6.2, 2026-09-26. Both runs on the indoor mock wall; the server re-run at 45017ba on each exported scene matches the phone's result exactly.

  • Run 1: result at 6:06 in the recording. Report ID A41-1-09 in run1-2000.md.
  • Run 2: result at 4:34, checks scrolled at 4:56–5:00. Report ID A41-2015-08 in run2-2015.md.

What happens.

Run 1, spot 10 ft 11 in → 13 ft 6 in right of the meter:

  • AC: 3 ft 2 in ± 5 ft 7 in, against a 3 ft rule.
  • Doors and windows: 3 ft 0 in ± 4 ft 0 in.
  • Cable run: 12 ft 7 in ± 6 ft 5 in, against the 15 ft reach.
  • 825 candidates: 0 pass, 719 unsure, 106 fail.

Run 2, spot 17 ft 1 in → 19 ft 8 in right:

  • AC: 3 ft 0 in ± 6 ft 0 in.
  • Doors and windows: 9 ft 9 in ± 4 ft 11 in.
  • Cable run: 17 ft 1 in ± 4 ft 3 in, against 15 ft confident and 20 ft maximum.
  • Meter working space passes, but at 15 ft 10 in ± 4 ft 3 in.
  • 784 candidates: 0 pass, 654 unsure, 130 fail.

A second symptom, from run 2's sweep: starts at 5.1–6.9 ft, which put the battery directly under the window, come out unsure for opening_clearance, not fail. The summed errors swamp a sub-foot distance. Ranking still avoids those starts through estimate_fails, but the check's outcome is wrong.

Worked example, run 2's AC: the one-tap AC mark ends at 14.1 ft, so its error is 0.3 + 0.16 × 14.1 = 2.56 ft. The wall at the spot's far edge (19.68 ft) is 0.3 + 0.16 × 19.68 = 3.45 ft. Summed, that is ± 6.0 ft for two things measured on the same walk, about 5 ft apart.

Cause.

  • errors.drift_per_ft is 0.16 ft/ft (rules.yaml#L38-L41). Its source is ADVIO walks on a 2018 iPhone 6s with ARKit 1.0, and it says current phones are untested.
  • Objects get the tap error plus drift × the farthest |s| of their span (scene.py#L809-L813). Ground patches get the same treatment (scene.py#L818-L822), and so do walls through Piece.error_at (scene.py#L72-L78).
  • Each check takes the wall's error at the battery's far edge (solver.py#L886-L890) and adds the object's error to it (solver.py#L464-L466). Both errors come from the same walk and are strongly correlated, so the sum overstates the error of the distance between them.
  • The app's stand-off distance is derived from the same 0.3 ft + 0.16 ft/ft (GuidancePlanner.swift#L42-L50), so a change here should update that comment and value too.

Fix direction.

  • Re-estimate drift on current phones: use the field tape test, or compare the packet's trajectory with the tape.
  • Treat the errors of features measured on the same walk as relative. For example, the tap errors plus drift × the distance between the two features along the wall, not the sum of each one's absolute error from the meter.
  • Consider measuring clearances between two features locally, from the photos that see both.
  • A battery directly under a window (a sub-foot distance) should come out fail. Once the error model changes, add that case as a test.

Related.

Activity

  1. added
    bugSomething is broken or behaving incorrectly.
    size:M30-99 effective changed lines.
    on Sep 27, 2026
  2. AidenJohnston commented on Sep 27, 2026

    @AidenJohnston
    CollaboratorAuthor

    Proposal: the drift error model needs data before anything changes

    This comment lists the options and the data each one needs. It does not pick a model. I checked the code against origin/t3/server, where the solver lives now.

    How it works today

    • Every AR-placed item that has no explicit plus_minus_ft gets an absolute error: tap error + drift_per_ft (0.16) × its farthest |s| from the meter. This covers tap and VLM objects in parse_scene, ground patches, and walls through Piece.error_at. Tape items and the meter itself get no drift.
    • Solver.evaluate gives the battery the wall's error at its far edge. check_clearance and check_along_wall then add the object's absolute error to it (e = err + piece.plus_minus). Two features taken on the same walk a few feet apart have strongly correlated errors, so this sum overstates the error of the distance between them. That is how run 2's AC comes out at ± 6 ft, and why a battery directly under a window gets unsure and not fail.
    • starts, _clearance_edges and _settle_on place the sweep's boundaries using the same battery error. Whatever the checks use, these three have to use too, or the sweep will miss the starts where an outcome changes.
    • The app's GuidanceConfig.standOff comment derives the 2 m stand-off from 0.3 ft + 0.16 ft/ft. It must change with the model.

    Options (not ranked)

    • (a) Keep the additive model and only change the rate. This is the smallest code change: rules.yaml plus the drift test expectations. It needs a measured rate for current phones. Even a lower rate still sums two correlated errors, so the under-window case may still come out unsure at larger |s|.
    • (b) Use a relative error for distances between two features. A clearance's error would be the two tap errors plus drift × the distance between the two features along the walk. The absolute, meter-relative error stays for quantities measured from the meter: the meter's working space and the route length. This changes the drift tests, and the boundary code (starts, _clearance_edges, _settle_on) has to use the same per-pair error. It still needs some number for how error grows with the distance between two features.
    • (c) Both.

    Data that would settle it

    1. Absolute drift on a current phone without LiDAR. Build 4.1's runs were on one. Put tape marks at known distances from the meter along the mock wall, for example 0, 5, 10, 15 and 20 ft. Tap each mark during a normal guided walk and compare the packet's positions with the tape. Several walks per phone give a p90 for each distance. The capture's trajectory measured against the same tape survey also works.
    2. Relative error between two features. Use the same walks, but compare the difference between pairs of marks (for example 15 → 20 ft) with the tape. Option (b) needs exactly this number, and it tests the assumption that the correlation is strong.
    3. The same on a LiDAR phone, if one is available. The public-dataset numbers in experiments: signals lab round 1, which extra phone signals sharpen clearances #59 (drift-anatomy) are far under the model at 20 ft: along-travel p90 is about 10 in, against 0.3 + 0.16 × 20 = 3.5 ft. Scale also wandered about 1.8% within a walk, which bears on (b). But that dataset's phone has LiDAR and its reference scale is unverified, so on its own it can't justify a rate for phones without LiDAR.

    Decisions still needed

    • Which drift source is accepted: the field tape test, a trajectory against a tape survey, or experiments: signals lab round 1, which extra phone signals sharpen clearances #59's public-dataset numbers, possibly as the value for LiDAR phones only.
    • Whether the solver may use per-pair (relative) errors for clearances between features. That changes the drift tests' expectations; the golden tests use drift 0 and are not affected.

    Once decided, a server PR on t3/server would do the following:

    • update rules.yaml (value and citation) and test_drift.py;
    • add a test that a battery directly under a window gives opening_clearance fail;
    • if (b) is chosen, add a property test that a relative error never exceeds the sum of the two absolute errors;
    • update the standOff comment and value in the app.

    🤖 Generated with Claude Code

  3. AidenJohnston commented on Sep 27, 2026

    @AidenJohnston
    CollaboratorAuthor

    Team decision (2026-09-27): defer; keep 0.16 ft/ft until tape-walk data exists.

    🤖 Generated with Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.size:M30-99 effective changed lines.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions