diff --git a/ios/HouseScan.xcodeproj/project.pbxproj b/ios/HouseScan.xcodeproj/project.pbxproj index 28a1d8241..f623d8f14 100644 --- a/ios/HouseScan.xcodeproj/project.pbxproj +++ b/ios/HouseScan.xcodeproj/project.pbxproj @@ -163,6 +163,66 @@ "Fixtures/synthetic-wall-lidar/keyframes/k00041.confidence.u8", "Fixtures/synthetic-wall-lidar/keyframes/k00041.depth.f32", "Fixtures/synthetic-wall-lidar/keyframes/k00041.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00042.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00042.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00042.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00043.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00043.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00043.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00044.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00044.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00044.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00045.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00045.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00045.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00046.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00046.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00046.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00047.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00047.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00047.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00048.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00048.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00048.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00049.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00049.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00049.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00050.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00050.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00050.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00051.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00051.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00051.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00052.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00052.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00052.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00053.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00053.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00053.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00054.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00054.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00054.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00055.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00055.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00055.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00056.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00056.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00056.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00057.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00057.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00057.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00058.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00058.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00058.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00059.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00059.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00059.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00060.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00060.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00060.jpg", + "Fixtures/synthetic-wall-lidar/keyframes/k00061.confidence.u8", + "Fixtures/synthetic-wall-lidar/keyframes/k00061.depth.f32", + "Fixtures/synthetic-wall-lidar/keyframes/k00061.jpg", "Fixtures/synthetic-wall-lidar/session.json", "Fixtures/synthetic-wall/keyframes", "Fixtures/synthetic-wall/keyframes/k00001.jpg", @@ -206,6 +266,26 @@ "Fixtures/synthetic-wall/keyframes/k00039.jpg", "Fixtures/synthetic-wall/keyframes/k00040.jpg", "Fixtures/synthetic-wall/keyframes/k00041.jpg", + "Fixtures/synthetic-wall/keyframes/k00042.jpg", + "Fixtures/synthetic-wall/keyframes/k00043.jpg", + "Fixtures/synthetic-wall/keyframes/k00044.jpg", + "Fixtures/synthetic-wall/keyframes/k00045.jpg", + "Fixtures/synthetic-wall/keyframes/k00046.jpg", + "Fixtures/synthetic-wall/keyframes/k00047.jpg", + "Fixtures/synthetic-wall/keyframes/k00048.jpg", + "Fixtures/synthetic-wall/keyframes/k00049.jpg", + "Fixtures/synthetic-wall/keyframes/k00050.jpg", + "Fixtures/synthetic-wall/keyframes/k00051.jpg", + "Fixtures/synthetic-wall/keyframes/k00052.jpg", + "Fixtures/synthetic-wall/keyframes/k00053.jpg", + "Fixtures/synthetic-wall/keyframes/k00054.jpg", + "Fixtures/synthetic-wall/keyframes/k00055.jpg", + "Fixtures/synthetic-wall/keyframes/k00056.jpg", + "Fixtures/synthetic-wall/keyframes/k00057.jpg", + "Fixtures/synthetic-wall/keyframes/k00058.jpg", + "Fixtures/synthetic-wall/keyframes/k00059.jpg", + "Fixtures/synthetic-wall/keyframes/k00060.jpg", + "Fixtures/synthetic-wall/keyframes/k00061.jpg", "Fixtures/synthetic-wall/session.json", ); target = EF941BB37D13A698579DF2A6 /* HouseScanUITests */; diff --git a/ios/HouseScan/Runtime/Autopilot.swift b/ios/HouseScan/Runtime/Autopilot.swift index 25112c763..edde51070 100644 --- a/ios/HouseScan/Runtime/Autopilot.swift +++ b/ios/HouseScan/Runtime/Autopilot.swift @@ -34,7 +34,7 @@ final class Autopilot { guard await waitUntil(timeout: 60, { self.engine.replay != nil || self.engine.state.failure != nil }), let replay = engine.replay else { return fail("replay did not load") } - async let prepared: Void = replay.prepareHeldBack() + async let prepared: Void = replay.prepareHeldBack(endsWherePhoneStood: engine.options.autopilotCantGetThere) await pause(hold) await engine.waitForGate(.onboarding) engine.finishOnboarding() @@ -341,6 +341,10 @@ final class Autopilot { await pause(hold) await answerOpenSky() log("answered the overhead question: open sky; \(overheadSummary)") + // The rest of the tilt-up run (the synthetic replay's upper walk) plays out, so its + // views are kept before the walk ends. + guard let replay = engine.replay else { return } + _ = await waitUntil(timeout: 30) { !replay.isPlaying } } /// An overhead gap request: the engine plays the recording's tilt-up frames for it, and the diff --git a/ios/HouseScan/Runtime/ReplayPlayer.swift b/ios/HouseScan/Runtime/ReplayPlayer.swift index 4b734de19..47b18dcfa 100644 --- a/ios/HouseScan/Runtime/ReplayPlayer.swift +++ b/ios/HouseScan/Runtime/ReplayPlayer.swift @@ -83,11 +83,17 @@ final class ReplayPlayer { /// Finds the frames to hold back for the autopilot's gap loop. Heavy, so it runs off the /// main actor; call once before the walk. - func prepareHeldBack() async { - let planned = planned + /// Planned over the walk's frames only: the recording's closing tilt-up run is played for the + /// tilt-up step and for requests above the walk (`ScanEngine.tiltUpFrames`), never in the walk. + /// With `endsWherePhoneStood`, against the ends a walk ended by "Can't get there" sets, + /// 1 m inside them (the cells skipped there are within 0.5 m of the phone). + func prepareHeldBack(endsWherePhoneStood: Bool = false) async { + let walkEnd = ScanEngine.tiltUpFrames(in: self, map: CoverageMap(wall: wall)).lowerBound + let planned = Array(planned[...held` says the autopilot has finished with the screen and holds it still, for a UI + /// test that must audit it settled rather than while the autopilot drives it. func waitForGate(_ phase: ScanPhase) async { guard options.autopilot, let gate = options.autopilotGate else { return } let file = gate.appending(path: phase.rawValue) + if !FileManager.default.fileExists(atPath: file.path) { + try? Data().write(to: gate.appending(path: "\(phase.rawValue).held")) + } let deadline = ContinuousClock.now + .seconds(120) while ContinuousClock.now < deadline, !FileManager.default.fileExists(atPath: file.path) { try? await Task.sleep(for: .milliseconds(100)) diff --git a/ios/HouseScan/UI/Components/InstructionCard.swift b/ios/HouseScan/UI/Components/InstructionCard.swift index ca1701384..9a3773c21 100644 --- a/ios/HouseScan/UI/Components/InstructionCard.swift +++ b/ios/HouseScan/UI/Components/InstructionCard.swift @@ -59,7 +59,7 @@ struct InstructionCard: View { .foregroundStyle(Palette.chalk) .padding(.horizontal, 16) .frame(minHeight: Metrics.minTarget) - .background(.white.opacity(0.14), in: .capsule) + .background(Palette.replyFill, in: .capsule) .contentShape(.capsule) .buttonStyle(PressableStyle()) // The colours are set above and PressableStyle doesn't read isEnabled, so the diff --git a/ios/HouseScan/UI/Theme/Theme.swift b/ios/HouseScan/UI/Theme/Theme.swift index 77740366e..2313e425f 100644 --- a/ios/HouseScan/UI/Theme/Theme.swift +++ b/ios/HouseScan/UI/Theme/Theme.swift @@ -17,6 +17,11 @@ enum Palette { /// is about 4.9:1 on paper, but the audit rated "See it on your wall" as only nearly /// passing; white on this is about 6.2:1. static let signalFill = Color(red: 26 / 255.0, green: 88 / 255.0, blue: 214 / 255.0) + /// A reply button on the dark instruction card: what 14% white over `ink` (#0B1220) looks like, + /// as a solid colour (#2D333F), so the button's contrast with `chalk` text (about 11:1) doesn't + /// depend on how the translucent layer is composited over the camera behind the card. The + /// accessibility audit rejected the translucent version on CI's LiDAR replay. + static let replyFill = Color(red: 45 / 255.0, green: 51 / 255.0, blue: 63 / 255.0) static let unseen = Color("CoverageUnseen") static let seen = Color("CoverageSeen") static let covered = Color("CoverageCovered") diff --git a/ios/HouseScanKit/Sources/HouseScanKit/Guidance/GapPlanner.swift b/ios/HouseScanKit/Sources/HouseScanKit/Guidance/GapPlanner.swift index ec74fb016..88f960d96 100644 --- a/ios/HouseScanKit/Sources/HouseScanKit/Guidance/GapPlanner.swift +++ b/ios/HouseScanKit/Sources/HouseScanKit/Guidance/GapPlanner.swift @@ -49,6 +49,15 @@ public struct GapPlan: Sendable, Equatable { /// Nil for a request built in meters, whose span is then read at the export's four decimals. public var requestedSpanFt: ClosedRange? + /// A request a view tilted up at the wall answers: something overhead, or the wall seen + /// higher than the walk's band. + public var asksAboveTheWalk: Bool { + switch need { + case .overhead, .wallUp: true + case .cells, .groundOut, .walkOut: false + } + } + public init( band: SurfaceBand, span: ClosedRange, reason: Reason, need: Need = .cells, requestedOutFt: Double? = nil, requestedSpanFt: ClosedRange? = nil diff --git a/ios/HouseScanKit/Sources/HouseScanKit/Replay/ReplayPlanning.swift b/ios/HouseScanKit/Sources/HouseScanKit/Replay/ReplayPlanning.swift index d616f57fc..d207eb48c 100644 --- a/ios/HouseScanKit/Sources/HouseScanKit/Replay/ReplayPlanning.swift +++ b/ios/HouseScanKit/Sources/HouseScanKit/Replay/ReplayPlanning.swift @@ -133,16 +133,32 @@ public enum ReplayPlanning { public var gap: GapPlan } + /// Where the walk that leaves a window out will set its ends. + public enum EndPolicy: Sendable, Equatable { + /// Marked at the covered extremes, as the autopilot's `markEnds` does. + case coveredExtremes + /// Set where the phone stood farthest each way ("Can't get there", `WalkedEnd`), after + /// "Can't get there" on the ground by the meter. The gap must then lie `margin` meters + /// inside those ends: "Can't get there" at an end also skips the cells there. + case walked(margin: Float) + } + /// Chooses frames to hold back from the autopilot's walk so the gap loop has something real /// to do: without them the planner finds a gap, and replaying them alone closes it. /// /// Candidates are contiguous windows of 3 to 10 frames, tried from the middle of the walk /// outward. The gap must lie within where the rest of the walk went (`isWithinWalk`): a gap /// past the farthest frame the phone stood at falls outside ends set where the phone is - /// ("Can't get there"), and then nothing asks for it. Each is screened on coverage rebuilt from what every frame sees (computed once), - /// then confirmed with the full auto-capture simulation the app runs. Nil when no window works, - /// for example when the planner's nearest gap is one no frame of the replay can close. - public static func heldBackWindow(frames: [PlannedFrame], wall: WallFrame, planner: GapPlanner = GapPlanner(), config: CoverageConfig = CoverageConfig()) -> HeldBackWindow? { + /// ("Can't get there"), and then nothing asks for it. With `ends` `.walked`, the window is + /// planned against the ends that flow sets and the cells it skips, so the gap it leaves is + /// one that flow asks for. Each is screened on coverage rebuilt from what every frame sees + /// (computed once), then confirmed with the full auto-capture simulation the app runs. Nil + /// when no window works, for example when the planner's nearest gap is one no frame of the + /// replay can close. + public static func heldBackWindow( + frames: [PlannedFrame], wall: WallFrame, ends policy: EndPolicy = .coveredExtremes, + planner: GapPlanner = GapPlanner(), config: CoverageConfig = CoverageConfig() + ) -> HeldBackWindow? { guard frames.count >= 6 else { return nil } let empty = CoverageMap(wall: wall, config: config) let sightings = frames.map { $0.trackingNormal ? empty.visibleCells(from: $0.camera) : [] } @@ -157,17 +173,18 @@ public enum ReplayPlanning { for index in kept.sorted() where !window.contains(index) { rest.record(sightings[index], from: frames[index].camera.position) } - guard let ends = coveredExtremes(rest) else { continue } - rest.setEnd(.left, at: ends.lowerBound) - rest.setEnd(.right, at: ends.upperBound) + let stood = frames.indices.filter { !window.contains($0) }.map { frames[$0] } + guard let ends = self.ends(policy, rest, stood: stood, wall: wall) else { continue } + prepare(&rest, ends: ends, policy: policy) guard let gap = planner.plan(rest), - isWithinWalk(gap, kept.sorted().filter { !window.contains($0) }.map { frames[$0] }, wall: wall) else { continue } + isWithinWalk(gap, kept.sorted().filter { !window.contains($0) }.map { frames[$0] }, wall: wall), + isInside(gap, ends: ends, policy: policy) else { continue } var restored = rest for index in window { restored.record(sightings[index], from: frames[index].camera.position) } guard planner.isSatisfied(gap, restored) else { continue } // Screened: confirm with the exact auto-capture path, a few times at most. confirmations += 1 - if let confirmed = confirm(frames, window: window, wall: wall, planner: planner, config: config) { + if let confirmed = confirm(frames, window: window, wall: wall, policy: policy, planner: planner, config: config) { return confirmed } if confirmations >= 12 { return nil } @@ -199,18 +216,43 @@ public enum ReplayPlanning { return low...high } - private static func confirm(_ frames: [PlannedFrame], window: Range, wall: WallFrame, planner: GapPlanner, config: CoverageConfig) -> HeldBackWindow? { + private static func confirm( + _ frames: [PlannedFrame], window: Range, wall: WallFrame, policy: EndPolicy, planner: GapPlanner, config: CoverageConfig + ) -> HeldBackWindow? { var rest = frames rest.removeSubrange(window) var walked = simulateWalk(rest, wall: wall, config: config) - guard let ends = coveredExtremes(walked) else { return nil } - walked.setEnd(.left, at: ends.lowerBound) - walked.setEnd(.right, at: ends.upperBound) - guard let gap = planner.plan(walked), isWithinWalk(gap, rest, wall: wall), + guard let ends = self.ends(policy, walked, stood: rest, wall: wall) else { return nil } + prepare(&walked, ends: ends, policy: policy) + guard let gap = planner.plan(walked), isWithinWalk(gap, rest, wall: wall), isInside(gap, ends: ends, policy: policy), satisfiedWithLeadIn(frames, window: window, walked: walked, gap: gap, planner: planner) else { return nil } return HeldBackWindow(frames: window, ends: ends, gap: gap) } + private static func ends(_ policy: EndPolicy, _ map: CoverageMap, stood: [PlannedFrame], wall: WallFrame) -> ClosedRange? { + switch policy { + case .coveredExtremes: + return coveredExtremes(map) + case .walked: + let s = stood.map { wall.wallPoint($0.camera.position).s } + guard let low = s.min(), let high = s.max(), low < 0, high > 0 else { return nil } + return low...high + } + } + + /// Sets the ends, and for a walked flow skips the ground by the meter, as "Can't get there" + /// on the walk's first request does (`GuidancePlanner.groundByMeterDone`, ±0.5 m). + private static func prepare(_ map: inout CoverageMap, ends: ClosedRange, policy: EndPolicy) { + map.setEnd(.left, at: ends.lowerBound) + map.setEnd(.right, at: ends.upperBound) + if case .walked = policy { map.markSkipped(.ground, -0.5...0.5) } + } + + private static func isInside(_ gap: GapPlan, ends: ClosedRange, policy: EndPolicy) -> Bool { + guard case .walked(let margin) = policy else { return true } + return ends.lowerBound + margin <= gap.span.lowerBound && gap.span.upperBound <= ends.upperBound - margin + } + /// Whether `gap` lies between the farthest positions along the wall `frames` stood at. private static func isWithinWalk(_ gap: GapPlan, _ frames: [PlannedFrame], wall: WallFrame) -> Bool { let s = frames.map { wall.wallPoint($0.camera.position).s } diff --git a/ios/HouseScanKit/Tests/HouseScanKitTests/ReplaySessionTests.swift b/ios/HouseScanKit/Tests/HouseScanKitTests/ReplaySessionTests.swift index 8f0b4c776..ff1b3ddc7 100644 --- a/ios/HouseScanKit/Tests/HouseScanKitTests/ReplaySessionTests.swift +++ b/ios/HouseScanKit/Tests/HouseScanKitTests/ReplaySessionTests.swift @@ -155,8 +155,9 @@ struct ReplaySessionTests { #expect(try ReplaySession.decode(sessionJSON: Data(json.utf8)).declaredWall == nil) } - /// The fixture written by ios/Tools/make-synthetic-replay.swift: 41 frames (38 for the close-up - /// and the walk, 3 tilted up for the tilt-up step), wall on z = 0 facing +z, meter at + /// The fixture written by ios/Tools/make-synthetic-replay.swift: 61 frames (38 for the close-up + /// and the walk, 3 tilted up for the tilt-up step, then 20 more tilted up along the wall for + /// the upper rows), wall on z = 0 facing +z, meter at /// (0, 1.5, 0), and the first frame aimed straight at the meter. @Test func decodesSyntheticWallFixture() throws { let folder = URL(fileURLWithPath: #filePath) @@ -165,7 +166,7 @@ struct ReplaySessionTests { .standardizedFileURL let session = try ReplaySession.load(folder: folder) #expect(session.id == "synthetic-wall") - #expect(session.frames.count == 41) + #expect(session.frames.count == 61) #expect(session.frames.allSatisfy { $0.trackingNormal }) let wall = try #require(session.declaredWall) @@ -183,10 +184,10 @@ struct ReplaySessionTests { /// The app plays the fixture's closing run of tilted-up frames for the tilt-up step and for /// overhead requests: views reaching at least 0.7 m above the wall band's 2.286 m top - /// (ScanEngine.tiltUpAbove). The three closing frames do, over the stretch by the meter. The - /// walk before them (frames 7 to 37, pitched 20 degrees down) reaches about 2.0 m, short of - /// the band's top, so the run is exactly those three. The level views of the meter before the - /// walk may reach it. + /// (ScanEngine.tiltUpAbove). The closing 23 frames all do, and the three from the tilt-up + /// position (38 to 40) over the stretch by the meter. The walk before them (frames 7 to 37, + /// pitched 20 degrees down) reaches about 2.0 m, short of the band's top, so the run starts + /// at frame 38. The level views of the meter before the walk may reach it. @Test func syntheticWallFixtureEndsWithTiltUpViews() throws { let folder = URL(fileURLWithPath: #filePath) .deletingLastPathComponent() @@ -203,9 +204,10 @@ struct ReplaySessionTests { let tilted = map.config.wallCaptureHeight + 0.7 let walkIsNotTiltedUp = reaches[7..<38].allSatisfy { reach in reach.allSatisfy { $0.out < tilted } } #expect(walkIsNotTiltedUp) - for reach in reaches.suffix(3) { + for reach in reaches[38..<41] { let overMeter = reach.contains { $0.out >= tilted && $0.span.contains(0) && $0.span.contains(1) } #expect(overMeter, "\(reach)") } + #expect(reaches[38...].allSatisfy { reach in reach.contains { $0.out >= tilted } }) } } diff --git a/ios/HouseScanKit/Tests/HouseScanKitTests/UpperWallFixtureTests.swift b/ios/HouseScanKit/Tests/HouseScanKitTests/UpperWallFixtureTests.swift new file mode 100644 index 000000000..18a01e8fb --- /dev/null +++ b/ios/HouseScanKit/Tests/HouseScanKitTests/UpperWallFixtureTests.swift @@ -0,0 +1,84 @@ +import Foundation +@testable import HouseScanKit +import simd +import Testing + +/// The synthetic replay reaches the wall above 6.5 ft only with its appended upper walk (frames +/// 42 to 61). Its first 41 frames, the recording before, stay the case that sees it only to +/// 6.5 ft: the walk's views stop at 2.007 m, short of the 7 ft row, and the three tilt-up views +/// share one position, so the upper rows never get a second. +@Suite struct UpperWallFixtureTests { + static func frames(limit: Int? = nil) throws -> (wall: WallFrame, frames: [PlannedFrame]) { + let folder = URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .appendingPathComponent("../../../HouseScanUITests/Fixtures/synthetic-wall") + .standardizedFileURL + let session = try ReplaySession.load(folder: folder) + let declared = try #require(session.declaredWall) + let wall = try #require(WallFrame(meter: declared.meter, outward: declared.outward, groundY: declared.groundY)) + let frames = session.frames.prefix(limit ?? session.frames.count).map { frame in + PlannedFrame( + camera: CameraFrame(cameraToWorld: frame.cameraToWorld, intrinsics: frame.intrinsics, imageSize: SIMD2(Float(frame.width), Float(frame.height))), + timestamp: frame.timestamp, trackingNormal: frame.trackingNormal) + } + return (wall, frames) + } + + /// Every frame through auto-capture as the app plays them: the walk, then the tilt-up run. + static func seenHeight(over span: ClosedRange, limit: Int? = nil) throws -> Float { + let (wall, frames) = try Self.frames(limit: limit) + let map = ReplayPlanning.simulateWalk(frames, wall: wall) + let spans = map.wallSeenSpans() + let cells = map.indices(overlapping: span) + return cells.map { index in + let middle = (map.cellRange(index).lowerBound + map.cellRange(index).upperBound) / 2 + return spans.first { $0.span.contains(middle) }?.out ?? 0 + }.min() ?? 0 + } + + /// The server's wall request on this replay: -3.542852 to 6.542852 ft, out_ft 6.500001. + static let request: ClosedRange = (-3.542852 * 0.3048)...(6.542852 * 0.3048) + static let requestOut: Double = 6.500001 + + @Test func theFirst41FramesSeeTheWallTo6Point5Feet() throws { + let height = try Self.seenHeight(over: Self.request, limit: 41) + #expect(SceneExport.feetDown(height) == 6.5) + #expect(SceneExport.feetDown(height) < Self.requestOut) + } + + @Test func theUpperWalkSeesItAboveTheRequest() throws { + let height = try Self.seenHeight(over: Self.request) + #expect(SceneExport.feetDown(height) > Self.requestOut) + #expect(nearlyEqual(height, CoverageConfig().wallCaptureHeight)) + // The request plans and settles against the map the replay builds. + let (wall, frames) = try Self.frames() + let map = ReplayPlanning.simulateWalk(frames, wall: wall) + let item = try JSONDecoder().decode(PlacementMissingEvidence.self, from: Data( + #"{"kind":"band","band":"wall","span_ft":[-3.542852,6.542852],"out_ft":6.500001,"message":"m"}"#.utf8)) + let plan = try #require(GapPlanner().plan(for: item, leftEnd: nil, rightEnd: nil, limitEnds: [])) + #expect(plan.asksAboveTheWalk) + #expect(GapPlanner().isSatisfied(plan, map)) + } + + /// The "Can't get there" flow ends the walk where the phone stood farthest each way and skips + /// the ground by the meter. Planned for that flow, the held-back window leaves a gap 1 m + /// inside those ends, which that walk (without the window) still asks for, and the window's + /// frames close. Planned against the covered extremes it could lie past them, and then the + /// flow never reached a gap request. + @Test func theCantGetThereFlowsHeldBackGapIsInsideItsEnds() throws { + let (wall, all) = try Self.frames() + let walk = Array(all.prefix(38)) + let held = try #require(ReplayPlanning.heldBackWindow(frames: walk, wall: wall, ends: .walked(margin: 1))) + var rest = walk + rest.removeSubrange(held.frames) + let stood = rest.map { wall.wallPoint($0.camera.position).s } + let ends = (stood.min() ?? 0)...(stood.max() ?? 0) + #expect(held.ends == ends) + #expect(ends.lowerBound + 1 <= held.gap.span.lowerBound && held.gap.span.upperBound <= ends.upperBound - 1) + var walked = ReplayPlanning.simulateWalk(rest, wall: wall) + walked.setEnd(.left, at: ends.lowerBound) + walked.setEnd(.right, at: ends.upperBound) + walked.markSkipped(.ground, -0.5...0.5) + #expect(GapPlanner().plan(walked) == held.gap) + } +} diff --git a/ios/HouseScanKit/Tests/HouseScanKitTests/WalkingHeightTests.swift b/ios/HouseScanKit/Tests/HouseScanKitTests/WalkingHeightTests.swift index 1fae98d7c..7662f9c02 100644 --- a/ios/HouseScanKit/Tests/HouseScanKitTests/WalkingHeightTests.swift +++ b/ios/HouseScanKit/Tests/HouseScanKitTests/WalkingHeightTests.swift @@ -21,7 +21,8 @@ import Testing var capture = AutoCapture() var planner = GuidancePlanner() var tasks: [GuidanceTask] = [] - for frame in session.frames.dropLast(3) { + // Up to the closing tilt-up run, frame 38 on, which the walk doesn't play. + for frame in session.frames.prefix(38) { let camera = CameraFrame(cameraToWorld: frame.cameraToWorld, intrinsics: frame.intrinsics, imageSize: SIMD2(Float(frame.width), Float(frame.height))) let sample = FrameSample(timestamp: frame.timestamp, camera: camera, tracking: .normal, quality: nil) if capture.evaluate(sample, newlySeenCells: map.newlySeenCount(from: camera)).isKeep { diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00042.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00042.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00042.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00042.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00042.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00042.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00042.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00042.jpg new file mode 100644 index 000000000..4cd184707 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00042.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00043.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00043.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00043.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00043.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00043.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00043.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00043.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00043.jpg new file mode 100644 index 000000000..1c70434f7 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00043.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00044.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00044.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00044.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00044.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00044.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00044.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00044.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00044.jpg new file mode 100644 index 000000000..7d578afad Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00044.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00045.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00045.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00045.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00045.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00045.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00045.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00045.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00045.jpg new file mode 100644 index 000000000..ed47fa8ba Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00045.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00046.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00046.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00046.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00046.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00046.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00046.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00046.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00046.jpg new file mode 100644 index 000000000..cb6a14957 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00046.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00047.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00047.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00047.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00047.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00047.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00047.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00047.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00047.jpg new file mode 100644 index 000000000..952b98e10 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00047.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00048.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00048.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00048.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00048.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00048.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00048.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00048.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00048.jpg new file mode 100644 index 000000000..11d98cced Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00048.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00049.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00049.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00049.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00049.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00049.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00049.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00049.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00049.jpg new file mode 100644 index 000000000..bbf4be747 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00049.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00050.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00050.confidence.u8 new file mode 100644 index 000000000..e76952e6e Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00050.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00050.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00050.depth.f32 new file mode 100644 index 000000000..03330264b Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00050.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00050.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00050.jpg new file mode 100644 index 000000000..e98c59305 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00050.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00051.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00051.confidence.u8 new file mode 100644 index 000000000..db7362287 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00051.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00051.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00051.depth.f32 new file mode 100644 index 000000000..3c256dd29 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00051.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00051.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00051.jpg new file mode 100644 index 000000000..74665d3ba Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00051.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00052.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00052.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00052.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00052.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00052.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00052.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00052.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00052.jpg new file mode 100644 index 000000000..42ccce680 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00052.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00053.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00053.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00053.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00053.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00053.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00053.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00053.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00053.jpg new file mode 100644 index 000000000..8c1bee880 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00053.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00054.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00054.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00054.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00054.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00054.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00054.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00054.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00054.jpg new file mode 100644 index 000000000..3cb85b85a Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00054.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00055.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00055.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00055.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00055.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00055.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00055.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00055.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00055.jpg new file mode 100644 index 000000000..91b09fb6b Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00055.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00056.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00056.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00056.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00056.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00056.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00056.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00056.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00056.jpg new file mode 100644 index 000000000..f6a8d7255 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00056.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00057.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00057.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00057.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00057.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00057.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00057.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00057.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00057.jpg new file mode 100644 index 000000000..11c868103 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00057.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00058.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00058.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00058.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00058.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00058.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00058.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00058.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00058.jpg new file mode 100644 index 000000000..59c0d0852 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00058.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00059.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00059.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00059.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00059.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00059.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00059.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00059.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00059.jpg new file mode 100644 index 000000000..1df009081 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00059.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00060.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00060.confidence.u8 new file mode 100644 index 000000000..4dadb8120 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00060.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00060.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00060.depth.f32 new file mode 100644 index 000000000..bbaa61417 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00060.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00060.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00060.jpg new file mode 100644 index 000000000..2199bd591 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00060.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00061.confidence.u8 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00061.confidence.u8 new file mode 100644 index 000000000..eec9c2c93 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00061.confidence.u8 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00061.depth.f32 b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00061.depth.f32 new file mode 100644 index 000000000..83983f6d7 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00061.depth.f32 differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00061.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00061.jpg new file mode 100644 index 000000000..b29e44e9b Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/keyframes/k00061.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/session.json b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/session.json index ca5843c2e..d24f4ac10 100644 --- a/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/session.json +++ b/ios/HouseScanUITests/Fixtures/synthetic-wall-lidar/session.json @@ -1620,6 +1620,786 @@ "timestamp" : 125.40000000000001, "tracking" : "normal", "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00042.confidence.u8", + "file" : "keyframes\/k00042.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00042", + "img" : "keyframes\/k00042.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 0.69999999999999996, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 126.40000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00043.confidence.u8", + "file" : "keyframes\/k00043.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00043", + "img" : "keyframes\/k00043.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 1.2, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 126.95, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00044.confidence.u8", + "file" : "keyframes\/k00044.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00044", + "img" : "keyframes\/k00044.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 1.7, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 127.5, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00045.confidence.u8", + "file" : "keyframes\/k00045.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00045", + "img" : "keyframes\/k00045.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 2.2000000000000002, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 128.05000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00046.confidence.u8", + "file" : "keyframes\/k00046.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00046", + "img" : "keyframes\/k00046.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 2.7000000000000002, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 128.59999999999999, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00047.confidence.u8", + "file" : "keyframes\/k00047.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00047", + "img" : "keyframes\/k00047.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 3.2000000000000002, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 129.15000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00048.confidence.u8", + "file" : "keyframes\/k00048.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00048", + "img" : "keyframes\/k00048.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 3.7000000000000002, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 129.69999999999999, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00049.confidence.u8", + "file" : "keyframes\/k00049.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00049", + "img" : "keyframes\/k00049.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 4.2000000000000002, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 130.25, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00050.confidence.u8", + "file" : "keyframes\/k00050.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00050", + "img" : "keyframes\/k00050.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 4.7000000000000002, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 130.80000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00051.confidence.u8", + "file" : "keyframes\/k00051.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00051", + "img" : "keyframes\/k00051.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 5.2000000000000002, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 131.34999999999999, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00052.confidence.u8", + "file" : "keyframes\/k00052.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00052", + "img" : "keyframes\/k00052.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -0.29999999999999999, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 138.40000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00053.confidence.u8", + "file" : "keyframes\/k00053.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00053", + "img" : "keyframes\/k00053.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -0.80000000000000004, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 138.94999999999999, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00054.confidence.u8", + "file" : "keyframes\/k00054.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00054", + "img" : "keyframes\/k00054.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -1.3, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 139.5, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00055.confidence.u8", + "file" : "keyframes\/k00055.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00055", + "img" : "keyframes\/k00055.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -1.8, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 140.05000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00056.confidence.u8", + "file" : "keyframes\/k00056.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00056", + "img" : "keyframes\/k00056.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -2.2999999999999998, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 140.59999999999999, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00057.confidence.u8", + "file" : "keyframes\/k00057.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00057", + "img" : "keyframes\/k00057.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -2.7999999999999998, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 141.15000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00058.confidence.u8", + "file" : "keyframes\/k00058.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00058", + "img" : "keyframes\/k00058.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -3.2999999999999998, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 141.69999999999999, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00059.confidence.u8", + "file" : "keyframes\/k00059.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00059", + "img" : "keyframes\/k00059.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -3.7999999999999998, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 142.25, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00060.confidence.u8", + "file" : "keyframes\/k00060.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00060", + "img" : "keyframes\/k00060.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -4.2999999999999998, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 142.80000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : { + "confidenceFile" : "keyframes\/k00061.confidence.u8", + "file" : "keyframes\/k00061.depth.f32", + "h" : 192, + "w" : 256 + }, + "h" : 480, + "id" : "k00061", + "img" : "keyframes\/k00061.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -4.7999999999999998, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 143.34999999999999, + "tracking" : "normal", + "w" : 640 } ], "measurements" : [ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00042.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00042.jpg new file mode 100644 index 000000000..4cd184707 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00042.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00043.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00043.jpg new file mode 100644 index 000000000..1c70434f7 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00043.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00044.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00044.jpg new file mode 100644 index 000000000..7d578afad Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00044.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00045.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00045.jpg new file mode 100644 index 000000000..ed47fa8ba Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00045.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00046.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00046.jpg new file mode 100644 index 000000000..cb6a14957 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00046.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00047.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00047.jpg new file mode 100644 index 000000000..952b98e10 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00047.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00048.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00048.jpg new file mode 100644 index 000000000..11d98cced Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00048.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00049.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00049.jpg new file mode 100644 index 000000000..bbf4be747 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00049.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00050.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00050.jpg new file mode 100644 index 000000000..e98c59305 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00050.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00051.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00051.jpg new file mode 100644 index 000000000..74665d3ba Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00051.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00052.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00052.jpg new file mode 100644 index 000000000..42ccce680 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00052.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00053.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00053.jpg new file mode 100644 index 000000000..8c1bee880 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00053.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00054.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00054.jpg new file mode 100644 index 000000000..3cb85b85a Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00054.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00055.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00055.jpg new file mode 100644 index 000000000..91b09fb6b Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00055.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00056.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00056.jpg new file mode 100644 index 000000000..f6a8d7255 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00056.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00057.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00057.jpg new file mode 100644 index 000000000..11c868103 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00057.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00058.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00058.jpg new file mode 100644 index 000000000..59c0d0852 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00058.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00059.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00059.jpg new file mode 100644 index 000000000..1df009081 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00059.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00060.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00060.jpg new file mode 100644 index 000000000..2199bd591 Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00060.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00061.jpg b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00061.jpg new file mode 100644 index 000000000..b29e44e9b Binary files /dev/null and b/ios/HouseScanUITests/Fixtures/synthetic-wall/keyframes/k00061.jpg differ diff --git a/ios/HouseScanUITests/Fixtures/synthetic-wall/session.json b/ios/HouseScanUITests/Fixtures/synthetic-wall/session.json index f581aefb3..449fedf3c 100644 --- a/ios/HouseScanUITests/Fixtures/synthetic-wall/session.json +++ b/ios/HouseScanUITests/Fixtures/synthetic-wall/session.json @@ -1415,6 +1415,686 @@ "timestamp" : 125.40000000000001, "tracking" : "normal", "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00042", + "img" : "keyframes\/k00042.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 0.69999999999999996, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 126.40000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00043", + "img" : "keyframes\/k00043.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 1.2, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 126.95, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00044", + "img" : "keyframes\/k00044.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 1.7, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 127.5, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00045", + "img" : "keyframes\/k00045.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 2.2000000000000002, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 128.05000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00046", + "img" : "keyframes\/k00046.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 2.7000000000000002, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 128.59999999999999, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00047", + "img" : "keyframes\/k00047.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 3.2000000000000002, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 129.15000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00048", + "img" : "keyframes\/k00048.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 3.7000000000000002, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 129.69999999999999, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00049", + "img" : "keyframes\/k00049.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 4.2000000000000002, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 130.25, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00050", + "img" : "keyframes\/k00050.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 4.7000000000000002, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 130.80000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00051", + "img" : "keyframes\/k00051.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + 5.2000000000000002, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 131.34999999999999, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00052", + "img" : "keyframes\/k00052.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -0.29999999999999999, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 138.40000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00053", + "img" : "keyframes\/k00053.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -0.80000000000000004, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 138.94999999999999, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00054", + "img" : "keyframes\/k00054.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -1.3, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 139.5, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00055", + "img" : "keyframes\/k00055.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -1.8, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 140.05000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00056", + "img" : "keyframes\/k00056.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -2.2999999999999998, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 140.59999999999999, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00057", + "img" : "keyframes\/k00057.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -2.7999999999999998, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 141.15000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00058", + "img" : "keyframes\/k00058.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -3.2999999999999998, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 141.69999999999999, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00059", + "img" : "keyframes\/k00059.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -3.7999999999999998, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 142.25, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00060", + "img" : "keyframes\/k00060.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -4.2999999999999998, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 142.80000000000001, + "tracking" : "normal", + "w" : 640 + }, + { + "depth" : null, + "h" : 480, + "id" : "k00061", + "img" : "keyframes\/k00061.jpg", + "intrinsics" : [ + 500, + 500, + 320, + 240 + ], + "pose" : [ + -0, + -0.92718385456678731, + -0.37460659341591196, + 0, + 0.99999999999999989, + -0, + 0, + 0, + -0, + -0.37460659341591201, + 0.92718385456678742, + 0, + -4.7999999999999998, + 1.5, + 2.6000000000000001, + 1 + ], + "reason" : "motion", + "timestamp" : 143.34999999999999, + "tracking" : "normal", + "w" : 640 } ], "measurements" : [ diff --git a/ios/HouseScanUITests/FullFlowUITests.swift b/ios/HouseScanUITests/FullFlowUITests.swift index 06e2177a9..093cc6611 100644 --- a/ios/HouseScanUITests/FullFlowUITests.swift +++ b/ios/HouseScanUITests/FullFlowUITests.swift @@ -14,6 +14,17 @@ final class FullFlowUITests: XCTestCase { /// Screens in the order the flow must show them. static let flow = ["onboarding", "findMeter", "meterCloseUp", "wallWalk", "markFeatures", "gapRequest", "uploading", "spotConfirm", "result", "resultAR"] + /// Screens audited only once the autopilot has finished with them and holds them still + /// (`.held` in the gate folder). On the walk the autopilot marks features, sets the + /// ends and tilts up while the replay plays, so the screen changes several times a second: + /// an audit started when the walk appeared read cards and buttons mid-crossfade, each pass a + /// different transition, and a copy fading out has no identifier, so two passes' transient + /// issues matched on the label or the "no element" key and failed as one lasting issue (CI + /// runs 36304552823, 36305050676, 36307476187). Held, the walk stays in its last state, both + /// ends set and "Done with this wall" on offer, and the states it passed through are audited + /// frozen by ScreenStatesUITests. + static let auditedOnceHeld: Set = ["wallWalk"] + override func setUp() { continueAfterFailure = false } @@ -299,12 +310,13 @@ final class FullFlowUITests: XCTestCase { /// Audits the screen twice, six seconds apart, and fails on the issues found both times /// (`AccessibilityAudit.run`). /// - /// The walk keeps taking photos while it is audited, so the photo count's number is often - /// mid-roll, and the result's text is still fading in when the screen appears. A single audit - /// reports those passing frames as contrast failures; a real contrast or clipping problem is - /// still there later. The walk also rebuilds parts of the screen while it is audited, so an - /// issue's element can be gone before it is read: that one is attached, not failed on. Every - /// screen state is also audited frozen by ScreenStatesUITests. + /// Replay screens can still be taking photos while they are audited, so the photo count's + /// number is often mid-roll, and the result's text is still fading in when the screen appears. + /// A single audit reports those passing frames as contrast failures; a real contrast or + /// clipping problem is still there later. The walk is audited once the autopilot holds it + /// (`auditedOnceHeld`). A screen that rebuilds parts of itself while it is audited can lose + /// an issue's element before it is read: that one is attached, not failed on. Every screen + /// state is also audited frozen by ScreenStatesUITests. @MainActor private func audit(_ app: XCUIApplication, screen: String) throws { // Six seconds: long enough for a system notification banner to leave. On CI one slid in @@ -366,6 +378,12 @@ final class FullFlowUITests: XCTestCase { // The walk replays the whole recording; everything else takes seconds. let timeout: TimeInterval = phase == "markFeatures" || phase == "result" ? 150 : 60 XCTAssertTrue(screen.waitForExistence(timeout: timeout), "screen.\(phase) never appeared") + if Self.auditedOnceHeld.contains(phase) { + let held = gate.appending(path: "\(phase).held") + let deadline = Date().addingTimeInterval(150) + while !FileManager.default.fileExists(atPath: held.path), Date() < deadline { Thread.sleep(forTimeInterval: 0.2) } + XCTAssertTrue(FileManager.default.fileExists(atPath: held.path), "the autopilot never finished with screen.\(phase)") + } // Let the entrance animation finish so the screenshot and audit see the settled screen. Thread.sleep(forTimeInterval: 1.0) let shot = XCTAttachment(screenshot: app.screenshot()) diff --git a/ios/README.md b/ios/README.md index 62f513d5e..f8323e881 100644 --- a/ios/README.md +++ b/ios/README.md @@ -19,7 +19,7 @@ Native iPhone app for the AR capture walk. The homeowner marks the electric mete | `-autopilotHold ` | How long the autopilot leaves each screen up (default 1.2 s). | | `-autopilotCantGetThere` | With `-autopilot`, ends the walk the way device run 1 did. It answers "Can't get there" to the walk's first request, the ground in front of the meter, before the replay has shown it. Then, instead of marking the ends, it plays again the frame where the walk went farthest on each side and answers "Can't get there" when the walk asks to go on that way, so the end lands where the phone stands. `FullFlowUITests.testCantGetThereEndsWhereThePhoneIs` runs it. | | `-autopilotSomethingThere` | With `-autopilot`, answers the first spot check "Something's there" instead of "It's clear". The scan stops claiming that area, is uploaded again, and later checks get "It's clear". The stretch taken out goes to the `-autopilotGate` folder as `spot-refusal.json` (`withdrawn_span_ft`, feet along the wall as in scene.json). `FullFlowUITests.testSomethingThereChecksTheWallAgain` runs it. | -| `-autopilotGate ` | Used by the UI tests. Before leaving a screen, the app waits for a file named after that phase in this folder. Once the result shows, the autopilot writes the scan's scene.json there. | +| `-autopilotGate ` | Used by the UI tests. Before leaving a screen, the app waits for a file named after that phase in this folder, and while it waits it leaves `.held` there, so a test can audit the screen once the autopilot has finished with it. Once the result shows, the autopilot writes the scan's scene.json there. | | `-simulateAppStore` | Runs as an App Store install would: no developer options, and practice meter stays off whatever the stored switch says. `PracticeMeterUITests` uses it. | | `-practiceMeter YES` or `NO` | Overrides the stored practice meter switch for one run (UserDefaults' argument domain). The full-flow UI tests pass `NO`, since the switch outlives a run in the Simulator. | | `-uiDemo` | Runs the screens on a scripted fake engine instead of the capture engine, for design work and for auditing states a replay can't reach. The arguments it takes are listed in `HouseScan/UI/Preview/UIDemo.swift`; for example `-uiDemoGroundQuestion` opens the feature review with the ground question showing, `-uiDemoPhase meterCloseUp -uiDemoMeterChoose` shows the meter number picker, and `-uiDemoPhase result -uiDemoCorner` shows the result model of a wall that turns a corner. | diff --git a/ios/Tools/make-synthetic-replay.swift b/ios/Tools/make-synthetic-replay.swift index bf6ed46f0..a3937ebe5 100644 --- a/ios/Tools/make-synthetic-replay.swift +++ b/ios/Tools/make-synthetic-replay.swift @@ -20,7 +20,8 @@ // electric meter x [-0.15, 0.15] y [1.3, 1.7]; gas meter x [-1.35, -1.05] y [0.3, 0.8]; // door x [-3.4, -2.5] y [0, 2.05]; window x [2.0, 3.0] y [0.9, 2.0]. // --lidar only: a bin x [1.2, 1.8] y [0, 1.1] z [0.5, 1.1]. -// The last three frames tilt up at the wall above the meter and the sky, for the tilt-up step. +// Frames 39 to 41 tilt up at the wall above the meter and the sky, for the tilt-up step; frames +// 42 to 61 walk along the wall tilted up the same way, for the upper rows (see f below). // Images are the unrotated landscape sensor image: camera +x is world down, so the ground appears // on the right of each JPEG. Output is deterministic; the only "noise" comes from integer hashes. @@ -338,6 +339,22 @@ for degrees in [22.0, 26.0, 30.0] { shots.append(Shot(camera: Camera(position: V3(0.2, 1.5, 2.6), forward: V3(0, sin(up), -cos(up))), time: time)) time += 0.5 } +// f) Appended after the frames above, which keep their numbers and bytes (k00001 to k00041): a +// walk along the wall 22 degrees up, x = 0.7, 1.2, ..., 5.2 and then back from -0.3 to -4.8, every +// 0.5 m. Its views show the wall from about 1.1 m to past the 7.5 ft top row from positions 0.5 m +// apart, so the upper rows get the second position the three views from one place above don't +// give; the replay's first 41 frames are the case without it. It starts 0.5 m from the tilt-up +// position and stays tilted up, so it is part of the recording's closing tilt-up run. +time += 0.5 +let upperXs = (0...9).map { 0.7 + 0.5 * Double($0) } + (0...9).map { -0.3 - 0.5 * Double($0) } +let upperUp = 22.0 * Double.pi / 180 +for (index, x) in upperXs.enumerated() { + // Back past the start, unfilmed, at a walking pace: 6.5 s for the 5.5 m, under + // auto-capture's 1.5 m/s between kept frames. + if index == 10 { time += 6.5 } + shots.append(Shot(camera: Camera(position: V3(x, 1.5, 2.6), forward: V3(0, sin(upperUp), -cos(upperUp))), time: time)) + time += 0.55 +} // MARK: - Output