diff --git a/experiments/device-field-test/README.md b/experiments/device-field-test/README.md new file mode 100644 index 000000000..ee3f26208 --- /dev/null +++ b/experiments/device-field-test/README.md @@ -0,0 +1,182 @@ +# Device field test + +## Question + +Does the House Scan TestFlight build produce a scan the server can place a battery on, from one walk +along a real wall with a real iPhone? And which behaviours that the Simulator can't check hold on a device? +Those are the tracking, the live meter tap, the close-up, haptics, the AR overlay and interruptions. + +## Method + +[RUNBOOK.md](RUNBOOK.md) lists every device-only claim from the product description on `t3/verification`, +in the order a person meets them, each with a test ID. The tester screen-records each run with the +microphone on and says the test IDs and haptics aloud. Before scanning, they tape the wall's features +relative to the meter. After the run: +- the exported scan is sent to the server again, which returns the same result the phone got; +- [`coverage_replay.py`](coverage_replay.py) replays its photos through the app's coverage rules. + +[setup-plan.html](setup-plan.html) is the plan for the first site, a mock house wall with a meter, panel, +window, eave and an installed battery (rendered in [setup-plan.png](setup-plan.png)). + +## Pass criteria + +The first and last come from the runbook's goals before run 1. The 4 in bound is Measure Lab's, applied after the run. + +- The uploaded scan covers the stretch of wall the tester walked, and the result names either a spot or a + reason that refers to something on that wall. +- Measured features are within 4 in of the tape along the wall and in height, as in Measure Lab's criteria. +- Every device-only claim in the runbook is recorded as pass, fail or not reached. + +## Result, run 1 + +Run on 2026-09-26 with TestFlight build 1.1 (workflow run +[36255760659](https://github.com/SamGu-NRX/house-scanning-master/actions/runs/36255760659), branch `beta` at +[`28cd898`](https://github.com/SamGu-NRX/house-scanning-master/commit/28cd8988187979800f74bde78ee1b1fc246d0ec3), +capture code `t3/ios-mvf` at +[`657ab28`](https://github.com/SamGu-NRX/house-scanning-master/tree/657ab283092f1a34cc2dff87d48a0871d284f9c4/ios)). +The 1 min 54 s screen recording was reviewed frame by frame, and its narration was transcribed on the +tester's laptop. It shows the same run as the scan: the share sheet reports `scan.zip` at 27.3 MB, and the +result text is the same. + +**The first criterion fails.** +- **What the phone captured.** It kept 44 photos and a meter close-up. It walked about 19 ft right of the + meter and back, 5 to 7½ ft from the wall. +- **What the scan says.** Both wall ends sit within 4 in of the meter: the exported wall baseline is + 0.66 ft long. The scan leaves out everything past its ends, so it tells the server that no wall and no + ground were observed. +- **What the server answered.** `manual_review` with no spot, asking the homeowner to "keep walking past + the left end of the scan (0 ft 4 in left of the meter)", and the same for the right. + +### What the recording shows + +"Can't get there" was tapped twice, and each tap set one end at the meter: + +- **00:22, left.** The card said "Walk slowly to your left". The tester stood about a metre from the meter + and hadn't walked or tilted down. The strip showed the wall row amber (seen once) either side of the + meter and the ground row grey. The walk switched to "Walk slowly to your right". +- **00:28–00:44, tilt prompts.** "Tilt down to show the ground" asked for the strip 3 ft 3 in, 2 ft 9 in, + 4 ft 6 in, 7 ft and 8 ft 9 in right of the meter. None of them asked for the ground in front of the meter. +- **00:53, right.** The tester stood at the right corner, about 16 ft from the meter. Just before the tap, + the strip's wall row was green (covered) for the first stretch from the meter and amber after it. The + ground row was amber all the way from the meter, never green. Right after the tap the card said "That's + the whole wall", and the strip went blank for the rest of the run. + +After that, the tester marked the AC stand-in (listed at "About 16 ft right"), the window ("About 7 ft +right", answered "It stays shut") and the gas meter ("Around your meter"). The upload took 2.9 s. The result +was "An installer will take a look", with the two "Keep walking past the … end of the scan (0 ft 4 in …)" +requests and no checks. The AR view is offered only with a spot, so it wasn't reached. + +The 20 ft "Is this the … end of the wall?" prompt never appeared. + +### How the walk was lost + +"Can't get there" during the walk sets that side's end at `GuidancePlanner.reach` +([`ScanEngine+Actions.swift`](https://github.com/SamGu-NRX/house-scanning-master/blob/ff95f1cb571e2f3ef56d9faae85880665ad0ca30/ios/HouseScan/Runtime/ScanEngine%2BActions.swift#L315-L319)). +`reach` +([`GuidancePlanner.swift`](https://github.com/SamGu-NRX/house-scanning-master/blob/ff95f1cb571e2f3ef56d9faae85880665ad0ca30/ios/HouseScanKit/Sources/HouseScanKit/Guidance/GuidancePlanner.swift#L101-L117)) +counts 6-inch cells outward from the meter. It stops at the first cell whose wall and ground are not both +covered. A cell is covered when each of its three sample rows has been seen from two positions: the wall +at 0, 3.25 and 6.5 ft up, and the ground at 0, 2 and 4 ft out. So if the ground right in front of the +meter hasn't been seen twice, the tap puts the end at the meter. + +After that, the coverage map ignores everything past the ends: +- `record` stops adding sightings outside them + ([`CoverageMap.swift`](https://github.com/SamGu-NRX/house-scanning-master/blob/ff95f1cb571e2f3ef56d9faae85880665ad0ca30/ios/HouseScanKit/Sources/HouseScanKit/Coverage/CoverageMap.swift#L332)); +- `coveredIntervals` and `groundDepthSpans` leave them out + ([L979](https://github.com/SamGu-NRX/house-scanning-master/blob/ff95f1cb571e2f3ef56d9faae85880665ad0ca30/ios/HouseScanKit/Sources/HouseScanKit/Coverage/CoverageMap.swift#L979), + [L527](https://github.com/SamGu-NRX/house-scanning-master/blob/ff95f1cb571e2f3ef56d9faae85880665ad0ca30/ios/HouseScanKit/Sources/HouseScanKit/Coverage/CoverageMap.swift#L527)). + +The walk still kept photos and accepted marks up to 19 ft away, and nothing on screen said they would be +dropped. The export then clamps the baseline to ±0.1 m around the meter +([`ScanEngine+Export.swift`](https://github.com/SamGu-NRX/house-scanning-master/blob/ff95f1cb571e2f3ef56d9faae85880665ad0ca30/ios/HouseScan/Runtime/ScanEngine%2BExport.swift#L84-L85)), +which is the ±0.328 ft the server echoes. This code is unchanged between the build's `657ab28` and the +branch head `ff95f1c`. + +The replay shows how long the scan was exposed to this. Replaying the 44 photos in order through the +coverage rules, with no ends marked, the covered stretch next to the meter stays at 0 ft on both sides +until photo 43: + +``` +photo x (ft) reach left reach right +k00001 0.38 0.0 ft 0.0 ft +... +k00030 19.69 0.0 ft 0.0 ft +... +k00042 6.02 0.0 ft 0.0 ft +k00043 5.54 1.0 ft 5.5 ft +``` + +At the meter, the wall rows were seen from the start. The ground 2 ft and 4 ft out was seen only by photos +41–43, taken after both ends were set, while the window and gas meter were being marked. So during the +walk, any "Can't get there" put that side's end at the meter. That matches the recording: the ground row by +the meter was amber at the right-hand tap. The runbook told the tester to use it at the left corner, only +17 in from the meter, and at the right corner if the end prompt never came. + +The replay also shows why the end prompt couldn't come: +- the ground 4 ft out was never seen twice past 5.5 ft right of the meter; +- the wall's top row, 6.5 ft up, was never seen twice past 10.5 ft. + +The prompt appears only after 20 ft of unbroken coverage. + +### Measurements + +These are the app's marks against the tape, both taken from the meter's centre line. Positive means the +app has it farther right or higher. + +| Feature | App − tape | Within 4 in | +|---|---|---| +| Window, right edge | −0.5 in | yes | +| Window, left edge | +5.4 in | no | +| Window, width | −5.9 in | no | +| Window, sill height | +1.9 in | yes | +| Gas meter stand-in, centre | 4.8 in nearer the meter | no | + +The window's width error comes almost entirely from its left edge. The gas meter is a one-tap mark with a +nominal 1 ft width. + +### Device-only claims + +The tester read screen text aloud but didn't say test IDs or haptics, so haptic claims remain unverified. +These were visible: + +| ID | Result | Evidence | +|---|---|---| +| O-1 | Not reached | The camera opened with no iOS prompt; permission was granted in an earlier run | +| F-2 | Pass | "Step a little closer to the wall" after an early tap | +| C-1 | Pass | The ring filled in under a second; the meter-number question followed | +| W-1 | Pass | Left, right, "Tilt down…" and "Take a step back" all appeared; the fog cleared after about 3 s | +| W-2 | Pass | The photo counter flashes | +| W-5 | Fail, B-16 | "Tap the ac unit" | +| W-7 | Not reached | The end prompt never appeared; see above | +| W-12 | Fail | Reproduced without a double tap: two ordinary taps on "Can't get there" gave "That's the whole wall" | +| U-1 | Measured | 2.9 s from "Looks complete" to the result | +| U-2 | Fail, known | About 2.2 s on "Sending measurements, 99%"; "Check clearances" never became active | +| R-1 to R-5 | Not reached | No spot, so no checks and no AR view | + +Not tried: W-4 (a refused mark), and runs 2 and 3. + +Other things the recording showed: +- **Marks outside the ends still count.** The AC stand-in, 16 ft past the right end, is still listed and + exported. +- **"Slow down" while standing still.** It appeared twice while the tester stood still to mark features. +- **No way to mark a battery.** The site's installed battery couldn't be marked, because the mark sheet + has no battery kind. +- **A tilt-prompt distance went backwards**, from 3 ft 3 in to 2 ft 9 in. + +## What this changes + +- **For the capture app (lane A owner's call):** "Can't get there" shouldn't be able to place an end + inside a stretch the tester has walked and photographed. Possible fixes: + - place the end at the farthest point seen, or at the phone's position along the wall, instead of at + the unbroken covered reach; + - refuse the tap and say what is missing ("tilt down to show the ground by your meter"); + - at least warn when an end lands within a foot of the meter. + + Separately, the walk shouldn't keep photos and marks it will drop without saying so. +- **For guidance:** the tilt prompts asked for the ground from 2 ft 9 in right of the meter outward, never in + front of the meter. That's the stretch `reach` needs first. Asking for it before "Walk slowly to your left" + would unblock `reach` early. The strip could also show that an end will land at the meter before the tap. +- **For the next runs:** the runbook now says to fill the strip next to the meter before any "Can't get + there". Testers should say test IDs and haptics aloud. Runs 2 and 3 are still to do. + +Raw material stays out of git: the recording, the scan and the tape readings. diff --git a/experiments/device-field-test/RUNBOOK.md b/experiments/device-field-test/RUNBOOK.md new file mode 100644 index 000000000..f547341a0 --- /dev/null +++ b/experiments/device-field-test/RUNBOOK.md @@ -0,0 +1,113 @@ +# Device test runbook: House Scan on a real iPhone + +The Simulator can't run ARKit, so the product description on `t3/verification` marks everything that needs +the live camera as unverified. This runbook collects those items in the order a person meets them while +scanning. Every test has an ID, so a screen recording with spoken IDs can be matched back to the claim. + +Sources, at `t3/verification` +[`63350b1`](https://github.com/SamGu-NRX/house-scanning-master/tree/63350b12495e7c69d18e6a4391e053a70f432dbd/verification/product): +the "Open questions and verification" section of each screen document, and the +[bug triage](https://github.com/SamGu-NRX/house-scanning-master/blob/63350b12495e7c69d18e6a4391e053a70f432dbd/verification/product/bug-triage.md) +for the `B-` numbers. "Known bug" means the triage already predicts the failure. The run confirms it on a device. + +## Before the run + +- Borrow a tape measure, painter's tape and a marker, plus a second phone for notes and for the call test (W-10). +- Set up the phone: + - Add Screen Recording to Control Center, and long-press it to turn the microphone on. Haptics don't + show in a recording, so say "haptic" or "no haptic" and the test ID out loud. + - Charge the phone, free a few GB and turn brightness up. + - Open `https://house-scanning-server.vercel.app/health` in Safari on the site's Wi-Fi. +- Check the TestFlight build: note its number, and check onboarding shows the "About 2 min" pill. +- Tape and write down the ground truth before anyone scans. [`setup-plan.html`](setup-plan.html) shows + the readings used for the mock wall. At minimum, measure along the wall from the meter's centre line to: + - both corners; + - each opening's edges; + - any gas meter; + - any existing battery. + + Also measure the sill and eave heights and the depth of open ground in front of the wall. +- Keep the recording, the exported scan and the readings in `private/` or `data/`. They show a real + site. Commit only derived numbers. + +## Run 1: full flow, clean wall + +| ID | Screen | Do | Expect | +|---|---|---|---| +| O-1 | Onboarding | Page through with Next, tap Allow camera, then Allow | The iOS camera prompt appears. Note when | +| O-2 | Onboarding | VoiceOver on, swipe the pages | Unknown: is the page number announced? | +| O-3 | Onboarding | Reduce Motion on, tap Next and Skip | Known: Skip still slides | +| F-2 | Find meter | Tap the meter the moment the camera opens, then again | "Step a little closer to the wall"; known: no haptic on the second refusal | +| F-1 | Find meter | Pan across the wall and ground for 5 s, tap the meter | A ring blooms, a success haptic, then the close-up | +| F-3 | Find meter | Tap before the wall is detected | Question: does it pin at the wrong depth? | +| F-4 | Find meter | Point at the sky until relocalizing | Question: "Point at the meter like this." shows no picture | +| C-1 | Close-up | Hold the meter in the circle | The ring fills in about 0.6 s, a light haptic, the walk begins 1.2 s later | +| C-2 | Close-up | Put the meter at the circle's edge | The drawn circle matches the captured area (device-only check) | +| W-1 | Walk | Follow each instruction | "Walk slowly to your left", "Tilt down…", "Take a step back". Is the fog readable? | +| W-2 | Walk | Watch the photo counter | It flashes green with no haptic | +| W-3 | Walk | Mark something, pick a kind, tap | A firm haptic on accept | +| W-4 | Walk | Tap a mark on the ceiling or sky twice | A warning haptic on the first refusal only | +| W-5 | Walk | Mark an AC unit | Known B-16: "Tap the ac unit" | +| W-7 | Walk | At about 20 ft: "Is this the … end?", then Wall ends here | Does the end land at the screen centre rather than the ring? Then the end question appears | +| W-8 | Walk | A wall that ends before 20 ft | Known B-06: only "Can't get there" is offered | +| W-9 | Walk | Move fast, aim at a blank surface, try a dark area | Tracking coaching appears | +| M-1 | Feature list | Read each mark and its distance aloud | Compare with the tape | +| G-1/G-2 | Gap request | Complete it, or tap I can't get there | A success haptic and the upload about 1.2 s later, or an immediate upload | +| U-1 | Upload | Time it | "About a minute" has never been measured | +| U-2 | Upload | Watch the steps | Known: stuck on "Sending photos, 99%", then "Checking your wall" is skipped | +| R-1 | Result | Read the headline and every "Measured … The rule is …" line aloud | A success haptic | +| R-2 | Result | Drag the 3D model vertically | Unknown: does it rotate the model or scroll the page? | +| R-3 | AR view | Walk 3 steps left and right | Does the battery sit on the real wall and stay put? | +| R-4 | AR view | Someone walks in front of the spot | Known: the drawing covers them | +| R-5 | AR view | Cover the lens for 3 s | Does the drawing hide while relocalizing? | + +**Before any "Can't get there" on the walk**, stand at the meter and tilt down until the coverage strip next +to the meter is fully covered. Then check the strip is covered without a gap from the meter to where you +stand. Run 1 shows why: the tap puts the wall's end at the edge of the unbroken covered stretch, and if +that stretch is empty, the end lands at the meter. See [README.md](README.md#result-run-1). + +How to fill it: +- Stand about 7 ft out from the meter and tilt down until the ground out to 4 ft from the wall is in view. +- Wait for the photo counter to flash, take a 2 ft step sideways, and wait for another flash. Do the same + with the wall up to 6½ ft. + +Each spot must be seen from two positions at least 0.25 m apart. Only kept photos count, and a photo is +kept after about 0.5 m of movement or 15° of turn. + +## Run 2: the same wall with clutter + +Put an obstacle in front of the meter or panel and repeat run 1. Add these: + +| ID | Do | Expect | +|---|---|---| +| C-4 | On the close-up, shake gently so frames alternate sharp and blurry, for 30 s | Known B-04: the way out may never appear | +| C-3 | Then point away for 4 s or more, twice | "Can't get a clear shot" after the first failed try; the close-up is skipped after the second | +| W-13 | Walk past the obstacle, look from the side, then tap Can't see past it | "Something is in front of the wall here"; the cells behind it turn hatched. Needs a build with LiDAR occlusion (after 1.1) and a Pro iPhone | +| M-2 | Tap Add something on the feature list | Known B-09: nothing visible happens | + +## Run 3: interruptions (separate short recordings) + +Group the tests into short recordings: W-12 then M-3; W-10 then W-11; U-4 then U-3; O-4 alone. A call or a +lock can end a screen recording, and this way it takes only its own tests with it. O-4 deletes the app and +every scan on it, so export scans first. + +| ID | Do | Expect | +|---|---|---| +| W-10 | Take a phone call during the walk, then return | Unknown: does it recover? Does the recording survive? | +| W-11 | Start a mark, tap once, cover the lens for more than 20 s | Back to Find the meter with no explanation; known: the half-done mark survives | +| W-12 | Tap Can't get there twice at the start of the walk | Known: "That's the whole wall" with nothing walked | +| M-3 | On the feature list, point away for more than 20 s | Known: the list and every mark vanish with no warning | +| U-3 | Lock the phone during the upload | Unknown | +| U-4 | Airplane mode before the upload, then off, then Try again | "You're offline", then recovery; watch for raw error text (B-02) | +| O-4 | Last: delete, reinstall, tap Don't Allow at the camera prompt | Known B-03: stuck on Find the meter with no Settings button | + +## After the run + +- Save the scan's `scene.json` and replay its coverage: + + ```sh + uv run coverage_replay.py path/to/scene.json + ``` + +- Put the claims each recording settled into the table in [README.md](README.md#result-run-1), with the + build number. diff --git a/experiments/device-field-test/coverage_replay.py b/experiments/device-field-test/coverage_replay.py new file mode 100644 index 000000000..86d5db9df --- /dev/null +++ b/experiments/device-field-test/coverage_replay.py @@ -0,0 +1,128 @@ +# /// script +# requires-python = ">=3.11" +# dependencies = ["numpy>=2"] +# /// +"""Replay a House Scan scene.json's kept photos through the app's coverage rules. + +For each kept photo, in order, prints how far the covered wall reaches on each side of the +meter: the same `reach` that "Can't get there" uses to place a wall end. Then prints, for +the cells either side of the meter, which photos saw each sample row. + +The rules mirror `CoverageMap` and `GuidancePlanner.reach` on `t3/ios-mvf` at ff95f1c: +- 6 in cells; +- wall rows at 0, 3.25 and 6.5 ft up; ground rows at 0, 2 and 4 ft out; +- a row counts when its two samples, a quarter and three quarters across the cell, are in view: + within 6 m, within 65 degrees of the surface normal, and 3 % inside the image edges; +- a cell is covered when every row was seen from two positions at least 0.25 m apart. + +Limits: it uses the exported (refined) wall line and meter for every photo, cannot see +tracking state, and ignores marked ends, so it shows what coverage would have been with no ends. + + uv run coverage_replay.py path/to/scene.json +""" + +import argparse +import json +from pathlib import Path + +import numpy as np + +FT = 0.3048 +CELL = 0.1524 +ROWS = {"wall": (0.0, 0.9906, 1.9812), "ground": (0.0, 0.6, 1.2)} +MAX_DISTANCE = 6.0 +MAX_ANGLE = np.radians(65) +MARGIN = 0.03 +BASELINE = 0.25 +MIN_DEPTH = 0.05 + + +def load(path: Path): + scene = json.loads(path.read_text()) + (x0, z0), (x1, z1) = scene["walls"][0]["baseline"] + direction = np.array([x1 - x0, z1 - z0]) + direction /= np.linalg.norm(direction) + along = np.array([direction[0], 0.0, direction[1]]) + outward = np.array([-direction[1], 0.0, direction[0]]) + meter = np.array(scene["meter"]["pos"]) * FT + origin = np.array([meter[0], 0.0, meter[2]]) + cameras = [] + for frame in scene["keyframes"]: + pose = np.array(frame["pose"], dtype=float).reshape(4, 4).T + pose[:3, 3] *= FT + cameras.append((frame["id"], pose, np.linalg.inv(pose), frame["intrinsics"], frame["w"], frame["h"])) + # Point the outward normal at the cameras, as the app's wall frame does. + mean_position = np.mean([c[1][:3, 3] for c in cameras], axis=0) + if np.dot(mean_position - origin, outward) < 0: + outward = -outward + return origin, along, outward, cameras + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("scene", type=Path) + parser.add_argument("--cells", type=int, default=45, help="cells to track on the right; 3 are tracked on the left") + args = parser.parse_args() + + origin, along, outward, cameras = load(args.scene) + up = np.array([0.0, 1.0, 0.0]) + + def point(band: str, s: float, offset: float) -> np.ndarray: + return origin + along * s + (up * offset if band == "wall" else outward * offset) + + def sees(camera, p: np.ndarray, normal: np.ndarray) -> bool: + _, pose, inverse, (fx, fy, cx, cy), w, h = camera + to_camera = pose[:3, 3] - p + distance = np.linalg.norm(to_camera) + if distance == 0 or distance > MAX_DISTANCE or np.dot(to_camera / distance, normal) < np.cos(MAX_ANGLE): + return False + local = inverse @ np.append(p, 1.0) + depth = -local[2] + if depth < MIN_DEPTH: + return False + u, v = cx + fx * local[0] / depth, cy - fy * local[1] / depth + return MARGIN * w <= u <= (1 - MARGIN) * w and MARGIN * h <= v <= (1 - MARGIN) * h + + def rows_seen(camera, band: str, index: int) -> list[int]: + low = index * CELL + samples = (low + CELL * 0.25, low + CELL * 0.75) + normal = outward if band == "wall" else up + return [r for r, offset in enumerate(ROWS[band]) if all(sees(camera, point(band, s, offset), normal) for s in samples)] + + indices = range(-3, args.cells) + positions = {(band, i): [[] for _ in ROWS[band]] for band in ROWS for i in indices} + covered: set[int] = set() + + def reach(step: int) -> float: + index, cells = (0 if step > 0 else -1), 0 + while index in covered: + cells, index = cells + 1, index + step + return cells * CELL / FT + + print("photo x (ft) reach left reach right") + for camera in cameras: + position = camera[1][:3, 3] + if np.dot(position - origin, outward) > 0: + for band in ROWS: + for i in indices: + for r in rows_seen(camera, band, i): + seen = positions[(band, i)][r] + if len(seen) < 2 and all(np.linalg.norm(q - position) >= BASELINE for q in seen): + seen.append(position) + for i in indices: + if all(len(row) >= 2 for band in ROWS for row in positions[(band, i)]): + covered.add(i) + x = np.dot(position - origin, along) / FT + print(f"{camera[0]} {x:7.2f} {reach(-1):8.1f} ft {reach(1):9.1f} ft") + + print("\nPhotos that saw each row of the cells either side of the meter") + for i in (-1, 0): + for band in ROWS: + for r, offset in enumerate(ROWS[band]): + who = [c[0] for c in cameras if r in rows_seen(c, band, i)] + label = f"{offset / FT:.1f} ft {'up' if band == 'wall' else 'out'}" + print(f"cell {i:+d} {band:6s} {label:11s} {', '.join(who) or '-'}") + + +if __name__ == "__main__": + main() diff --git a/experiments/device-field-test/setup-plan.html b/experiments/device-field-test/setup-plan.html new file mode 100644 index 000000000..23e91fb49 --- /dev/null +++ b/experiments/device-field-test/setup-plan.html @@ -0,0 +1,260 @@ + + + + +Mock wall: device test setup + + + + +

Mock wall: what to set up, measure and walk

+

Schematic, not to scale: the mock house wall used for run 1 on 2026-09-26. Letters A–K are the tape readings taken before scanning; test IDs are in RUNBOOK.md.

+ +
+ Painter's tape mark + Measure with the tape (letter = row in the table) + You change this + Already there, leave it +
+ +

1. Front view: standing on the turf, looking at the green meter wall

+ + + + + + + + + + + + + + wood corner + wood corner + + + + + + eave / overhang + + + + + + + + + + + METER + + + panel + + + disconnect + + + + + WINDOW + + + + Base + battery + (leave it) + + + + + + mark as "AC unit" + (stand-in, or use a box) + + + + GAS + tape paper here + + + + + + + covered table: MOVE OUT + + + + + + L + M + R + + + + + + + + + + + + + + + + A + B + C + D + E + F + G + H + J + + + F = turf upto the eave'sunderside + + + +

2. Top view: where things go and where you walk

+
+ + + + + + + + + + + + + + + concrete floor + + + turf + + + green meter wall + + + + + + + GAS + + METER + + window + + + battery + + + "AC unit" + folding table + + + + covered table + (now) + + + park it here, off the turf + for run 1 (back for run 2) + + + + K + + + + 1 + 2 + + + + + 3 + 4 + stay 6–8 ft from the wall, walk slowly + + + + + + + + +
#On the phone
1Start recording, open House Scan, go through onboarding. Stand about 6 ft in front of the meter and tap it (F-1).
2Step in until the meter fills the circle and hold still for the close-up (C-1).
3Back out to 6–8 ft. It says "walk left" first: go to the left corner, then all the way right. Tilt down when told so the turf is in view.
4Along the way: Mark something for GAS, the window and the "AC unit". At each corner, answer the end question with "turns a corner" (W-7, W-8). Then the list, upload, result and AR view.
+
+

The wall is probably under 20 ft, so "Wall ends here" may never appear (known bug B-06). If it doesn't, say so on the recording and use "Can't get there" at the corner.

+ +
+
+

3. Tape readings: write them down before anyone scans

+ + + + + + + + + + + + +
MeasureFrom → toReading
AWall lengthtape L → tape R, along the base___ ft ___ in
BMeter heightturf → meter box's bottom edge___ ft ___ in
CMeter to windowmeter centre line (tape M) → window's left frame edge___ ft ___ in
DWindow widthleft frame edge → right frame edge___ ft ___ in
ESill heightturf → window sill___ ft ___ in
FOverheadturf → eave's underside___ ft ___ in
GMeter to batterytape M → battery's near edge___ ft ___ in
HBattery sizewidth, and depth out from the wall___ × ___
JGas to meterGAS paper's centre → tape M___ ft ___ in
KTurf depthwall → concrete edge___ ft ___ in
+
+
+ +

4. Run order: one screen recording each, say the test ID out loud

+
+
Run 1: cleanCovered table parked off the turf, GAS paper up. +
+
Run 2: clutteredCovered table back in front of the panel. +
+
Run 3: break itShort separate recordings. +
+
+ + + diff --git a/experiments/device-field-test/setup-plan.png b/experiments/device-field-test/setup-plan.png new file mode 100644 index 000000000..90d0624bb Binary files /dev/null and b/experiments/device-field-test/setup-plan.png differ