250 lines
8.4 KiB
Bash
Executable File
250 lines
8.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
MACOS_DIR="$ROOT_DIR/apps/macos"
|
|
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/apc-overlay-offline.XXXXXX")"
|
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
|
|
(
|
|
cd "$MACOS_DIR"
|
|
swift build \
|
|
--product AgentPetCompanion \
|
|
-Xswiftc -strict-concurrency=complete \
|
|
-Xswiftc -warnings-as-errors >/dev/null
|
|
swift build \
|
|
--product AgentPetCompanionCoreValidation \
|
|
-Xswiftc -strict-concurrency=complete \
|
|
-Xswiftc -warnings-as-errors >/dev/null
|
|
)
|
|
|
|
BIN_DIR="$(cd "$MACOS_DIR" && swift build --show-bin-path)"
|
|
"$BIN_DIR/AgentPetCompanionCoreValidation" >/dev/null
|
|
(cd "$MACOS_DIR" && swift run AgentPetCompanionUIValidation)
|
|
CORE_OBJECT_DIR="$BIN_DIR/AgentPetCompanionCore.build"
|
|
CORE_OBJECTS=(
|
|
"$CORE_OBJECT_DIR/AppModels.swift.o"
|
|
"$CORE_OBJECT_DIR/FrameScheduler.swift.o"
|
|
)
|
|
LOCALIZATION_ACCESSOR="$BIN_DIR/AgentPetCompanion.build/DerivedSources/resource_bundle_accessor.swift"
|
|
|
|
swiftc \
|
|
-parse-as-library \
|
|
-enable-testing \
|
|
-strict-concurrency=complete \
|
|
-warnings-as-errors \
|
|
-emit-module \
|
|
-emit-library \
|
|
-module-name OverlayGeometryValidation \
|
|
-I "$BIN_DIR/Modules" \
|
|
"$MACOS_DIR/Sources/AgentPetCompanion/App/Localization.swift" \
|
|
"$MACOS_DIR/Sources/AgentPetCompanion/App/PackagedResourceBundle.swift" \
|
|
"$LOCALIZATION_ACCESSOR" \
|
|
"$MACOS_DIR/Sources/AgentPetCompanion/Overlay/OverlayGeometry.swift" \
|
|
"$MACOS_DIR/Sources/AgentPetCompanion/Overlay/OverlayResizeAccessibility.swift" \
|
|
"${CORE_OBJECTS[@]}" \
|
|
-emit-module-path "$TMP_DIR/OverlayGeometryValidation.swiftmodule" \
|
|
-o "$TMP_DIR/libOverlayGeometryValidation.dylib"
|
|
|
|
swiftc \
|
|
-I "$TMP_DIR" \
|
|
-I "$BIN_DIR/Modules" \
|
|
-L "$TMP_DIR" \
|
|
-lOverlayGeometryValidation \
|
|
-o "$TMP_DIR/geometry-check" \
|
|
- <<'SWIFT'
|
|
@testable import OverlayGeometryValidation
|
|
import AppKit
|
|
import CoreGraphics
|
|
|
|
let displays = [
|
|
OverlayDisplayGeometry(
|
|
frame: CGRect(x: 0, y: 0, width: 1512, height: 982),
|
|
visibleFrame: CGRect(x: 0, y: 25, width: 1512, height: 934),
|
|
backingScaleFactor: 2
|
|
),
|
|
OverlayDisplayGeometry(
|
|
frame: CGRect(x: -1280, y: 0, width: 1280, height: 800),
|
|
visibleFrame: CGRect(x: -1280, y: 0, width: 1280, height: 775),
|
|
backingScaleFactor: 1
|
|
)
|
|
]
|
|
|
|
for display in displays {
|
|
let frame = display.visibleFrame
|
|
for scale: CGFloat in [0.10, 0.72, 1.8] {
|
|
for point in [
|
|
CGPoint(x: frame.minX, y: frame.minY),
|
|
CGPoint(x: frame.maxX, y: frame.minY),
|
|
CGPoint(x: frame.minX, y: frame.maxY),
|
|
CGPoint(x: frame.maxX, y: frame.maxY)
|
|
] {
|
|
let center = OverlayGeometry.clampedPetScreenCenter(
|
|
point,
|
|
scale: scale,
|
|
visibleFrame: frame,
|
|
clickMenuEnabled: true
|
|
)
|
|
// Drag clamping intentionally follows the visible pet pixels and
|
|
// reachable controls, not the larger transparent render panel.
|
|
// The latter may extend beyond the movement frame for shadows.
|
|
let bounds = OverlayGeometry.petMovementScreenBounds(
|
|
scale: scale,
|
|
petScreenCenter: center,
|
|
clickMenuEnabled: true,
|
|
includeResize: true
|
|
)
|
|
precondition(frame.insetBy(dx: -0.5, dy: -0.5).contains(bounds))
|
|
}
|
|
}
|
|
}
|
|
|
|
let target = OverlayGeometry.dragTargetDisplay(
|
|
pointer: CGPoint(x: -640, y: 400),
|
|
proposedPetCenter: CGPoint(x: 20, y: 400),
|
|
displays: displays,
|
|
fallback: displays[0]
|
|
)
|
|
precondition(target == displays[1])
|
|
precondition(OverlayGeometry.clampedScale(0.01) == OverlayGeometry.minimumScale)
|
|
precondition(OverlayGeometry.clampedScale(9) == OverlayGeometry.maximumScale)
|
|
precondition(OverlayGeometry.resolvedInitialScale(persistedScale: 0.12, hasPersistedPosition: false) == 0.72)
|
|
precondition(OverlayGeometry.resolvedInitialScale(persistedScale: 0.12, hasPersistedPosition: true) == 0.12)
|
|
|
|
let resizeValidation = await MainActor.run {
|
|
let view = OverlayResizeAccessibilityView(frame: CGRect(x: 0, y: 0, width: 38, height: 38))
|
|
var steps: [CGFloat] = []
|
|
view.scale = 0.72
|
|
view.onScaleStep = { steps.append($0) }
|
|
precondition(view.acceptsFirstResponder)
|
|
precondition(view.accessibilityRole() == .slider)
|
|
precondition(view.accessibilityPerformIncrement())
|
|
precondition(view.accessibilityPerformDecrement())
|
|
let value = (view.accessibilityValue() as? NSNumber)?.doubleValue
|
|
return (steps, value)
|
|
}
|
|
precondition(resizeValidation.0 == [0.05, -0.05])
|
|
precondition(resizeValidation.1 == 0.72)
|
|
print("Overlay geometry offline validation ok")
|
|
SWIFT
|
|
DYLD_LIBRARY_PATH="$TMP_DIR" "$TMP_DIR/geometry-check"
|
|
|
|
swiftc \
|
|
-parse-as-library \
|
|
-enable-testing \
|
|
-strict-concurrency=complete \
|
|
-warnings-as-errors \
|
|
-emit-library \
|
|
-emit-module \
|
|
-module-name PetFramePipelineValidation \
|
|
-I "$BIN_DIR/Modules" \
|
|
"$MACOS_DIR/Sources/AgentPetCompanion/App/Localization.swift" \
|
|
"$MACOS_DIR/Sources/AgentPetCompanion/App/PackagedResourceBundle.swift" \
|
|
"$LOCALIZATION_ACCESSOR" \
|
|
"$MACOS_DIR/Sources/AgentPetCompanion/Overlay/OverlayGeometry.swift" \
|
|
"$MACOS_DIR/Sources/AgentPetCompanion/Overlay/PetFramePipeline.swift" \
|
|
"$MACOS_DIR/Sources/AgentPetCompanion/App/PetAssetLocator.swift" \
|
|
"${CORE_OBJECTS[@]}" \
|
|
-emit-module-path "$TMP_DIR/PetFramePipelineValidation.swiftmodule" \
|
|
-o "$TMP_DIR/libPetFramePipelineValidation.dylib"
|
|
|
|
swiftc \
|
|
-I "$TMP_DIR" \
|
|
-I "$BIN_DIR/Modules" \
|
|
-L "$TMP_DIR" \
|
|
-lPetFramePipelineValidation \
|
|
-o "$TMP_DIR/pipeline-check" \
|
|
- <<'SWIFT'
|
|
@testable import PetFramePipelineValidation
|
|
import AgentPetCompanionCore
|
|
import CoreImage
|
|
import Foundation
|
|
|
|
final class DecodeProbe: @unchecked Sendable {
|
|
private let lock = NSLock()
|
|
private var count = 0
|
|
private var decodedOnMain = false
|
|
|
|
func decode(_ url: URL) -> PetDecodedFrame {
|
|
lock.lock()
|
|
count += 1
|
|
decodedOnMain = decodedOnMain || Thread.isMainThread
|
|
lock.unlock()
|
|
return PetDecodedFrame(
|
|
image: CIImage(color: .white).cropped(to: CGRect(x: 0, y: 0, width: 2, height: 2)),
|
|
pixelWidth: 2,
|
|
pixelHeight: 2
|
|
)
|
|
}
|
|
|
|
func snapshot() -> (count: Int, decodedOnMain: Bool) {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
return (count, decodedOnMain)
|
|
}
|
|
}
|
|
|
|
let urls = (0..<20).map { URL(fileURLWithPath: "/virtual/\($0).png") }
|
|
let probe = DecodeProbe()
|
|
let pipeline = PetFramePipeline(
|
|
memoryBudgetBytes: 32,
|
|
originalWindowSize: 7,
|
|
catalog: { _, _ in PetFrameAssetCatalog(frameURLs: urls, coverURL: nil) },
|
|
decoder: { probe.decode($0) }
|
|
)
|
|
let quality = QualityLevel.original
|
|
let pet = PetSummary(
|
|
id: "pet_test",
|
|
name: "Test",
|
|
style: "pixel",
|
|
quality: quality,
|
|
renderSize: quality.renderSize,
|
|
petpackPath: "/virtual/test.petpack",
|
|
coverPath: "",
|
|
active: true,
|
|
createdAt: "2026-07-10T00:00:00Z"
|
|
)
|
|
let prepared = try await pipeline.prepare(PetFrameLoadRequest(
|
|
pet: pet,
|
|
stateName: "tool",
|
|
fps: 12,
|
|
loops: true
|
|
))
|
|
precondition(prepared.sourceKind == .ring)
|
|
precondition(prepared.readyFrameCount <= 7)
|
|
let readsAfterPrepare = probe.snapshot().count
|
|
for index in 0..<100 {
|
|
_ = prepared.readyFrame(at: index % max(1, prepared.frameCount))
|
|
}
|
|
precondition(probe.snapshot().count == readsAfterPrepare)
|
|
|
|
let advanced = try await pipeline.prefetch(prepared, around: 12)
|
|
precondition(advanced.readyFrame(at: 12) != nil)
|
|
precondition(advanced.readyFrameCount <= 7)
|
|
let metrics = await pipeline.cacheMetrics()
|
|
precondition(metrics.byteCount <= 32)
|
|
precondition(metrics.maximumConcurrentDecodes == 1)
|
|
precondition(!probe.snapshot().decodedOnMain)
|
|
print("Pet frame pipeline offline validation ok")
|
|
SWIFT
|
|
DYLD_LIBRARY_PATH="$TMP_DIR" "$TMP_DIR/pipeline-check"
|
|
|
|
if rg -n 'Timer\.publish|pointerTimer|1\.0 / (24|30)\.0' \
|
|
"$MACOS_DIR/Sources/AgentPetCompanion/Overlay/OverlayRootView.swift" \
|
|
"$MACOS_DIR/Sources/AgentPetCompanion/Overlay/PetOverlayController.swift"; then
|
|
echo "overlay offline validation failed: high-frequency pointer polling remains" >&2
|
|
exit 1
|
|
fi
|
|
|
|
DRAW_BODY="$(awk '
|
|
/func draw\(in view: MTKView\)/ { capture=1 }
|
|
capture { print }
|
|
capture && /@MainActor/ { exit }
|
|
' "$MACOS_DIR/Sources/AgentPetCompanion/Overlay/PetFramePipeline.swift")"
|
|
if rg -n 'CGImageSource|contentsOfDirectory|Data\(contentsOf|FileHandle' <<<"$DRAW_BODY"; then
|
|
echo "overlay offline validation failed: draw path performs file I/O or decode" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Overlay offline validation ok"
|