peachsim 0.1.1640 → 0.1.1641
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/cli/app-stubs.ts +38 -13
- package/cli/cloud-session.ts +16 -0
- package/cli/commands/assert.ts +17 -4
- package/cli/commands/box.ts +33 -19
- package/cli/commands/doctor.ts +24 -15
- package/cli/commands/inspect/actions.ts +3 -2
- package/cli/commands/inspect/core.ts +0 -8
- package/cli/commands/inspect.ts +0 -14
- package/cli/commands/platform.ts +2 -1
- package/cli/commands/record.ts +64 -28
- package/cli/commands/swift.ts +10 -2
- package/cli/commands/three-mode.ts +4 -38
- package/cli/commands/upload.ts +8 -8
- package/cli/flow-live-status.ts +0 -2
- package/cli/main.ts +1 -1
- package/cli/ws-bridge.ts +0 -6
- package/detox/attempt-events.cjs +41 -5
- package/detox/index.ts +155 -109
- package/detox/jest-environment.cjs +5 -1
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +10 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/bundle-channel.cjs +2059 -1368
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/config.mjs +1 -1
- package/dist-lib/detox/index.cjs +572 -125
- package/dist-lib/detox/jest-environment.cjs +5 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/expo-swift-bundle.cjs +1837 -1164
- package/dist-lib/expo-swift-metadata.cjs +1 -1
- package/dist-lib/expo-swift-metadata.mjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +2290 -1616
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +44 -17
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/kotlin-host.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +33 -13
- package/dist-lib/native-app-identity-project.cjs +7 -4
- package/dist-lib/native-app-identity-project.mjs +7 -4
- package/dist-lib/native-app-identity.cjs +1 -1
- package/dist-lib/native-app-identity.mjs +1 -1
- package/dist-lib/native-firebase-metadata.cjs +1 -1
- package/dist-lib/native-firebase-metadata.mjs +1 -1
- package/dist-lib/plist.cjs +1 -1
- package/dist-lib/plist.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +4 -3
- package/dist-lib/sdk.mjs +4 -3
- package/dist-lib/skills.cjs +2823 -2109
- package/dist-lib/swift-compile.cjs +370 -239
- package/dist-lib/swift-compile.mjs +368 -230
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +8 -3
- package/dist-lib/swift-submit.mjs +8 -3
- package/dist-lib/swift-toolchain.cjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +1757 -1108
- package/dist-lib/vite.cjs +1 -1
- package/dist-lib/worklet-executable.cjs +1 -1
- package/dist-lib/worklet-executable.mjs +1 -1
- package/package.json +1 -1
- package/src/backend-origin.ts +8 -0
- package/src/react-native-bridge-modules.ts +89 -76
- package/src/swift-compile.ts +134 -107
- package/src/swift-distribution.lock.json +9 -8
- package/src/swift-host.ts +6 -4
- package/src/swift-objc-compile.ts +385 -193
- package/src/swift-objc-metadata.ts +204 -0
- package/src/swift-objc-sdk.ts +195 -2
- package/src/swift-package-manifest.ts +6 -3
- package/src/swift-root-lock.ts +153 -0
- package/src/swift-submit.ts +15 -2
- package/src/swiftContract.ts +12 -0
- package/src/xcode-project.ts +10 -3
package/dist-lib/detox/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1641 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1524,6 +1524,26 @@ var require_motion_change = __commonJS({
|
|
|
1524
1524
|
}
|
|
1525
1525
|
});
|
|
1526
1526
|
|
|
1527
|
+
// ../peach-engine/src/capture/frame-offset.ts
|
|
1528
|
+
var frame_offset_exports = {};
|
|
1529
|
+
__export(frame_offset_exports, {
|
|
1530
|
+
frameIndexForOffset: () => frameIndexForOffset
|
|
1531
|
+
});
|
|
1532
|
+
function frameIndexForOffset(offsetMs, frameIntervalMs) {
|
|
1533
|
+
if (!Number.isFinite(offsetMs) || offsetMs < 0)
|
|
1534
|
+
throw new Error(`capture: invalid stage offset ${offsetMs}`);
|
|
1535
|
+
if (!Number.isFinite(frameIntervalMs) || frameIntervalMs <= 0)
|
|
1536
|
+
throw new Error(`capture: invalid frame interval ${frameIntervalMs}`);
|
|
1537
|
+
const quotient = offsetMs / frameIntervalMs;
|
|
1538
|
+
const nearest = Math.round(quotient);
|
|
1539
|
+
return Math.abs(quotient - nearest) <= Number.EPSILON * Math.abs(quotient) ? nearest : Math.ceil(quotient);
|
|
1540
|
+
}
|
|
1541
|
+
var init_frame_offset = __esm({
|
|
1542
|
+
"../peach-engine/src/capture/frame-offset.ts"() {
|
|
1543
|
+
"use strict";
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1546
|
+
|
|
1527
1547
|
// ../../scripts/lib/remote-build-delivery.ts
|
|
1528
1548
|
var remote_build_delivery_exports = {};
|
|
1529
1549
|
__export(remote_build_delivery_exports, {
|
|
@@ -1655,6 +1675,7 @@ var require_native_frame_receipt = __commonJS({
|
|
|
1655
1675
|
"../peach-conformance/e2e/utils/native-frame-receipt.cjs"(exports2, module2) {
|
|
1656
1676
|
"use strict";
|
|
1657
1677
|
var { createHash } = require("node:crypto");
|
|
1678
|
+
var { frameIndexForOffset: frameIndexForOffset2 } = (init_frame_offset(), __toCommonJS(frame_offset_exports));
|
|
1658
1679
|
function nativeVideoFrameReceipt(recordingSha256, sourceFrameIndex, ptsSeconds, inputClock) {
|
|
1659
1680
|
if (!/^[a-f0-9]{64}$/.test(recordingSha256)) throw new Error("invalid recording hash");
|
|
1660
1681
|
if (!Number.isSafeInteger(sourceFrameIndex) || sourceFrameIndex < 0)
|
|
@@ -1735,25 +1756,29 @@ var require_native_frame_receipt = __commonJS({
|
|
|
1735
1756
|
if (output !== bytes.length) throw new Error("incomplete native framebuffer");
|
|
1736
1757
|
return bytes;
|
|
1737
1758
|
}
|
|
1738
|
-
function
|
|
1759
|
+
function nativeFramebufferPixels(receipt, bytes) {
|
|
1739
1760
|
requireNativeRawFrame(receipt, bytes);
|
|
1740
|
-
const { PNG } = require("pngjs");
|
|
1741
|
-
const fs3 = require("node:fs");
|
|
1742
1761
|
const { width, height, stride } = receipt.rawFramebuffer;
|
|
1743
|
-
|
|
1744
|
-
throw new Error("native framebuffer and screenshot dimensions differ");
|
|
1745
|
-
const png = new PNG({ width, height });
|
|
1762
|
+
const data = Buffer.allocUnsafe(width * height * 4);
|
|
1746
1763
|
for (let y = 0; y < height; y++) {
|
|
1747
1764
|
for (let x = 0; x < width; x++) {
|
|
1748
1765
|
const source = y * stride + x * 4;
|
|
1749
1766
|
const destination = (y * width + x) * 4;
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1767
|
+
data[destination] = bytes[source + 2];
|
|
1768
|
+
data[destination + 1] = bytes[source + 1];
|
|
1769
|
+
data[destination + 2] = bytes[source];
|
|
1770
|
+
data[destination + 3] = bytes[source + 3];
|
|
1754
1771
|
}
|
|
1755
1772
|
}
|
|
1756
|
-
|
|
1773
|
+
return { width, height, data };
|
|
1774
|
+
}
|
|
1775
|
+
function writeNativeFramebufferPng(receipt, bytes, outputPath, expectedSize) {
|
|
1776
|
+
const { PNG } = require("pngjs");
|
|
1777
|
+
const fs3 = require("node:fs");
|
|
1778
|
+
const pixels = nativeFramebufferPixels(receipt, bytes);
|
|
1779
|
+
if (pixels.width !== expectedSize.width || pixels.height !== expectedSize.height)
|
|
1780
|
+
throw new Error("native framebuffer and screenshot dimensions differ");
|
|
1781
|
+
fs3.writeFileSync(outputPath, PNG.sync.write(pixels));
|
|
1757
1782
|
return receipt;
|
|
1758
1783
|
}
|
|
1759
1784
|
module2.exports = {
|
|
@@ -1761,6 +1786,7 @@ var require_native_frame_receipt = __commonJS({
|
|
|
1761
1786
|
requireNativeProducerFrame,
|
|
1762
1787
|
requireNativeRawFrame,
|
|
1763
1788
|
decodeNativeFramebuffer,
|
|
1789
|
+
nativeFramebufferPixels,
|
|
1764
1790
|
writeNativeFramebufferPng
|
|
1765
1791
|
};
|
|
1766
1792
|
async function buildNativeFrameConsumer({ root, cacheRoot, waitForAdmission = false }) {
|
|
@@ -1896,7 +1922,10 @@ var require_native_frame_receipt = __commonJS({
|
|
|
1896
1922
|
ticks.push({
|
|
1897
1923
|
targetTimestampSeconds: row.targetTimestampSeconds,
|
|
1898
1924
|
timestampSeconds: row.timestampSeconds,
|
|
1899
|
-
durationSeconds: row.durationSeconds
|
|
1925
|
+
durationSeconds: row.durationSeconds,
|
|
1926
|
+
clockBridge: row.clockBridge,
|
|
1927
|
+
callbackThread: row.callbackThread,
|
|
1928
|
+
callbackWasMainThread: row.callbackWasMainThread
|
|
1900
1929
|
});
|
|
1901
1930
|
}
|
|
1902
1931
|
if (ticks.length < 2)
|
|
@@ -1910,7 +1939,7 @@ var require_native_frame_receipt = __commonJS({
|
|
|
1910
1939
|
};
|
|
1911
1940
|
}
|
|
1912
1941
|
module2.exports.nativeUIFrameClockReceipt = nativeUIFrameClockReceipt;
|
|
1913
|
-
function pairedNativeWorkletFrameClock(root, lifecycle) {
|
|
1942
|
+
function pairedNativeWorkletFrameClock(root, lifecycle, seed = "ui-frame-after-delivered-touch-up") {
|
|
1914
1943
|
const { boundText } = require_attempt_events();
|
|
1915
1944
|
if (!lifecycle || typeof lifecycle !== "object" || Array.isArray(lifecycle))
|
|
1916
1945
|
throw new Error("paired worklets clock needs a retained native lifecycle");
|
|
@@ -1940,6 +1969,42 @@ var require_native_frame_receipt = __commonJS({
|
|
|
1940
1969
|
)
|
|
1941
1970
|
)
|
|
1942
1971
|
);
|
|
1972
|
+
if (seed === "delivered-touch-drag") {
|
|
1973
|
+
const down = input.events[0];
|
|
1974
|
+
const up2 = input.events.at(-1);
|
|
1975
|
+
if (input.status !== "witnessed" || down?.phase !== 0 || up2?.phase !== 3 || !input.events.slice(1, -1).every((event) => event.phase === 1) || input.events.length < 3 || input.events.some(
|
|
1976
|
+
(event, index) => index > 0 && (event.deliveredAtSeconds < input.events[index - 1].deliveredAtSeconds || event.touchTimestampSeconds < input.events[index - 1].touchTimestampSeconds)
|
|
1977
|
+
))
|
|
1978
|
+
throw new Error(
|
|
1979
|
+
"paired drag needs one ordered delivered UIKit down/moves/up sequence"
|
|
1980
|
+
);
|
|
1981
|
+
if (!clock.ticks.some((tick2) => tick2.timestampSeconds < down.deliveredAtSeconds) || !clock.ticks.some((tick2) => tick2.timestampSeconds > up2.deliveredAtSeconds))
|
|
1982
|
+
throw new Error("native UI clock does not bracket the delivered drag");
|
|
1983
|
+
return {
|
|
1984
|
+
provenance: {
|
|
1985
|
+
sourceReceipt: receipt.sourceReceipt,
|
|
1986
|
+
sourceReceiptSha256: receipt.sourceReceiptSha256,
|
|
1987
|
+
inputReceipt: inputReceipt.sourceReceipt,
|
|
1988
|
+
inputReceiptSha256: inputReceipt.sourceReceiptSha256,
|
|
1989
|
+
alignment: "delivered-touch-drag",
|
|
1990
|
+
stageOffsetBasis: "declared-from-delivered-down",
|
|
1991
|
+
deliveredTouchDownSeconds: down.deliveredAtSeconds
|
|
1992
|
+
},
|
|
1993
|
+
hold: {
|
|
1994
|
+
x: down.x,
|
|
1995
|
+
y: down.y,
|
|
1996
|
+
release: { x: up2.x, y: up2.y },
|
|
1997
|
+
durationMs: (up2.deliveredAtSeconds - down.deliveredAtSeconds) * 1e3,
|
|
1998
|
+
eventDurationMs: (up2.touchTimestampSeconds - down.touchTimestampSeconds) * 1e3,
|
|
1999
|
+
moves: input.events.slice(1, -1).map((event) => ({
|
|
2000
|
+
x: event.x,
|
|
2001
|
+
y: event.y,
|
|
2002
|
+
afterMs: (event.deliveredAtSeconds - down.deliveredAtSeconds) * 1e3,
|
|
2003
|
+
eventAfterMs: (event.touchTimestampSeconds - down.touchTimestampSeconds) * 1e3
|
|
2004
|
+
}))
|
|
2005
|
+
}
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
1943
2008
|
const up = input.events.find((event) => event.phase === 3);
|
|
1944
2009
|
if (input.status !== "witnessed" || !up)
|
|
1945
2010
|
throw new Error("paired worklets clock needs a delivered UIKit touch-up witness");
|
|
@@ -1965,6 +2030,323 @@ var require_native_frame_receipt = __commonJS({
|
|
|
1965
2030
|
};
|
|
1966
2031
|
}
|
|
1967
2032
|
module2.exports.pairedNativeWorkletFrameClock = pairedNativeWorkletFrameClock;
|
|
2033
|
+
function nativeGradingPlan(sourceRoot, declaration) {
|
|
2034
|
+
const fs3 = require("node:fs"), path5 = require("node:path");
|
|
2035
|
+
const catalogs = [
|
|
2036
|
+
["packages/peach-conformance/src/cases/catalog.cjs", "CONFORMANCE_CASE_CATALOG"],
|
|
2037
|
+
[
|
|
2038
|
+
"packages/peach-library-conformance/src/cases/catalog.cjs",
|
|
2039
|
+
"LIBRARY_CONFORMANCE_CASE_CATALOG"
|
|
2040
|
+
]
|
|
2041
|
+
];
|
|
2042
|
+
let catalog, catalogPath;
|
|
2043
|
+
for (const [relative, key] of catalogs) {
|
|
2044
|
+
const entries = require(path5.join(sourceRoot, relative))[key];
|
|
2045
|
+
const entry = entries.find((item) => item.name === declaration.caseName);
|
|
2046
|
+
const checkpoint = entry?.checkpoints?.find(
|
|
2047
|
+
(item) => item.id === declaration.checkpoint
|
|
2048
|
+
);
|
|
2049
|
+
if (checkpoint) {
|
|
2050
|
+
catalog = checkpoint;
|
|
2051
|
+
catalogPath = relative;
|
|
2052
|
+
break;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
if (!catalog || !Array.isArray(catalog.stages) || JSON.stringify(catalog.stages) !== JSON.stringify(declaration.stages.map((stage) => stage.label)))
|
|
2056
|
+
throw new Error(
|
|
2057
|
+
"native sampled grading plan must include every catalog stage in order"
|
|
2058
|
+
);
|
|
2059
|
+
if (JSON.stringify(catalog.motionCadence ?? null) !== JSON.stringify(declaration.cadence))
|
|
2060
|
+
throw new Error("native sampled grading plan changed the catalog cadence");
|
|
2061
|
+
if (declaration.anchorPlan?.stages?.length !== declaration.stages.length || declaration.stages.some(
|
|
2062
|
+
(stage, index) => declaration.anchorPlan.stages[index].anchorId !== (stage.anchor?.id ?? "transition")
|
|
2063
|
+
))
|
|
2064
|
+
throw new Error("native sampled grading plan changed a declared stage anchor");
|
|
2065
|
+
if (declaration.stages.some(
|
|
2066
|
+
(stage) => !Number.isFinite(stage.offsetMs) || stage.offsetMs < 0
|
|
2067
|
+
))
|
|
2068
|
+
throw new Error("native sampled grading plan has invalid stage offsets");
|
|
2069
|
+
return {
|
|
2070
|
+
...declaration,
|
|
2071
|
+
contract: "continuous-catalog-stage-window-v1",
|
|
2072
|
+
catalogPath,
|
|
2073
|
+
catalogSha256: createHash("sha256").update(fs3.readFileSync(path5.join(sourceRoot, catalogPath))).digest("hex")
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
module2.exports.nativeGradingPlan = nativeGradingPlan;
|
|
2077
|
+
function tickValue(value, label = "native sampled recording") {
|
|
2078
|
+
if (typeof value !== "string" || !/^\d+$/.test(value))
|
|
2079
|
+
throw new Error(`${label} has invalid mach ticks`);
|
|
2080
|
+
return BigInt(value);
|
|
2081
|
+
}
|
|
2082
|
+
function nativeResolvedGradingWindow(plan, resolution, frames, markers) {
|
|
2083
|
+
const markerStart = tickValue(markers?.machStart, "native grade-start marker");
|
|
2084
|
+
const markerEnd = tickValue(markers?.machEnd, "native grade-end marker");
|
|
2085
|
+
if (!resolution || !Array.isArray(resolution.stages) || resolution.stages.length !== plan.stages.length || resolution.stages.some(
|
|
2086
|
+
(stage, index) => stage.label !== plan.stages[index].label || stage.offsetMs !== plan.stages[index].offsetMs || stage.anchorId !== plan.anchorPlan.stages[index].anchorId || !Number.isFinite(stage.atSec) || stage.atSec < 0
|
|
2087
|
+
) || !Number.isFinite(resolution.triggerInVideoSec) || resolution.triggerInVideoSec < 0)
|
|
2088
|
+
throw new Error("native sampled window resolution dropped or changed declared stages");
|
|
2089
|
+
for (const stage of resolution.stages) {
|
|
2090
|
+
const offset = stage.touchOffsetMs ?? stage.offsetMs;
|
|
2091
|
+
const expected = stage.basisSec + frameIndexForOffset2(offset, 1e3 / 60) * (1e3 / 60) / 1e3;
|
|
2092
|
+
if (!Number.isFinite(stage.basisSec) || stage.basisSec < 0 || !Number.isFinite(offset) || offset < 0 || !stage.initialBeforeTrigger && stage.atSec !== expected || stage.anchorId !== "transition" && offset !== stage.offsetMs)
|
|
2093
|
+
throw new Error("native sampled window changed the declared stage timing");
|
|
2094
|
+
}
|
|
2095
|
+
const origin = frames[0]?.consumerArrival;
|
|
2096
|
+
if (!origin || !Number.isSafeInteger(origin.timebaseNumer) || origin.timebaseNumer <= 0 || !Number.isSafeInteger(origin.timebaseDenom) || origin.timebaseDenom <= 0)
|
|
2097
|
+
throw new Error("native sampled window has no source clock");
|
|
2098
|
+
const atTicks = (seconds) => {
|
|
2099
|
+
const nanos = Math.ceil(seconds * 1e9);
|
|
2100
|
+
if (!Number.isSafeInteger(nanos) || nanos < 0)
|
|
2101
|
+
throw new Error("native sampled window exceeds exact source range");
|
|
2102
|
+
return BigInt(origin.machTicks) + (BigInt(nanos) * BigInt(origin.timebaseDenom) + BigInt(origin.timebaseNumer) - 1n) / BigInt(origin.timebaseNumer);
|
|
2103
|
+
};
|
|
2104
|
+
const endSeconds = Math.max(
|
|
2105
|
+
...resolution.stages.map((stage) => stage.atSec),
|
|
2106
|
+
...plan.cadence ? [resolution.triggerInVideoSec + 1.9] : []
|
|
2107
|
+
);
|
|
2108
|
+
const firstAnchor = atTicks(
|
|
2109
|
+
Math.min(
|
|
2110
|
+
resolution.triggerInVideoSec,
|
|
2111
|
+
...resolution.stages.map((stage) => Math.min(stage.atSec, stage.basisSec))
|
|
2112
|
+
)
|
|
2113
|
+
);
|
|
2114
|
+
const start = firstAnchor < markerStart ? firstAnchor : markerStart;
|
|
2115
|
+
const end = atTicks(endSeconds) + 1n;
|
|
2116
|
+
if (start < BigInt(origin.machTicks) || end > markerEnd || end <= start)
|
|
2117
|
+
throw new Error("native recorder does not bracket the complete catalog stage window");
|
|
2118
|
+
return { machStart: String(start), machEnd: String(end) };
|
|
2119
|
+
}
|
|
2120
|
+
module2.exports.nativeResolvedGradingWindow = nativeResolvedGradingWindow;
|
|
2121
|
+
function nativeRefreshBoundaryWindows(ticks, { startSeconds, endSeconds }) {
|
|
2122
|
+
if (!Number.isFinite(startSeconds) || !Number.isFinite(endSeconds) || !(endSeconds > startSeconds))
|
|
2123
|
+
throw new Error("native sampled grading window is empty");
|
|
2124
|
+
const intervals = [], gaps = [];
|
|
2125
|
+
for (const [refresh, tick2] of ticks.entries()) {
|
|
2126
|
+
const start = tick2.timestampSeconds, end = tick2.targetTimestampSeconds;
|
|
2127
|
+
if (!(end > start)) throw new Error("native UI clock has a reversed refresh interval");
|
|
2128
|
+
if (end > startSeconds && start < endSeconds) intervals.push({ refresh, start, end });
|
|
2129
|
+
const next = ticks[refresh + 1];
|
|
2130
|
+
if (!next) continue;
|
|
2131
|
+
const rounding = 8 * Number.EPSILON * Math.max(1, Math.abs(end), Math.abs(next.timestampSeconds));
|
|
2132
|
+
if (Math.abs(end - next.timestampSeconds) <= rounding) continue;
|
|
2133
|
+
const gapStart = Math.min(end, next.timestampSeconds);
|
|
2134
|
+
const gapEnd = Math.max(end, next.timestampSeconds);
|
|
2135
|
+
gaps.push({
|
|
2136
|
+
afterRefresh: refresh,
|
|
2137
|
+
start: gapStart,
|
|
2138
|
+
end: gapEnd,
|
|
2139
|
+
intersectsGrading: gapEnd > startSeconds && gapStart < endSeconds
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
2142
|
+
return {
|
|
2143
|
+
intervals,
|
|
2144
|
+
gaps,
|
|
2145
|
+
bracketed: intervals.length > 0 && intervals[0].start <= startSeconds && intervals.at(-1).end >= endSeconds
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
module2.exports.nativeRefreshBoundaryWindows = nativeRefreshBoundaryWindows;
|
|
2149
|
+
function nativeRefreshSamples({
|
|
2150
|
+
frames,
|
|
2151
|
+
reads: observations,
|
|
2152
|
+
clockBytes,
|
|
2153
|
+
finalFrames,
|
|
2154
|
+
finalSurfaceStatus = "settled",
|
|
2155
|
+
grading
|
|
2156
|
+
}) {
|
|
2157
|
+
const clock = nativeUIFrameClockReceipt(clockBytes);
|
|
2158
|
+
const movingFinalSurface = finalSurfaceStatus === "moving" && finalFrames.length === 0;
|
|
2159
|
+
if (!frames.length || !observations.length || !movingFinalSurface && finalFrames.length < 3)
|
|
2160
|
+
throw new Error("native sampled recording has no observed stable final surface");
|
|
2161
|
+
const origin = frames[0].consumerArrival;
|
|
2162
|
+
if (origin?.domain !== "host-mach-absolute-time" || !/^[0-9a-f-]{36}$/i.test(origin.bootSessionUUID ?? "") || !Number.isSafeInteger(origin.timebaseNumer) || origin.timebaseNumer <= 0 || !Number.isSafeInteger(origin.timebaseDenom) || origin.timebaseDenom <= 0)
|
|
2163
|
+
throw new Error("native sampled recording has no authenticated host clock");
|
|
2164
|
+
const secondsPerTick = origin.timebaseNumer / origin.timebaseDenom / 1e9;
|
|
2165
|
+
const bridgeOrigin = tickValue(clock.ticks[0].clockBridge?.machBefore);
|
|
2166
|
+
const elapsed = (value) => {
|
|
2167
|
+
const ticks = tickValue(value) - bridgeOrigin;
|
|
2168
|
+
if (ticks < -BigInt(Number.MAX_SAFE_INTEGER) || ticks > BigInt(Number.MAX_SAFE_INTEGER))
|
|
2169
|
+
throw new Error("native clock bridge exceeds exact mach range");
|
|
2170
|
+
return Number(ticks) * secondsPerTick;
|
|
2171
|
+
};
|
|
2172
|
+
let lowerOffset = -Infinity;
|
|
2173
|
+
let upperOffset = Infinity;
|
|
2174
|
+
for (const tick2 of clock.ticks) {
|
|
2175
|
+
if (tick2.callbackThread !== "dedicated-user-interactive-run-loop" || tick2.callbackWasMainThread !== false)
|
|
2176
|
+
throw new Error(
|
|
2177
|
+
"native UI clock callback did not run on its independent recorder thread"
|
|
2178
|
+
);
|
|
2179
|
+
const bridge = tick2.clockBridge;
|
|
2180
|
+
if (bridge?.bootSessionUUID !== origin.bootSessionUUID || bridge.timebaseNumer !== origin.timebaseNumer || bridge.timebaseDenom !== origin.timebaseDenom || !Number.isFinite(bridge.mediaSeconds))
|
|
2181
|
+
throw new Error("native clock bridge kernel identity or timebase mismatch");
|
|
2182
|
+
const before = elapsed(bridge.machBefore), after = elapsed(bridge.machAfter);
|
|
2183
|
+
if (after < before) throw new Error("native clock bridge has reversed bracket");
|
|
2184
|
+
const rounding = 8 * Number.EPSILON * Math.max(1, Math.abs(bridge.mediaSeconds));
|
|
2185
|
+
lowerOffset = Math.max(lowerOffset, bridge.mediaSeconds - after - rounding);
|
|
2186
|
+
upperOffset = Math.min(upperOffset, bridge.mediaSeconds - before + rounding);
|
|
2187
|
+
}
|
|
2188
|
+
if (lowerOffset > upperOffset) throw new Error("native clock bridge brackets disagree");
|
|
2189
|
+
const sourceFrames = new Map(
|
|
2190
|
+
frames.map((frame, index) => [frame.callbackIndex, { frame, index }])
|
|
2191
|
+
);
|
|
2192
|
+
if (sourceFrames.size !== frames.length)
|
|
2193
|
+
throw new Error("native source callbacks repeat");
|
|
2194
|
+
const preservedEnds = frames.map((frame) => tickValue(frame.readWindow?.machEnd));
|
|
2195
|
+
if (preservedEnds.some((end, index) => index > 0 && end <= preservedEnds[index - 1]))
|
|
2196
|
+
throw new Error("native preserved source copy windows are not increasing");
|
|
2197
|
+
let latestPreserved = -1;
|
|
2198
|
+
let previousEnd = -Infinity;
|
|
2199
|
+
const reads = observations.map((frame) => {
|
|
2200
|
+
const readEnd = tickValue(frame.readWindow?.machEnd);
|
|
2201
|
+
while (latestPreserved + 1 < frames.length && preservedEnds[latestPreserved + 1] <= readEnd)
|
|
2202
|
+
latestPreserved++;
|
|
2203
|
+
const reference = sourceFrames.get(frame.sourceCallbackIndex);
|
|
2204
|
+
if (!reference || reference.index !== latestPreserved || reference.frame.rawPath !== frame.rawPath || reference.frame.rawFramebuffer?.sha256 !== frame.rawFramebuffer?.sha256 || reference.frame.rawFramebuffer?.width !== frame.rawFramebuffer?.width || reference.frame.rawFramebuffer?.height !== frame.rawFramebuffer?.height || reference.frame.rawFramebuffer?.stride !== frame.rawFramebuffer?.stride || reference.frame.rawFramebuffer?.pixelFormat !== frame.rawFramebuffer?.pixelFormat || reference.frame.source?.screenId !== frame.source?.screenId || tickValue(reference.frame.readWindow?.machEnd) > tickValue(frame.readWindow?.machEnd))
|
|
2205
|
+
throw new Error("native sampled read has no matching immutable source pixels");
|
|
2206
|
+
const host = frame.consumerArrival;
|
|
2207
|
+
if (host?.bootSessionUUID !== origin.bootSessionUUID || host.domain !== origin.domain || host.timebaseNumer !== origin.timebaseNumer || host.timebaseDenom !== origin.timebaseDenom || !Number.isSafeInteger(frame.source?.seedBeforeRead) || frame.source.seedBeforeRead !== frame.source.seedAfterRead || frame.source.surfaceSeed !== frame.source.seedBeforeRead || frame.rawFramebuffer?.status !== "preserved" || !/^[a-f0-9]{64}$/.test(frame.rawFramebuffer.sha256))
|
|
2208
|
+
throw new Error("native sampled read is superseded or has a different clock");
|
|
2209
|
+
const start = elapsed(frame.readWindow?.machStart), end = elapsed(frame.readWindow?.machEnd);
|
|
2210
|
+
if (end < start || start < previousEnd || tickValue(frame.readWindow.machStart) < tickValue(host.machTicks))
|
|
2211
|
+
throw new Error("native sampled read has an invalid copy window");
|
|
2212
|
+
previousEnd = end;
|
|
2213
|
+
return {
|
|
2214
|
+
start: start + lowerOffset,
|
|
2215
|
+
end: end + upperOffset,
|
|
2216
|
+
sourceFrameIndex: reference.index,
|
|
2217
|
+
callbackIndex: frame.callbackIndex
|
|
2218
|
+
};
|
|
2219
|
+
});
|
|
2220
|
+
const gradedStart = elapsed(grading?.machStart) + lowerOffset;
|
|
2221
|
+
const gradedEnd = elapsed(grading?.machEnd) + upperOffset;
|
|
2222
|
+
if (!(gradedEnd > gradedStart))
|
|
2223
|
+
throw new Error("native sampled grading window is empty");
|
|
2224
|
+
const boundaries = nativeRefreshBoundaryWindows(clock.ticks, {
|
|
2225
|
+
startSeconds: gradedStart,
|
|
2226
|
+
endSeconds: gradedEnd
|
|
2227
|
+
});
|
|
2228
|
+
const uncovered = boundaries.gaps.find((gap) => gap.intersectsGrading);
|
|
2229
|
+
if (uncovered)
|
|
2230
|
+
throw new Error(
|
|
2231
|
+
`native UI clock has an uncovered refresh after ${uncovered.afterRefresh}`
|
|
2232
|
+
);
|
|
2233
|
+
if (!boundaries.bracketed)
|
|
2234
|
+
throw new Error("native UI clock does not bracket every graded refresh");
|
|
2235
|
+
const samples = [];
|
|
2236
|
+
let cursor = 0;
|
|
2237
|
+
for (const { refresh, start, end } of boundaries.intervals) {
|
|
2238
|
+
while (cursor < reads.length && reads[cursor].start < start) cursor++;
|
|
2239
|
+
if (cursor === reads.length || reads[cursor].end >= end)
|
|
2240
|
+
throw new Error(`native recording has no surface sampled during refresh ${refresh}`);
|
|
2241
|
+
samples.push({
|
|
2242
|
+
refresh,
|
|
2243
|
+
sourceFrameIndex: reads[cursor].sourceFrameIndex,
|
|
2244
|
+
observationCallbackIndex: reads[cursor].callbackIndex,
|
|
2245
|
+
evidence: `surface sampled during refresh ${refresh}`
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
const final = observations.at(-1);
|
|
2249
|
+
let previousFinalEnd = tickValue(final.readWindow.machEnd);
|
|
2250
|
+
for (const settled of finalFrames) {
|
|
2251
|
+
const host = settled.consumerArrival;
|
|
2252
|
+
const source = settled.source, raw = settled.rawFramebuffer;
|
|
2253
|
+
if (host?.bootSessionUUID !== origin.bootSessionUUID || host.domain !== origin.domain || host.timebaseNumer !== origin.timebaseNumer || host.timebaseDenom !== origin.timebaseDenom || !Number.isSafeInteger(source?.seedBeforeRead) || source.surfaceSeed !== source.seedBeforeRead || source.seedBeforeRead !== source.seedAfterRead || raw?.status !== "preserved" || raw.sha256 !== final.rawFramebuffer.sha256 || raw.width !== final.rawFramebuffer.width || raw.height !== final.rawFramebuffer.height || raw.stride !== final.rawFramebuffer.stride || raw.pixelFormat !== final.rawFramebuffer.pixelFormat || source.screenId !== final.source.screenId || tickValue(settled.readWindow?.machStart) <= previousFinalEnd || tickValue(settled.readWindow?.machEnd) < tickValue(settled.readWindow?.machStart))
|
|
2254
|
+
throw new Error(
|
|
2255
|
+
"native recording final surface is unobserved or mismatches stable post-settle reads"
|
|
2256
|
+
);
|
|
2257
|
+
previousFinalEnd = tickValue(settled.readWindow.machEnd);
|
|
2258
|
+
}
|
|
2259
|
+
return {
|
|
2260
|
+
contract: "per-refresh-sampled-surface-v1",
|
|
2261
|
+
samples,
|
|
2262
|
+
displayedWrite: "unknown: may differ from sampled write within refresh",
|
|
2263
|
+
limits: {
|
|
2264
|
+
clockDiscontinuities: boundaries.gaps,
|
|
2265
|
+
...movingFinalSurface ? { finalSurface: "moving" } : {}
|
|
2266
|
+
},
|
|
2267
|
+
bridge: { bootSessionUUID: origin.bootSessionUUID, lowerOffset, upperOffset }
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
module2.exports.nativeRefreshSamples = nativeRefreshSamples;
|
|
2271
|
+
function authenticateNativeRefreshSamples(root, sourceRoot, lifecycle) {
|
|
2272
|
+
const fs3 = require("node:fs");
|
|
2273
|
+
const path5 = require("node:path");
|
|
2274
|
+
const { boundText } = require_attempt_events();
|
|
2275
|
+
const native = lifecycle.nativeFramebuffer;
|
|
2276
|
+
if (!native || native.exitCode !== 0 || native.failure || native.sourceSha256 !== createHash("sha256").update(
|
|
2277
|
+
fs3.readFileSync(
|
|
2278
|
+
path5.join(sourceRoot, "scripts/debug/native-ios-frame-consumer.m")
|
|
2279
|
+
)
|
|
2280
|
+
).digest("hex"))
|
|
2281
|
+
throw new Error("native sampled recording has no source-matched consumer");
|
|
2282
|
+
const parse2 = (text) => text.split("\n").filter(Boolean).map(JSON.parse);
|
|
2283
|
+
const frames = parse2(
|
|
2284
|
+
boundText(root, native.receipt, native.receiptSha256, "native sampled reads")
|
|
2285
|
+
);
|
|
2286
|
+
const observations = parse2(
|
|
2287
|
+
boundText(root, native.reads, native.readsSha256, "native sampled copy windows")
|
|
2288
|
+
);
|
|
2289
|
+
const clock = lifecycle.uiFrameClock;
|
|
2290
|
+
const clockBytes = Buffer.from(
|
|
2291
|
+
boundText(
|
|
2292
|
+
root,
|
|
2293
|
+
clock?.sourceReceipt,
|
|
2294
|
+
clock?.sourceReceiptSha256,
|
|
2295
|
+
"native sampled UI clock and bridge"
|
|
2296
|
+
)
|
|
2297
|
+
);
|
|
2298
|
+
const finalSurfaceStatus = lifecycle.finalFrameProof?.status;
|
|
2299
|
+
if (!Array.isArray(native.finalReads) || native.finalReads.length < 3 && !(finalSurfaceStatus === "moving" && native.finalReads.length === 0))
|
|
2300
|
+
throw new Error("native sampled recording has no stable post-settle reads");
|
|
2301
|
+
if (new Set(native.finalReads.map((receipt) => receipt.path)).size !== native.finalReads.length)
|
|
2302
|
+
throw new Error("native sampled end-state receipts repeat an observation");
|
|
2303
|
+
const finalFrames = native.finalReads.map((receipt) => {
|
|
2304
|
+
const rows = parse2(
|
|
2305
|
+
boundText(root, receipt.path, receipt.sha256, "native sampled end state")
|
|
2306
|
+
);
|
|
2307
|
+
if (!rows.length) throw new Error("native sampled end state is unobserved");
|
|
2308
|
+
return rows.at(-1);
|
|
2309
|
+
});
|
|
2310
|
+
const plan = JSON.parse(
|
|
2311
|
+
boundText(
|
|
2312
|
+
root,
|
|
2313
|
+
native.gradingPlan,
|
|
2314
|
+
native.gradingPlanSha256,
|
|
2315
|
+
"native sampled pre-capture stage plan"
|
|
2316
|
+
)
|
|
2317
|
+
);
|
|
2318
|
+
const verifiedPlan = nativeGradingPlan(sourceRoot, plan);
|
|
2319
|
+
const { catalogSha256: capturedCatalogSha256, ...capturedCheckpoint } = plan;
|
|
2320
|
+
const { catalogSha256: currentCatalogSha256, ...verifiedCheckpoint } = verifiedPlan;
|
|
2321
|
+
if (!/^[a-f0-9]{64}$/.test(capturedCatalogSha256 ?? "") || !/^[a-f0-9]{64}$/.test(currentCatalogSha256) || JSON.stringify(verifiedCheckpoint) !== JSON.stringify(capturedCheckpoint) || observations.some((row) => row.gradingPlanSha256 !== native.gradingPlanSha256) || frames.some((row) => row.gradingPlanSha256 !== native.gradingPlanSha256))
|
|
2322
|
+
throw new Error("native sampled stage plan was not bound before acquisition");
|
|
2323
|
+
const resolution = JSON.parse(
|
|
2324
|
+
boundText(
|
|
2325
|
+
root,
|
|
2326
|
+
native.windowResolution,
|
|
2327
|
+
native.windowResolutionSha256,
|
|
2328
|
+
"native sampled window resolution"
|
|
2329
|
+
)
|
|
2330
|
+
);
|
|
2331
|
+
const markers = JSON.parse(
|
|
2332
|
+
boundText(
|
|
2333
|
+
root,
|
|
2334
|
+
native.gradingMarkers,
|
|
2335
|
+
native.gradingMarkersSha256,
|
|
2336
|
+
"native sampled trigger markers"
|
|
2337
|
+
)
|
|
2338
|
+
);
|
|
2339
|
+
const grading = nativeResolvedGradingWindow(plan, resolution, frames, markers);
|
|
2340
|
+
return nativeRefreshSamples({
|
|
2341
|
+
frames,
|
|
2342
|
+
reads: observations,
|
|
2343
|
+
clockBytes,
|
|
2344
|
+
finalFrames,
|
|
2345
|
+
finalSurfaceStatus,
|
|
2346
|
+
grading
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
module2.exports.authenticateNativeRefreshSamples = authenticateNativeRefreshSamples;
|
|
1968
2350
|
async function encodeNativeFramebufferMovie(rawDir, videoPath) {
|
|
1969
2351
|
const fs3 = require("node:fs");
|
|
1970
2352
|
const path5 = require("node:path");
|
|
@@ -1991,27 +2373,6 @@ var require_native_frame_receipt = __commonJS({
|
|
|
1991
2373
|
});
|
|
1992
2374
|
if (times.some((value, index) => index > 0 && value <= times[index - 1]))
|
|
1993
2375
|
throw new Error("nonmonotonic native source arrivals");
|
|
1994
|
-
const reads = rows.map((row) => ({
|
|
1995
|
-
callbackIndex: row.callbackIndex,
|
|
1996
|
-
seedBeforeRead: row.source?.seedBeforeRead,
|
|
1997
|
-
seedAfterRead: row.source?.seedAfterRead,
|
|
1998
|
-
arrival: BigInt(row.consumerArrival.machTicks)
|
|
1999
|
-
}));
|
|
2000
|
-
for (const [index, read] of reads.entries()) {
|
|
2001
|
-
if (!Number.isSafeInteger(read.seedBeforeRead) || !Number.isSafeInteger(read.seedAfterRead))
|
|
2002
|
-
throw new Error(`native callback ${read.callbackIndex} has no surface seed`);
|
|
2003
|
-
const previous = reads[index - 1];
|
|
2004
|
-
if (!previous) continue;
|
|
2005
|
-
const lost = read.seedBeforeRead - previous.seedAfterRead - 1;
|
|
2006
|
-
if (lost > 1) {
|
|
2007
|
-
const gapMs = Number(
|
|
2008
|
-
(read.arrival - previous.arrival) * BigInt(firstClock.timebaseNumer) / BigInt(firstClock.timebaseDenom)
|
|
2009
|
-
) / 1e6;
|
|
2010
|
-
throw new Error(
|
|
2011
|
-
`native recording skipped ${lost} surface updates between callbacks ${previous.callbackIndex} and ${read.callbackIndex} (surface seed ${previous.seedAfterRead} to ${read.seedBeforeRead}, ${gapMs.toFixed(1)}ms between arrivals): no captured frame shows them`
|
|
2012
|
-
);
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
2376
|
function selectTime(start, end) {
|
|
2016
2377
|
if (end - start === 1) return String(times[start]);
|
|
2017
2378
|
const middle = Math.floor((start + end) / 2);
|
|
@@ -2415,6 +2776,10 @@ var require_attempt_events = __commonJS({
|
|
|
2415
2776
|
};
|
|
2416
2777
|
}
|
|
2417
2778
|
function authenticateNativeCadenceCensus(root, sourceRoot, life, decoded) {
|
|
2779
|
+
const {
|
|
2780
|
+
authenticateNativeRefreshSamples
|
|
2781
|
+
} = require_native_frame_receipt();
|
|
2782
|
+
authenticateNativeRefreshSamples(root, sourceRoot, life);
|
|
2418
2783
|
const native = life.nativeFramebuffer;
|
|
2419
2784
|
if (!native || native.exitCode !== 0 || native.failure || native.sourceSha256 !== proofByteHash(
|
|
2420
2785
|
fs3.readFileSync(
|
|
@@ -2432,10 +2797,10 @@ var require_attempt_events = __commonJS({
|
|
|
2432
2797
|
root,
|
|
2433
2798
|
native.mediaFrames,
|
|
2434
2799
|
native.mediaFramesSha256,
|
|
2435
|
-
"native cadence
|
|
2800
|
+
"native cadence sampled surface census"
|
|
2436
2801
|
).trim().split("\n").map(JSON.parse);
|
|
2437
2802
|
if (frames.length !== decoded.observations.length || media.length !== frames.length)
|
|
2438
|
-
throw new Error("native cadence acquisition and
|
|
2803
|
+
throw new Error("native cadence acquisition and sampled surface census differ");
|
|
2439
2804
|
const {
|
|
2440
2805
|
decodeNativeFramebuffer,
|
|
2441
2806
|
requireNativeRawFrame
|
|
@@ -2454,12 +2819,22 @@ var require_attempt_events = __commonJS({
|
|
|
2454
2819
|
return row.callbackIndex;
|
|
2455
2820
|
})
|
|
2456
2821
|
);
|
|
2457
|
-
const
|
|
2822
|
+
const reads = boundText(
|
|
2823
|
+
root,
|
|
2824
|
+
native.reads,
|
|
2825
|
+
native.readsSha256,
|
|
2826
|
+
"native cadence observations"
|
|
2827
|
+
).split("\n").filter(Boolean).map(JSON.parse);
|
|
2458
2828
|
let expectedCallback = 0;
|
|
2459
|
-
for (const
|
|
2829
|
+
for (const read of reads) {
|
|
2460
2830
|
while (superseded.delete(expectedCallback)) expectedCallback++;
|
|
2831
|
+
if (read.callbackIndex !== expectedCallback++)
|
|
2832
|
+
throw new Error("native cadence observations have invalid callback order");
|
|
2833
|
+
}
|
|
2834
|
+
const origin = frames[0].consumerArrival;
|
|
2835
|
+
for (const [index, frame] of frames.entries()) {
|
|
2461
2836
|
const clock = frame.consumerArrival;
|
|
2462
|
-
if (
|
|
2837
|
+
if (media[index].sourceFrameIndex !== index || !clock || clock.domain !== "host-mach-absolute-time" || clock.domain !== origin.domain || clock.timebaseNumer !== origin.timebaseNumer || clock.timebaseDenom !== origin.timebaseDenom || !Number.isSafeInteger(clock.timebaseNumer) || clock.timebaseNumer <= 0 || !Number.isSafeInteger(clock.timebaseDenom) || clock.timebaseDenom <= 0)
|
|
2463
2838
|
throw new Error("native cadence source census has invalid clock or order");
|
|
2464
2839
|
const pts = (BigInt(clock.machTicks) - BigInt(origin.machTicks)) * BigInt(clock.timebaseNumer) / BigInt(clock.timebaseDenom);
|
|
2465
2840
|
if (pts < 0n || pts !== BigInt(media[index].ptsNanoseconds) || Math.abs(Number(pts) / 1e6 - decoded.observations[index].atMs) > 1e-3)
|
|
@@ -2723,6 +3098,26 @@ var require_attempt_events = __commonJS({
|
|
|
2723
3098
|
paths.add(native.receipt);
|
|
2724
3099
|
paths.add(native.superseded);
|
|
2725
3100
|
paths.add(native.mediaFrames);
|
|
3101
|
+
paths.add(native.gradingMarkers);
|
|
3102
|
+
paths.add(native.gradingPlan);
|
|
3103
|
+
paths.add(native.windowResolution);
|
|
3104
|
+
paths.add(native.reads);
|
|
3105
|
+
for (const receipt2 of native.finalReads) {
|
|
3106
|
+
const text = boundText(
|
|
3107
|
+
root,
|
|
3108
|
+
receipt2.path,
|
|
3109
|
+
receipt2.sha256,
|
|
3110
|
+
"native sampled end state"
|
|
3111
|
+
);
|
|
3112
|
+
paths.add(receipt2.path);
|
|
3113
|
+
for (const row of text.split("\n").filter(Boolean).map(JSON.parse)) {
|
|
3114
|
+
const file = proofBoundPath(
|
|
3115
|
+
path5.dirname(proofBoundPath(root, receipt2.path)),
|
|
3116
|
+
row.rawPath
|
|
3117
|
+
);
|
|
3118
|
+
paths.add(path5.relative(root, file).split(path5.sep).join("/"));
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
2726
3121
|
const rows = fs3.readFileSync(proofBoundPath(root, native.receipt), "utf8").trim().split("\n").map(JSON.parse);
|
|
2727
3122
|
for (const row of rows) {
|
|
2728
3123
|
const file = proofBoundPath(
|
|
@@ -13139,6 +13534,7 @@ var by = {
|
|
|
13139
13534
|
// detox/index.ts
|
|
13140
13535
|
var import_motion_change = __toESM(require_motion_change(), 1);
|
|
13141
13536
|
var import_proof_frame = __toESM(require_proof_frame(), 1);
|
|
13537
|
+
var WORKLET_FRAME_TRACE_LIMIT = 16384;
|
|
13142
13538
|
var BASE_URL = process.env.PEACH_URL || "http://localhost:5173";
|
|
13143
13539
|
var SCREENSHOT_DIR = process.env.PEACH_SCREENSHOT_DIR || path4.join(process.cwd(), "test", "detox-driver", "screenshots", String(process.pid));
|
|
13144
13540
|
var _browser = null;
|
|
@@ -13415,8 +13811,12 @@ async function waitForSootsimWorkletsIdle(page, timeout = 3e3) {
|
|
|
13415
13811
|
);
|
|
13416
13812
|
if (!result?.settled) {
|
|
13417
13813
|
const pending = result?.pending ?? "no peer stats reply";
|
|
13418
|
-
throw
|
|
13419
|
-
|
|
13814
|
+
throw await describeSootsimBridgeFailure(
|
|
13815
|
+
page,
|
|
13816
|
+
new Error(
|
|
13817
|
+
`sootsim worklet runtime did not settle after ${result?.elapsed ?? timeout}ms (pending=${pending}; observation=${JSON.stringify(result?.observation)})`
|
|
13818
|
+
),
|
|
13819
|
+
"sootsim worklet runtime did not settle"
|
|
13420
13820
|
);
|
|
13421
13821
|
}
|
|
13422
13822
|
}
|
|
@@ -14629,6 +15029,7 @@ var device = {
|
|
|
14629
15029
|
async installApp() {
|
|
14630
15030
|
},
|
|
14631
15031
|
async uninstallApp() {
|
|
15032
|
+
await closeBrowser(false);
|
|
14632
15033
|
},
|
|
14633
15034
|
async openURL(url) {
|
|
14634
15035
|
const page = getPage();
|
|
@@ -15005,6 +15406,14 @@ var device = {
|
|
|
15005
15406
|
definition: import_motion_change.MOTION_CHANGE_DEFINITION,
|
|
15006
15407
|
installGlobal: true
|
|
15007
15408
|
});
|
|
15409
|
+
if (options.workletFrameTracePath) {
|
|
15410
|
+
await page.evaluate(async (limit) => {
|
|
15411
|
+
const enable = window.SootSim?.bridges?.test?.setShellReanimatedTraceEnabled;
|
|
15412
|
+
if (typeof enable !== "function")
|
|
15413
|
+
throw new Error("worklet trace bridge is absent");
|
|
15414
|
+
await enable(true, true, limit);
|
|
15415
|
+
}, WORKLET_FRAME_TRACE_LIMIT);
|
|
15416
|
+
}
|
|
15008
15417
|
const capturePromise = page.evaluate(
|
|
15009
15418
|
async (input) => {
|
|
15010
15419
|
const captureWindow = window;
|
|
@@ -15108,49 +15517,8 @@ var device = {
|
|
|
15108
15517
|
const controller = await lockstep.begin(60, {
|
|
15109
15518
|
workletFrameTimestampsSeconds: input.workletFrameTimestampsSeconds
|
|
15110
15519
|
});
|
|
15111
|
-
|
|
15112
|
-
const
|
|
15113
|
-
nextMove: 0,
|
|
15114
|
-
pressFrameIndex: null,
|
|
15115
|
-
releaseReceipt: null,
|
|
15116
|
-
released: false,
|
|
15117
|
-
x: input.hold?.x ?? 0,
|
|
15118
|
-
y: input.hold?.y ?? 0
|
|
15119
|
-
};
|
|
15120
|
-
const holdEventFrameIndex = (afterMs) => (holdState.pressFrameIndex ?? 0) + controller.framesForOffset(afterMs);
|
|
15121
|
-
const nextHoldEventFrameIndex = () => {
|
|
15122
|
-
if (!input.hold || holdState.released || holdState.pressFrameIndex === null) {
|
|
15123
|
-
return null;
|
|
15124
|
-
}
|
|
15125
|
-
const move = moves[holdState.nextMove];
|
|
15126
|
-
return holdEventFrameIndex(move ? move.afterMs : input.hold.durationMs);
|
|
15127
|
-
};
|
|
15128
|
-
const advanceHold = async (receipt) => {
|
|
15129
|
-
if (!input.hold || !interact || holdState.released) return;
|
|
15130
|
-
if (holdState.pressFrameIndex === null) {
|
|
15131
|
-
holdState.pressFrameIndex = receipt.frameIndex - 1;
|
|
15132
|
-
}
|
|
15133
|
-
for (let move = moves[holdState.nextMove]; move && receipt.frameIndex >= holdEventFrameIndex(move.afterMs); move = moves[holdState.nextMove]) {
|
|
15134
|
-
holdState.nextMove++;
|
|
15135
|
-
holdState.x = move.x;
|
|
15136
|
-
holdState.y = move.y;
|
|
15137
|
-
await interact.touchMove(
|
|
15138
|
-
move.x,
|
|
15139
|
-
move.y,
|
|
15140
|
-
999,
|
|
15141
|
-
controller.initialFrame.virtualTimestampMs + move.afterMs
|
|
15142
|
-
);
|
|
15143
|
-
}
|
|
15144
|
-
if (receipt.frameIndex < holdEventFrameIndex(input.hold.durationMs)) return;
|
|
15145
|
-
holdState.released = true;
|
|
15146
|
-
holdState.releaseReceipt = receipt;
|
|
15147
|
-
await interact.touchUp(
|
|
15148
|
-
holdState.x,
|
|
15149
|
-
holdState.y,
|
|
15150
|
-
999,
|
|
15151
|
-
controller.initialFrame.virtualTimestampMs + input.hold.durationMs
|
|
15152
|
-
);
|
|
15153
|
-
};
|
|
15520
|
+
let heldInput = null;
|
|
15521
|
+
const releaseReceipt = () => controller.inputReceipts.find((receipt) => receipt.phase === "up");
|
|
15154
15522
|
const keptFrames = /* @__PURE__ */ new Map();
|
|
15155
15523
|
const keepFrame = (receipt, bitmap) => {
|
|
15156
15524
|
keptFrames.get(receipt.frameIndex)?.bitmap.close();
|
|
@@ -15169,17 +15537,51 @@ var device = {
|
|
|
15169
15537
|
throw new Error("paired worklets callback preceded the Peach trigger");
|
|
15170
15538
|
state.ready = true;
|
|
15171
15539
|
if (input.hold && interact) {
|
|
15172
|
-
const
|
|
15173
|
-
|
|
15174
|
-
|
|
15175
|
-
|
|
15176
|
-
|
|
15177
|
-
|
|
15178
|
-
|
|
15179
|
-
|
|
15180
|
-
|
|
15181
|
-
|
|
15182
|
-
|
|
15540
|
+
const hold = input.hold;
|
|
15541
|
+
const moves = hold.moves ?? [];
|
|
15542
|
+
const release = hold.release ?? moves.at(-1) ?? hold;
|
|
15543
|
+
const events = [
|
|
15544
|
+
{
|
|
15545
|
+
phase: "down",
|
|
15546
|
+
x: hold.x,
|
|
15547
|
+
y: hold.y,
|
|
15548
|
+
pointerId: 999,
|
|
15549
|
+
afterMs: 0,
|
|
15550
|
+
async deliver(timestamp2) {
|
|
15551
|
+
const hit = await interact.touchDown(hold.x, hold.y, 999, timestamp2);
|
|
15552
|
+
if (!hit)
|
|
15553
|
+
throw new Error(`peach motion hold missed at ${hold.x},${hold.y}`);
|
|
15554
|
+
}
|
|
15555
|
+
},
|
|
15556
|
+
...moves.map(
|
|
15557
|
+
(move) => ({
|
|
15558
|
+
phase: "move",
|
|
15559
|
+
x: move.x,
|
|
15560
|
+
y: move.y,
|
|
15561
|
+
pointerId: 999,
|
|
15562
|
+
afterMs: move.afterMs,
|
|
15563
|
+
eventAfterMs: move.eventAfterMs,
|
|
15564
|
+
async deliver(timestamp2) {
|
|
15565
|
+
await interact.touchMove(move.x, move.y, 999, timestamp2);
|
|
15566
|
+
}
|
|
15567
|
+
})
|
|
15568
|
+
),
|
|
15569
|
+
{
|
|
15570
|
+
phase: "up",
|
|
15571
|
+
x: release.x,
|
|
15572
|
+
y: release.y,
|
|
15573
|
+
pointerId: 999,
|
|
15574
|
+
afterMs: hold.durationMs,
|
|
15575
|
+
eventAfterMs: hold.eventDurationMs,
|
|
15576
|
+
async deliver(timestamp2) {
|
|
15577
|
+
await interact.touchUp(release.x, release.y, 999, timestamp2);
|
|
15578
|
+
}
|
|
15579
|
+
}
|
|
15580
|
+
];
|
|
15581
|
+
heldInput = controller.scheduleInput(events);
|
|
15582
|
+
heldInput.catch(() => {
|
|
15583
|
+
});
|
|
15584
|
+
await controller.flushFrame();
|
|
15183
15585
|
} else {
|
|
15184
15586
|
while (!state.triggerStarted) {
|
|
15185
15587
|
if (state.cancelled) throw new Error("peach motion capture was cancelled");
|
|
@@ -15213,9 +15615,8 @@ var device = {
|
|
|
15213
15615
|
for (let frame = 0; !visibleStart && frame < detectionFrameLimit; frame++) {
|
|
15214
15616
|
if (state.cancelled) throw new Error("peach motion capture was cancelled");
|
|
15215
15617
|
const receipt = await controller.stepFrame();
|
|
15216
|
-
await advanceHold(receipt);
|
|
15217
15618
|
const candidateBitmap = await captureBitmap();
|
|
15218
|
-
if (input.anchorAtTouch === "down" || input.anchorAtTouch === "up" &&
|
|
15619
|
+
if (input.anchorAtTouch === "down" || input.anchorAtTouch === "up" && releaseReceipt()?.frameIndex === receipt.frameIndex) {
|
|
15219
15620
|
keepFrame(receipt, await createImageBitmap(candidateBitmap));
|
|
15220
15621
|
}
|
|
15221
15622
|
const bounds = boundsFor(candidateBitmap);
|
|
@@ -15248,17 +15649,17 @@ var device = {
|
|
|
15248
15649
|
let currentReceipt = visibleStart;
|
|
15249
15650
|
if (input.anchorAtTouch === "down") driveStart = controller.initialFrame;
|
|
15250
15651
|
if (input.anchorAtTouch === "up") {
|
|
15251
|
-
while (!
|
|
15652
|
+
while (!releaseReceipt()) {
|
|
15252
15653
|
if (state.cancelled) throw new Error("peach motion capture was cancelled");
|
|
15253
15654
|
const receipt = await controller.stepFrame();
|
|
15254
|
-
await advanceHold(receipt);
|
|
15255
15655
|
currentReceipt = receipt;
|
|
15256
|
-
if (
|
|
15656
|
+
if (releaseReceipt()) keepFrame(receipt, await captureBitmap());
|
|
15257
15657
|
}
|
|
15258
|
-
|
|
15658
|
+
const released = releaseReceipt();
|
|
15659
|
+
if (!released) {
|
|
15259
15660
|
throw new Error("peach release anchoring found no release frame");
|
|
15260
15661
|
}
|
|
15261
|
-
driveStart =
|
|
15662
|
+
driveStart = { ...released, tenantFrame: null };
|
|
15262
15663
|
}
|
|
15263
15664
|
const resolvedAnchors = /* @__PURE__ */ new Map();
|
|
15264
15665
|
const anchorRecords = [];
|
|
@@ -15283,7 +15684,6 @@ var device = {
|
|
|
15283
15684
|
while (searchReceipt.virtualTimestampMs < searchStartVts) {
|
|
15284
15685
|
if (state.cancelled) throw new Error("peach motion capture was cancelled");
|
|
15285
15686
|
searchReceipt = await controller.stepFrame();
|
|
15286
|
-
await advanceHold(searchReceipt);
|
|
15287
15687
|
}
|
|
15288
15688
|
currentReceipt = searchReceipt;
|
|
15289
15689
|
let anchorBitmap = await captureBitmap();
|
|
@@ -15295,7 +15695,6 @@ var device = {
|
|
|
15295
15695
|
for (let frame = 0; frame < detectionFrameLimit; frame++) {
|
|
15296
15696
|
if (state.cancelled) throw new Error("peach motion capture was cancelled");
|
|
15297
15697
|
const receipt = await controller.stepFrame();
|
|
15298
|
-
await advanceHold(receipt);
|
|
15299
15698
|
const candidateBitmap = await captureBitmap();
|
|
15300
15699
|
const bounds = boundsFor(candidateBitmap);
|
|
15301
15700
|
const candidate = regionPixels(candidateBitmap);
|
|
@@ -15361,11 +15760,7 @@ var device = {
|
|
|
15361
15760
|
});
|
|
15362
15761
|
continue;
|
|
15363
15762
|
}
|
|
15364
|
-
for (let eventFrameIndex = nextHoldEventFrameIndex(); eventFrameIndex !== null && stageFrameIndex > eventFrameIndex; eventFrameIndex = nextHoldEventFrameIndex()) {
|
|
15365
|
-
await advanceHold(await controller.stepFrame());
|
|
15366
|
-
}
|
|
15367
15763
|
const receipt = await controller.stepToOffset(anchorReceipt, stage.offsetMs);
|
|
15368
|
-
await advanceHold(receipt);
|
|
15369
15764
|
currentReceipt = receipt;
|
|
15370
15765
|
snapshots.push({
|
|
15371
15766
|
actualMs: receipt.virtualTimestampMs - anchorReceipt.virtualTimestampMs,
|
|
@@ -15379,11 +15774,11 @@ var device = {
|
|
|
15379
15774
|
}
|
|
15380
15775
|
if (input.workletFrameTimestampsSeconds) controller.endWorkletFrameReceipt();
|
|
15381
15776
|
let releasedOffsetMs = 0;
|
|
15382
|
-
while (
|
|
15777
|
+
while (input.hold && !releaseReceipt()) {
|
|
15383
15778
|
const receipt = await controller.stepFrame();
|
|
15384
|
-
await advanceHold(receipt);
|
|
15385
15779
|
releasedOffsetMs = receipt.virtualTimestampMs - driveStart.virtualTimestampMs;
|
|
15386
15780
|
}
|
|
15781
|
+
await heldInput;
|
|
15387
15782
|
if (input.settleMs > 0) {
|
|
15388
15783
|
const maximumPrimaryMs = snapshots.reduce(
|
|
15389
15784
|
(maximum, snapshot2) => Math.max(
|
|
@@ -15449,22 +15844,24 @@ var device = {
|
|
|
15449
15844
|
token
|
|
15450
15845
|
}
|
|
15451
15846
|
);
|
|
15452
|
-
|
|
15453
|
-
|
|
15454
|
-
(captureToken) => {
|
|
15455
|
-
const registry = window.__sootsimVisibleChangeCaptures;
|
|
15456
|
-
return registry?.[captureToken]?.ready === true;
|
|
15457
|
-
},
|
|
15458
|
-
token,
|
|
15459
|
-
{ timeout: 5e3 }
|
|
15460
|
-
),
|
|
15461
|
-
capturePromise.then(() => {
|
|
15462
|
-
throw new Error("peach motion capture completed before its trigger");
|
|
15463
|
-
})
|
|
15464
|
-
]);
|
|
15465
|
-
const triggerStartMs = Date.now();
|
|
15847
|
+
let captureFailure;
|
|
15848
|
+
let triggerStartMs = 0;
|
|
15466
15849
|
let triggerCompletedMs = 0;
|
|
15467
15850
|
try {
|
|
15851
|
+
await Promise.race([
|
|
15852
|
+
page.waitForFunction(
|
|
15853
|
+
(captureToken) => {
|
|
15854
|
+
const registry = window.__sootsimVisibleChangeCaptures;
|
|
15855
|
+
return registry?.[captureToken]?.ready === true;
|
|
15856
|
+
},
|
|
15857
|
+
token,
|
|
15858
|
+
{ timeout: 5e3 }
|
|
15859
|
+
),
|
|
15860
|
+
capturePromise.then(() => {
|
|
15861
|
+
throw new Error("peach motion capture completed before its trigger");
|
|
15862
|
+
})
|
|
15863
|
+
]);
|
|
15864
|
+
triggerStartMs = Date.now();
|
|
15468
15865
|
const triggerPromise = (async () => {
|
|
15469
15866
|
if (!options.hold) {
|
|
15470
15867
|
if (typeof options.trigger !== "function") {
|
|
@@ -15500,6 +15897,7 @@ var device = {
|
|
|
15500
15897
|
}
|
|
15501
15898
|
};
|
|
15502
15899
|
} catch (error) {
|
|
15900
|
+
captureFailure = { error };
|
|
15503
15901
|
await page.evaluate((captureToken) => {
|
|
15504
15902
|
const registry = window.__sootsimVisibleChangeCaptures;
|
|
15505
15903
|
const capture = registry?.[captureToken];
|
|
@@ -15509,6 +15907,55 @@ var device = {
|
|
|
15509
15907
|
await capturePromise.catch(() => {
|
|
15510
15908
|
});
|
|
15511
15909
|
throw error;
|
|
15910
|
+
} finally {
|
|
15911
|
+
if (options.workletFrameTracePath) {
|
|
15912
|
+
try {
|
|
15913
|
+
const events = await page.evaluate(async () => {
|
|
15914
|
+
const test = window.SootSim?.bridges?.test;
|
|
15915
|
+
const read = test?.getShellReanimatedTrace;
|
|
15916
|
+
const enable = test?.setShellReanimatedTraceEnabled;
|
|
15917
|
+
if (typeof read !== "function" || typeof enable !== "function")
|
|
15918
|
+
throw new Error("worklet trace bridge is absent");
|
|
15919
|
+
try {
|
|
15920
|
+
return await read();
|
|
15921
|
+
} finally {
|
|
15922
|
+
await enable(false, false);
|
|
15923
|
+
}
|
|
15924
|
+
});
|
|
15925
|
+
fs2.writeFileSync(
|
|
15926
|
+
options.workletFrameTracePath,
|
|
15927
|
+
JSON.stringify(
|
|
15928
|
+
{
|
|
15929
|
+
source: "peach-native-frame-queue",
|
|
15930
|
+
limit: WORKLET_FRAME_TRACE_LIMIT,
|
|
15931
|
+
events
|
|
15932
|
+
},
|
|
15933
|
+
null,
|
|
15934
|
+
2
|
|
15935
|
+
)
|
|
15936
|
+
);
|
|
15937
|
+
} catch (traceError) {
|
|
15938
|
+
if (!captureFailure) throw traceError;
|
|
15939
|
+
const primary = captureFailure.error;
|
|
15940
|
+
if (primary && (typeof primary === "object" || typeof primary === "function") && Object.isExtensible(primary)) {
|
|
15941
|
+
try {
|
|
15942
|
+
Object.defineProperty(primary, "workletFrameTraceError", {
|
|
15943
|
+
value: traceError,
|
|
15944
|
+
configurable: true
|
|
15945
|
+
});
|
|
15946
|
+
} catch (attachmentError) {
|
|
15947
|
+
console.error(
|
|
15948
|
+
"[peach motion capture] could not attach trace failure:",
|
|
15949
|
+
attachmentError
|
|
15950
|
+
);
|
|
15951
|
+
}
|
|
15952
|
+
}
|
|
15953
|
+
console.error(
|
|
15954
|
+
"[peach motion capture] secondary worklet trace failure:",
|
|
15955
|
+
traceError
|
|
15956
|
+
);
|
|
15957
|
+
}
|
|
15958
|
+
}
|
|
15512
15959
|
}
|
|
15513
15960
|
},
|
|
15514
15961
|
// FAST multi-stage capture for sub-second animation diffs. each stage
|