From 031dc50b99cae4b072bb87c7606a081d06f73c19 Mon Sep 17 00:00:00 2001 From: Mikei386 <44135113+Mikei386@users.noreply.github.com> Date: Sun, 13 Sep 2026 10:33:13 +0200 Subject: [PATCH] Validate exact Vision boundary values and real-project regression --- Resources/Info.plist | 4 +-- Sources/GrowthLapse/VideoProcessor.swift | 7 +++-- Tests/Integration/RenderIntegration.swift | 35 +++++++++++++++++++++-- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Resources/Info.plist b/Resources/Info.plist index 70e6261..36fa050 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -19,9 +19,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.4.1 + 0.4.2 CFBundleVersion - 6 + 7 LSMinimumSystemVersion 13.0 NSHighResolutionCapable diff --git a/Sources/GrowthLapse/VideoProcessor.swift b/Sources/GrowthLapse/VideoProcessor.swift index c2f8c9d..1751d57 100644 --- a/Sources/GrowthLapse/VideoProcessor.swift +++ b/Sources/GrowthLapse/VideoProcessor.swift @@ -924,9 +924,10 @@ final class VideoProcessor: ObservableObject { if let pose = sample.pose { guard [pose.x, pose.y, pose.height, pose.yaw, pose.roll].allSatisfy({ $0.isFinite }), (0...1).contains(pose.x), (0...1).contains(pose.y), (0...1).contains(pose.height), - // Vision angles originate as Float; Float.pi is slightly larger than Double.pi. - abs(pose.yaw) <= Double(Float.pi), abs(pose.roll) <= Double(Float.pi), - pose.pitch == nil || (pose.pitch!.isFinite && abs(pose.pitch!) <= Double(Float.pi)) else { return false } + // Vision may return either Float rounding adjacent to pi. Swift Float.pi + // is not necessarily the rounding used by Vision; allow 1e-6 radians. + abs(pose.yaw) <= (.pi + 1e-6), abs(pose.roll) <= (.pi + 1e-6), + pose.pitch == nil || (pose.pitch!.isFinite && abs(pose.pitch!) <= (.pi + 1e-6)) else { return false } } if let color = sample.color { guard color.luminance.isFinite, (0...1).contains(color.luminance), diff --git a/Tests/Integration/RenderIntegration.swift b/Tests/Integration/RenderIntegration.swift index bb70dce..6111d2a 100644 --- a/Tests/Integration/RenderIntegration.swift +++ b/Tests/Integration/RenderIntegration.swift @@ -29,6 +29,35 @@ struct RenderIntegration { @MainActor static func main() async throws { + if let path = ProcessInfo.processInfo.environment["GROWTHLAPSE_VALIDATE_PROJECT"] { + let validator = VideoProcessor() + guard let project = validator.loadProject(from: URL(fileURLWithPath: path)) else { + throw TestFailure.failed("Real project validation failed: \(validator.state)") + } + print("PASS real project load: \(project.clips.count) clips") + if ProcessInfo.processInfo.environment["GROWTHLAPSE_TEST_REAL_DRAFT"] == "1" { + let original = try Data(contentsOf: URL(fileURLWithPath: path)) + var subset = project + subset.clips = project.clips.filter { $0.displayName.hasPrefix("063") || $0.displayName.hasPrefix("066") } + try require(subset.clips.count == 2, "Expected two selected regression clips") + validator.project = subset + validator.startDraft(settings: SettingsStore.load()) + let deadline = Date().addingTimeInterval(180) + while validator.state.isRunning && Date() < deadline { + try await Task.sleep(nanoseconds: 100_000_000) + } + guard let draft = validator.draftOutput else { + validator.cancel() + throw TestFailure.failed("Real draft failed: \(validator.state)\n\(validator.logs.suffix(25).joined(separator: "\n"))") + } + try require(FileManager.default.fileExists(atPath: draft.path), "Real draft missing") + let after = try Data(contentsOf: URL(fileURLWithPath: path)) + try require(original == after, "Real draft changed source project") + print("PASS actual affected clips rendered as isolated draft: \(draft.path)") + validator.discardDraft() + return + } + } try ContinuityChecks.run() let defaults = RenderSettings() try require(defaults.inputFolder == nil && defaults.outputFile == nil @@ -198,7 +227,7 @@ struct RenderIntegration { // Vision's single-precision ±pi must survive project validation unchanged. var angleProject = second - let visionPi = Double(Float.pi) + let visionPi = 3.1415927410125732 // Exact value observed in Vision JSON; not Swift Float.pi angleProject.clips[0].visualAnalysis = ClipVisualAnalysis(sourceSignature: "angle-test", samples: [ VisualSample(time: 0, faceCount: 1, pose: FacePose(x: 0.5, y: 0.5, height: 0.3, yaw: visionPi, @@ -210,10 +239,10 @@ struct RenderIntegration { let accepted = angleLoader.loadProject(from: angleURL) try require(accepted?.clips[0].visualAnalysis == angleProject.clips[0].visualAnalysis, "Valid Vision Float.pi angles rejected or altered") - angleProject.clips[0].visualAnalysis!.samples[0].pose!.roll = 3.15 + angleProject.clips[0].visualAnalysis!.samples[0].pose!.roll = Double.pi + 0.00001 try JSONEncoder().encode(angleProject).write(to: angleURL) try require(angleLoader.loadProject(from: angleURL) == nil, "Out-of-range angle accepted") - print("PASS Vision Float.pi project round-trip and invalid-angle rejection") + print("PASS Vision exact boundary project round-trip and invalid-angle rejection") // Exercise failure after normalization, while FFmpeg is producing the final video. let wrapper = root.appendingPathComponent("ffmpeg-fail")