peachsim 0.1.1635 → 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 +10 -1
- package/cli/app-config.ts +14 -5
- package/cli/app-stubs.ts +96 -22
- package/cli/bridge-flow-runner.ts +2 -2
- package/cli/cloud-client.ts +60 -40
- 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 +37 -20
- package/cli/commands/flow.ts +1 -1
- package/cli/commands/inspect/actions.ts +3 -2
- package/cli/commands/inspect/core.ts +0 -8
- package/cli/commands/inspect/shared.ts +9 -4
- package/cli/commands/inspect.ts +65 -61
- package/cli/commands/platform.ts +2 -1
- package/cli/commands/preview.ts +1 -1
- package/cli/commands/record.ts +154 -68
- package/cli/commands/swift.ts +18 -4
- package/cli/commands/three-mode.ts +4 -38
- package/cli/commands/upload.ts +55 -49
- package/cli/drivers/playwright-sim-host.ts +65 -42
- package/cli/flow-live-status.ts +0 -2
- package/cli/main.ts +2 -2
- package/cli/outbound-endpoints.ts +7 -5
- package/cli/pause-aware-timeout.ts +4 -0
- package/cli/ws-bridge.ts +19 -16
- package/detox/attempt-events.cjs +99 -6
- package/detox/attempt-events.d.cts +7 -0
- package/detox/expectations.ts +7 -3
- package/detox/index.ts +259 -139
- package/detox/jest-environment.cjs +5 -1
- package/detox/motion-change.cjs +170 -21
- package/detox/motion-change.d.cts +32 -0
- package/detox/navigation-lifecycle.ts +35 -3
- 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 +17 -7
- 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 +5014 -2268
- 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 +1036 -166
- 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 +4613 -1963
- package/dist-lib/expo-swift-metadata.cjs +9 -7
- package/dist-lib/expo-swift-metadata.mjs +9 -7
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +58072 -16400
- 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 +2790 -143
- 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 +2579 -95
- package/dist-lib/native-app-identity-project.cjs +243 -53
- package/dist-lib/native-app-identity-project.mjs +243 -53
- package/dist-lib/native-app-identity.cjs +29 -30
- package/dist-lib/native-app-identity.mjs +29 -30
- 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 +2 -2
- package/dist-lib/react-native-host-modules.mjs +2 -2
- package/dist-lib/scripts/dev-server-scanner.cjs +8 -6
- package/dist-lib/sdk.cjs +4 -3
- package/dist-lib/sdk.mjs +4 -3
- package/dist-lib/skills.cjs +66475 -23097
- package/dist-lib/swift-compile.cjs +452 -260
- package/dist-lib/swift-compile.mjs +451 -252
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +14 -7
- package/dist-lib/swift-submit.mjs +14 -7
- package/dist-lib/swift-toolchain.cjs +316 -41
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +8188 -5636
- package/dist-lib/vite.cjs +2857 -12
- package/dist-lib/worklet-executable.cjs +14 -2
- package/dist-lib/worklet-executable.mjs +14 -2
- package/package.json +1 -1
- package/src/backend-origin.ts +21 -6
- package/src/expo-swift-host.ts +5 -2
- package/src/expoKotlinContract.ts +4 -7
- package/src/expoSwiftContract.ts +4 -6
- package/src/host/staged-host-files.ts +9 -0
- package/src/native-app-identity-project.ts +1 -4
- package/src/native-app-identity.ts +1 -4
- package/src/nativeAppIconContract.ts +1 -4
- package/src/output-lock-stdio.ts +36 -0
- package/src/react-native-bridge-modules.ts +106 -78
- package/src/react-native-host-modules.ts +4 -1
- package/src/settings-bundle.ts +1 -4
- package/src/swift-compile.ts +224 -139
- package/src/swift-distribution.lock.json +23 -12
- package/src/swift-distribution.ts +103 -4
- package/src/swift-host.ts +95 -30
- package/src/swift-icu.ts +16 -10
- package/src/swift-objc-compile.ts +547 -0
- package/src/swift-objc-metadata.ts +204 -0
- package/src/swift-objc-sdk.ts +883 -19
- package/src/swift-objc-standin.ts +126 -14
- package/src/swift-package-manifest.ts +42 -1
- package/src/swift-packages.ts +35 -21
- package/src/swift-root-lock.ts +153 -0
- package/src/swift-standin-emit.ts +40 -7
- package/src/swift-standins.ts +404 -138
- package/src/swift-submit.ts +18 -7
- package/src/swift-toolchain.ts +378 -46
- package/src/swiftContract.ts +12 -0
- package/src/worker-system-chunks.ts +11 -12
- package/src/worklet-executable.ts +19 -1
- package/src/xcode-project.ts +280 -33
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;
|
|
@@ -1147,6 +1147,32 @@ var require_lib = __commonJS({
|
|
|
1147
1147
|
}
|
|
1148
1148
|
});
|
|
1149
1149
|
|
|
1150
|
+
// ../peach-engine/src/platform/adapter.ts
|
|
1151
|
+
var IOS_ADAPTER, ANDROID_ADAPTER, RUNTIME_PLATFORM_ADAPTERS;
|
|
1152
|
+
var init_adapter = __esm({
|
|
1153
|
+
"../peach-engine/src/platform/adapter.ts"() {
|
|
1154
|
+
"use strict";
|
|
1155
|
+
IOS_ADAPTER = Object.freeze({
|
|
1156
|
+
id: "ios",
|
|
1157
|
+
displayName: "iOS",
|
|
1158
|
+
isIOS: true,
|
|
1159
|
+
isAndroid: false,
|
|
1160
|
+
select: (registry) => registry.ios
|
|
1161
|
+
});
|
|
1162
|
+
ANDROID_ADAPTER = Object.freeze({
|
|
1163
|
+
id: "android",
|
|
1164
|
+
displayName: "Android",
|
|
1165
|
+
isIOS: false,
|
|
1166
|
+
isAndroid: true,
|
|
1167
|
+
select: (registry) => registry.android
|
|
1168
|
+
});
|
|
1169
|
+
RUNTIME_PLATFORM_ADAPTERS = Object.freeze({
|
|
1170
|
+
ios: IOS_ADAPTER,
|
|
1171
|
+
android: ANDROID_ADAPTER
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1150
1176
|
// detox/motion-change.cjs
|
|
1151
1177
|
var require_motion_change = __commonJS({
|
|
1152
1178
|
"detox/motion-change.cjs"(exports2, module2) {
|
|
@@ -1220,29 +1246,48 @@ var require_motion_change = __commonJS({
|
|
|
1220
1246
|
var SETTLE_BLIP_MAXIMUM_CHANGE_PERCENT = 0.6;
|
|
1221
1247
|
var GESTURE_MOTION_MIN_TRANSITIONS = 120;
|
|
1222
1248
|
var GESTURE_TRAVEL_RATIO_RANGE = Object.freeze({ min: 0.8, max: 1.25 });
|
|
1249
|
+
var GESTURE_QUARTER_TRAVEL_RATIO_RANGE = Object.freeze({ min: 0.5, max: 2 });
|
|
1223
1250
|
function evaluateMotionTransitions(transitions, oracleTarget) {
|
|
1224
1251
|
const key = (transition) => `${transition.fromLabel}\0${transition.toLabel}`;
|
|
1225
1252
|
const oracle = transitions.filter((transition) => transition.target === oracleTarget);
|
|
1226
1253
|
const sootsim = transitions.filter((transition) => transition.target === "sootsim");
|
|
1227
|
-
const
|
|
1254
|
+
const paired = oracle.length === sootsim.length && new Set(oracle.map(key)).size === oracle.length && oracle.every((transition, index) => key(transition) === key(sootsim[index]));
|
|
1228
1255
|
const measured = transitions.every(
|
|
1229
1256
|
(transition) => transition.dimensionsMatch && transition.changePercent !== null && transition.changePixels !== null && transition.totalPixels !== null
|
|
1230
1257
|
);
|
|
1231
|
-
const moving = oracle.filter((transition) => transition.passedMinimum);
|
|
1232
1258
|
const travel = (list) => list.filter(
|
|
1233
1259
|
(transition) => transition.changePercent >= PAIRED_MOTION_MINIMUM_CHANGE_PERCENT
|
|
1234
1260
|
).reduce((total, transition) => total + transition.changePercent, 0);
|
|
1235
|
-
const
|
|
1236
|
-
|
|
1261
|
+
const movesNear = (list, index) => [index - 1, index, index + 1].some((neighbour) => list[neighbour]?.passedMinimum === true);
|
|
1262
|
+
const pairedChange = (transition) => transition.changePercent >= PAIRED_MOTION_MINIMUM_CHANGE_PERCENT;
|
|
1263
|
+
const settleBlip = (list) => {
|
|
1264
|
+
const index = list.findLastIndex(pairedChange);
|
|
1265
|
+
return index >= 0 && list[index].changePercent < SETTLE_BLIP_MAXIMUM_CHANGE_PERCENT && !(list[index - 1]?.passedMinimum ?? true) ? index : -1;
|
|
1266
|
+
};
|
|
1267
|
+
const oracleBlip = settleBlip(oracle);
|
|
1268
|
+
const sootsimBlip = settleBlip(sootsim);
|
|
1269
|
+
const moving = oracle.filter(
|
|
1270
|
+
(transition, index) => pairedChange(transition) && index !== oracleBlip
|
|
1237
1271
|
);
|
|
1238
|
-
const isSettleBlip = (transition, index) => transition.changePercent < SETTLE_BLIP_MAXIMUM_CHANGE_PERCENT && !(oracle[index - 1]?.passedMinimum ?? true);
|
|
1239
1272
|
const within = (actual, expected, range) => actual >= expected * range.min && actual <= expected * range.max;
|
|
1240
|
-
const
|
|
1241
|
-
(
|
|
1273
|
+
const quarter = (list, index) => list.slice(
|
|
1274
|
+
Math.floor(list.length * index / 4),
|
|
1275
|
+
Math.floor(list.length * (index + 1) / 4)
|
|
1276
|
+
);
|
|
1277
|
+
const follows = oracle.length >= GESTURE_MOTION_MIN_TRANSITIONS ? within(travel(sootsim), travel(oracle), GESTURE_TRAVEL_RATIO_RANGE) && [0, 1, 2, 3].every(
|
|
1278
|
+
(index) => within(
|
|
1279
|
+
travel(quarter(sootsim, index)),
|
|
1280
|
+
travel(quarter(oracle, index)),
|
|
1281
|
+
GESTURE_QUARTER_TRAVEL_RATIO_RANGE
|
|
1282
|
+
)
|
|
1283
|
+
) : oracle.every(
|
|
1284
|
+
(transition, index) => !pairedChange(transition) || index === oracleBlip || movesNear(sootsim, index)
|
|
1285
|
+
) && sootsim.every(
|
|
1286
|
+
(transition, index) => !pairedChange(transition) || index === sootsimBlip || movesNear(oracle, index)
|
|
1242
1287
|
);
|
|
1243
1288
|
return {
|
|
1244
1289
|
moving: moving.length,
|
|
1245
|
-
passed: measured && oracle.length > 0 &&
|
|
1290
|
+
passed: measured && oracle.length > 0 && paired && moving.length > 0 && follows
|
|
1246
1291
|
};
|
|
1247
1292
|
}
|
|
1248
1293
|
var DISPLAY_LINK_INTERVAL_MS = 1e3 / 60;
|
|
@@ -1396,8 +1441,78 @@ var require_motion_change = __commonJS({
|
|
|
1396
1441
|
}
|
|
1397
1442
|
return { mode: "playing", anchorMs: anchor.atMs, holds };
|
|
1398
1443
|
}
|
|
1444
|
+
var PUBLICATION_DENSE_LADDER_MIN_STAGES = 10;
|
|
1445
|
+
var PUBLICATION_MIN_INTERMEDIATE_PASSES = 2;
|
|
1446
|
+
function motionCycleOnsets(event, eventPath) {
|
|
1447
|
+
if (!Array.isArray(event.anchors)) return [];
|
|
1448
|
+
const cycles = event.anchors.filter(
|
|
1449
|
+
(anchor) => typeof anchor === "object" && anchor !== null && anchor.cycle === true
|
|
1450
|
+
);
|
|
1451
|
+
if (cycles.length === 0) return [];
|
|
1452
|
+
const finite = (value, label) => {
|
|
1453
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
1454
|
+
throw new Error(`motion event cycle ${label} has no onset time: ${eventPath}`);
|
|
1455
|
+
return value;
|
|
1456
|
+
};
|
|
1457
|
+
const android = event.captureMode === "android-screenrecord-sequence";
|
|
1458
|
+
const peach = typeof event.driveStart === "object" && event.driveStart !== null;
|
|
1459
|
+
const basisMs = peach ? finite(event.driveStart.virtualTimestampMs, "transition") : android ? finite(event.triggerProducerSeconds, "transition") * 1e3 : finite(event.triggerInVideoSec, "transition") * 1e3;
|
|
1460
|
+
return [
|
|
1461
|
+
{ id: "transition", atMs: basisMs },
|
|
1462
|
+
...cycles.map((anchor) => {
|
|
1463
|
+
if (typeof anchor.id !== "string")
|
|
1464
|
+
throw new Error(`motion event cycle anchor has no id: ${eventPath}`);
|
|
1465
|
+
const atMs = peach ? finite(anchor.virtualTimestampMs, anchor.id) : android ? finite(anchor.producerSeconds, anchor.id) * 1e3 : finite(anchor.detection?.atSec, anchor.id) * 1e3;
|
|
1466
|
+
return { id: anchor.id, atMs };
|
|
1467
|
+
})
|
|
1468
|
+
];
|
|
1469
|
+
}
|
|
1470
|
+
var MOTION_CYCLE_PERIOD_TOLERANCE_MS = 1e3 / 60;
|
|
1471
|
+
function motionCyclePeriods(reference, sootsim) {
|
|
1472
|
+
const ids = (onsets) => onsets.map((onset) => onset.id).join(",");
|
|
1473
|
+
if (ids(reference) !== ids(sootsim))
|
|
1474
|
+
throw new Error(
|
|
1475
|
+
`motion cycle anchors differ between lanes: reference [${ids(reference)}], sootsim [${ids(sootsim)}]`
|
|
1476
|
+
);
|
|
1477
|
+
return reference.slice(1).map((onset, index) => {
|
|
1478
|
+
const referenceMs = onset.atMs - reference[index].atMs;
|
|
1479
|
+
const sootsimMs = sootsim[index + 1].atMs - sootsim[index].atMs;
|
|
1480
|
+
return {
|
|
1481
|
+
id: onset.id,
|
|
1482
|
+
passed: Math.abs(referenceMs - sootsimMs) <= MOTION_CYCLE_PERIOD_TOLERANCE_MS,
|
|
1483
|
+
referenceMs,
|
|
1484
|
+
sootsimMs
|
|
1485
|
+
};
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
function evaluateMotionPublication(stages, transitions, oracleTarget, cycleOnsets) {
|
|
1489
|
+
const cyclePeriods = motionCyclePeriods(cycleOnsets.reference, cycleOnsets.sootsim);
|
|
1490
|
+
const intermediates = stages.slice(1, -1);
|
|
1491
|
+
const intermediatePassed = intermediates.filter((stage) => stage.passedThreshold).length;
|
|
1492
|
+
const stagesByLabel = new Map(stages.map((stage) => [stage.label, stage]));
|
|
1493
|
+
const oracleMovingInto = new Set(
|
|
1494
|
+
transitions.filter(
|
|
1495
|
+
(transition) => transition.target === oracleTarget && (transition.changePercent >= PAIRED_MOTION_MINIMUM_CHANGE_PERCENT || transition.changePixels > 0 && stagesByLabel.get(transition.toLabel)?.oracleFromFirstPercent >= PAIRED_MOTION_MINIMUM_CHANGE_PERCENT && stagesByLabel.get(transition.toLabel)?.oracleFromLastPercent >= PAIRED_MOTION_MINIMUM_CHANGE_PERCENT)
|
|
1496
|
+
).map((transition) => transition.toLabel)
|
|
1497
|
+
);
|
|
1498
|
+
const movingPassed = intermediates.filter(
|
|
1499
|
+
(stage) => stage.passedThreshold && oracleMovingInto.has(stage.label)
|
|
1500
|
+
).length;
|
|
1501
|
+
const needsIntermediates = stages.length >= PUBLICATION_DENSE_LADDER_MIN_STAGES;
|
|
1502
|
+
const first = stages[0];
|
|
1503
|
+
const last = stages[stages.length - 1];
|
|
1504
|
+
return {
|
|
1505
|
+
passed: stages.length > 0 && first.passedThreshold && last.passedThreshold && (!needsIntermediates || movingPassed >= PUBLICATION_MIN_INTERMEDIATE_PASSES) && cyclePeriods.every((period) => period.passed) && evaluateMotionTransitions(transitions, oracleTarget).passed,
|
|
1506
|
+
cyclePeriods,
|
|
1507
|
+
intermediateCount: intermediates.length,
|
|
1508
|
+
intermediatePassed,
|
|
1509
|
+
movingPassed
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1399
1512
|
module2.exports = {
|
|
1400
1513
|
MOTION_CHANGE_DEFINITION: MOTION_CHANGE_DEFINITION2,
|
|
1514
|
+
evaluateMotionPublication,
|
|
1515
|
+
motionCycleOnsets,
|
|
1401
1516
|
measureMotionChange: measureMotionChange2,
|
|
1402
1517
|
measureProofMotionChange,
|
|
1403
1518
|
evaluateMotionTransitions,
|
|
@@ -1409,6 +1524,26 @@ var require_motion_change = __commonJS({
|
|
|
1409
1524
|
}
|
|
1410
1525
|
});
|
|
1411
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
|
+
|
|
1412
1547
|
// ../../scripts/lib/remote-build-delivery.ts
|
|
1413
1548
|
var remote_build_delivery_exports = {};
|
|
1414
1549
|
__export(remote_build_delivery_exports, {
|
|
@@ -1540,6 +1675,7 @@ var require_native_frame_receipt = __commonJS({
|
|
|
1540
1675
|
"../peach-conformance/e2e/utils/native-frame-receipt.cjs"(exports2, module2) {
|
|
1541
1676
|
"use strict";
|
|
1542
1677
|
var { createHash } = require("node:crypto");
|
|
1678
|
+
var { frameIndexForOffset: frameIndexForOffset2 } = (init_frame_offset(), __toCommonJS(frame_offset_exports));
|
|
1543
1679
|
function nativeVideoFrameReceipt(recordingSha256, sourceFrameIndex, ptsSeconds, inputClock) {
|
|
1544
1680
|
if (!/^[a-f0-9]{64}$/.test(recordingSha256)) throw new Error("invalid recording hash");
|
|
1545
1681
|
if (!Number.isSafeInteger(sourceFrameIndex) || sourceFrameIndex < 0)
|
|
@@ -1620,25 +1756,29 @@ var require_native_frame_receipt = __commonJS({
|
|
|
1620
1756
|
if (output !== bytes.length) throw new Error("incomplete native framebuffer");
|
|
1621
1757
|
return bytes;
|
|
1622
1758
|
}
|
|
1623
|
-
function
|
|
1759
|
+
function nativeFramebufferPixels(receipt, bytes) {
|
|
1624
1760
|
requireNativeRawFrame(receipt, bytes);
|
|
1625
|
-
const { PNG } = require("pngjs");
|
|
1626
|
-
const fs3 = require("node:fs");
|
|
1627
1761
|
const { width, height, stride } = receipt.rawFramebuffer;
|
|
1628
|
-
|
|
1629
|
-
throw new Error("native framebuffer and screenshot dimensions differ");
|
|
1630
|
-
const png = new PNG({ width, height });
|
|
1762
|
+
const data = Buffer.allocUnsafe(width * height * 4);
|
|
1631
1763
|
for (let y = 0; y < height; y++) {
|
|
1632
1764
|
for (let x = 0; x < width; x++) {
|
|
1633
1765
|
const source = y * stride + x * 4;
|
|
1634
1766
|
const destination = (y * width + x) * 4;
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
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];
|
|
1639
1771
|
}
|
|
1640
1772
|
}
|
|
1641
|
-
|
|
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));
|
|
1642
1782
|
return receipt;
|
|
1643
1783
|
}
|
|
1644
1784
|
module2.exports = {
|
|
@@ -1646,6 +1786,7 @@ var require_native_frame_receipt = __commonJS({
|
|
|
1646
1786
|
requireNativeProducerFrame,
|
|
1647
1787
|
requireNativeRawFrame,
|
|
1648
1788
|
decodeNativeFramebuffer,
|
|
1789
|
+
nativeFramebufferPixels,
|
|
1649
1790
|
writeNativeFramebufferPng
|
|
1650
1791
|
};
|
|
1651
1792
|
async function buildNativeFrameConsumer({ root, cacheRoot, waitForAdmission = false }) {
|
|
@@ -1757,6 +1898,455 @@ var require_native_frame_receipt = __commonJS({
|
|
|
1757
1898
|
};
|
|
1758
1899
|
}
|
|
1759
1900
|
module2.exports.nativeUIKitInputReceipt = nativeUIKitInputReceipt;
|
|
1901
|
+
function nativeUIFrameClockReceipt(bytes) {
|
|
1902
|
+
if (!Buffer.isBuffer(bytes))
|
|
1903
|
+
throw new Error("ui frame clock receipt must preserve source bytes");
|
|
1904
|
+
const rows = bytes.toString("utf8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
1905
|
+
if (rows.at(-1)?.kind !== "stopped")
|
|
1906
|
+
throw new Error("ui frame clock receipt was not closed by its recorder");
|
|
1907
|
+
const ticks = [];
|
|
1908
|
+
const workletsTicks = [];
|
|
1909
|
+
for (const row of rows.slice(0, -1)) {
|
|
1910
|
+
if (row.kind === "worklets-frame") {
|
|
1911
|
+
if (!Number.isFinite(row.timestampSeconds) || row.timestampSeconds < 0)
|
|
1912
|
+
throw new Error("invalid worklets frame clock row");
|
|
1913
|
+
if (workletsTicks.length > 0 && row.timestampSeconds <= workletsTicks.at(-1).timestampSeconds)
|
|
1914
|
+
throw new Error("worklets frame clock timestamps must increase");
|
|
1915
|
+
workletsTicks.push({ timestampSeconds: row.timestampSeconds });
|
|
1916
|
+
continue;
|
|
1917
|
+
}
|
|
1918
|
+
if (row.kind !== "ui-frame" || !Number.isFinite(row.targetTimestampSeconds) || !Number.isFinite(row.timestampSeconds) || !(row.durationSeconds > 0))
|
|
1919
|
+
throw new Error("invalid ui frame clock row");
|
|
1920
|
+
if (ticks.length > 0 && row.targetTimestampSeconds <= ticks.at(-1).targetTimestampSeconds)
|
|
1921
|
+
throw new Error("ui frame clock target timestamps must increase");
|
|
1922
|
+
ticks.push({
|
|
1923
|
+
targetTimestampSeconds: row.targetTimestampSeconds,
|
|
1924
|
+
timestampSeconds: row.timestampSeconds,
|
|
1925
|
+
durationSeconds: row.durationSeconds,
|
|
1926
|
+
clockBridge: row.clockBridge,
|
|
1927
|
+
callbackThread: row.callbackThread,
|
|
1928
|
+
callbackWasMainThread: row.callbackWasMainThread
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
if (ticks.length < 2)
|
|
1932
|
+
throw new Error("ui frame clock receipt holds fewer than two ticks");
|
|
1933
|
+
return {
|
|
1934
|
+
domain: "ca-media-time-seconds",
|
|
1935
|
+
source: "cadisplaylink-target-timestamp",
|
|
1936
|
+
sourceReceiptSha256: createHash("sha256").update(bytes).digest("hex"),
|
|
1937
|
+
ticks,
|
|
1938
|
+
workletsTicks
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
module2.exports.nativeUIFrameClockReceipt = nativeUIFrameClockReceipt;
|
|
1942
|
+
function pairedNativeWorkletFrameClock(root, lifecycle, seed = "ui-frame-after-delivered-touch-up") {
|
|
1943
|
+
const { boundText } = require_attempt_events();
|
|
1944
|
+
if (!lifecycle || typeof lifecycle !== "object" || Array.isArray(lifecycle))
|
|
1945
|
+
throw new Error("paired worklets clock needs a retained native lifecycle");
|
|
1946
|
+
const receipt = lifecycle.uiFrameClock;
|
|
1947
|
+
if (!receipt)
|
|
1948
|
+
throw new Error("paired worklets clock needs a retained native UI clock receipt");
|
|
1949
|
+
const inputReceipt = lifecycle.deliveredUIKitInput;
|
|
1950
|
+
if (!inputReceipt)
|
|
1951
|
+
throw new Error("paired worklets clock needs a delivered UIKit touch-up witness");
|
|
1952
|
+
const clock = nativeUIFrameClockReceipt(
|
|
1953
|
+
Buffer.from(
|
|
1954
|
+
boundText(
|
|
1955
|
+
root,
|
|
1956
|
+
receipt.sourceReceipt,
|
|
1957
|
+
receipt.sourceReceiptSha256,
|
|
1958
|
+
"native UI clock receipt"
|
|
1959
|
+
)
|
|
1960
|
+
)
|
|
1961
|
+
);
|
|
1962
|
+
const input = nativeUIKitInputReceipt(
|
|
1963
|
+
Buffer.from(
|
|
1964
|
+
boundText(
|
|
1965
|
+
root,
|
|
1966
|
+
inputReceipt.sourceReceipt,
|
|
1967
|
+
inputReceipt.sourceReceiptSha256,
|
|
1968
|
+
"delivered UIKit input receipt"
|
|
1969
|
+
)
|
|
1970
|
+
)
|
|
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
|
+
}
|
|
2008
|
+
const up = input.events.find((event) => event.phase === 3);
|
|
2009
|
+
if (input.status !== "witnessed" || !up)
|
|
2010
|
+
throw new Error("paired worklets clock needs a delivered UIKit touch-up witness");
|
|
2011
|
+
const startIndex = clock.ticks.findIndex(
|
|
2012
|
+
(tick2) => tick2.timestampSeconds > up.deliveredAtSeconds
|
|
2013
|
+
);
|
|
2014
|
+
if (startIndex < 1)
|
|
2015
|
+
throw new Error("native UI clock does not bracket delivered touch-up");
|
|
2016
|
+
const timestampsSeconds = clock.ticks.slice(startIndex - 1).map((tick2) => tick2.targetTimestampSeconds);
|
|
2017
|
+
return {
|
|
2018
|
+
provenance: {
|
|
2019
|
+
sourceReceipt: receipt.sourceReceipt,
|
|
2020
|
+
sourceReceiptSha256: receipt.sourceReceiptSha256,
|
|
2021
|
+
inputReceipt: inputReceipt.sourceReceipt,
|
|
2022
|
+
inputReceiptSha256: inputReceipt.sourceReceiptSha256,
|
|
2023
|
+
deliveredTouchUpSeconds: up.deliveredAtSeconds,
|
|
2024
|
+
startIndex,
|
|
2025
|
+
alignment: "inferred-next-ui-frame-after-delivered-touch-up",
|
|
2026
|
+
frameCount: timestampsSeconds.length - 1
|
|
2027
|
+
},
|
|
2028
|
+
// the trigger reads the previous frame; the first delivery reads startIndex.
|
|
2029
|
+
timestampsSeconds
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
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;
|
|
1760
2350
|
async function encodeNativeFramebufferMovie(rawDir, videoPath) {
|
|
1761
2351
|
const fs3 = require("node:fs");
|
|
1762
2352
|
const path5 = require("node:path");
|
|
@@ -1979,6 +2569,13 @@ var require_attempt_events = __commonJS({
|
|
|
1979
2569
|
throw new Error("cadence evidence path escapes its root");
|
|
1980
2570
|
return resolved;
|
|
1981
2571
|
}
|
|
2572
|
+
function boundText(root, relative, sha256, label) {
|
|
2573
|
+
const bytes = fs3.readFileSync(proofBoundPath(root, relative));
|
|
2574
|
+
if (proofByteHash(bytes) !== sha256)
|
|
2575
|
+
throw new Error(`${label} does not match its recorded hash`);
|
|
2576
|
+
return bytes.toString("utf8");
|
|
2577
|
+
}
|
|
2578
|
+
module2.exports.boundText = boundText;
|
|
1982
2579
|
function authenticateDiscreteFrameAsset(root, declaration, calibrate = false) {
|
|
1983
2580
|
const cadence = parseDiscreteFrameCadence(declaration);
|
|
1984
2581
|
const fixture = fs3.readFileSync(proofBoundPath(root, cadence.asset.fixturePath));
|
|
@@ -2179,6 +2776,10 @@ var require_attempt_events = __commonJS({
|
|
|
2179
2776
|
};
|
|
2180
2777
|
}
|
|
2181
2778
|
function authenticateNativeCadenceCensus(root, sourceRoot, life, decoded) {
|
|
2779
|
+
const {
|
|
2780
|
+
authenticateNativeRefreshSamples
|
|
2781
|
+
} = require_native_frame_receipt();
|
|
2782
|
+
authenticateNativeRefreshSamples(root, sourceRoot, life);
|
|
2182
2783
|
const native = life.nativeFramebuffer;
|
|
2183
2784
|
if (!native || native.exitCode !== 0 || native.failure || native.sourceSha256 !== proofByteHash(
|
|
2184
2785
|
fs3.readFileSync(
|
|
@@ -2186,19 +2787,54 @@ var require_attempt_events = __commonJS({
|
|
|
2186
2787
|
)
|
|
2187
2788
|
))
|
|
2188
2789
|
throw new Error("native cadence has no source-matched framebuffer consumer");
|
|
2189
|
-
const frames =
|
|
2190
|
-
|
|
2790
|
+
const frames = boundText(
|
|
2791
|
+
root,
|
|
2792
|
+
native.receipt,
|
|
2793
|
+
native.receiptSha256,
|
|
2794
|
+
"native cadence source receipt"
|
|
2795
|
+
).trim().split("\n").map(JSON.parse);
|
|
2796
|
+
const media = boundText(
|
|
2797
|
+
root,
|
|
2798
|
+
native.mediaFrames,
|
|
2799
|
+
native.mediaFramesSha256,
|
|
2800
|
+
"native cadence sampled surface census"
|
|
2801
|
+
).trim().split("\n").map(JSON.parse);
|
|
2191
2802
|
if (frames.length !== decoded.observations.length || media.length !== frames.length)
|
|
2192
|
-
throw new Error("native cadence acquisition and
|
|
2803
|
+
throw new Error("native cadence acquisition and sampled surface census differ");
|
|
2193
2804
|
const {
|
|
2194
2805
|
decodeNativeFramebuffer,
|
|
2195
2806
|
requireNativeRawFrame
|
|
2196
2807
|
} = require_native_frame_receipt();
|
|
2197
2808
|
const rawRoot = path5.dirname(proofBoundPath(root, native.receipt));
|
|
2809
|
+
const superseded = new Set(
|
|
2810
|
+
boundText(
|
|
2811
|
+
root,
|
|
2812
|
+
native.superseded,
|
|
2813
|
+
native.supersededSha256,
|
|
2814
|
+
"native cadence superseded receipt"
|
|
2815
|
+
).split("\n").filter(Boolean).map((line) => {
|
|
2816
|
+
const row = JSON.parse(line);
|
|
2817
|
+
if (!Number.isSafeInteger(row.callbackIndex) || row.seedBeforeRead === row.seedAfterRead)
|
|
2818
|
+
throw new Error("native superseded receipt is not an overwritten read");
|
|
2819
|
+
return row.callbackIndex;
|
|
2820
|
+
})
|
|
2821
|
+
);
|
|
2822
|
+
const reads = boundText(
|
|
2823
|
+
root,
|
|
2824
|
+
native.reads,
|
|
2825
|
+
native.readsSha256,
|
|
2826
|
+
"native cadence observations"
|
|
2827
|
+
).split("\n").filter(Boolean).map(JSON.parse);
|
|
2828
|
+
let expectedCallback = 0;
|
|
2829
|
+
for (const read of reads) {
|
|
2830
|
+
while (superseded.delete(expectedCallback)) expectedCallback++;
|
|
2831
|
+
if (read.callbackIndex !== expectedCallback++)
|
|
2832
|
+
throw new Error("native cadence observations have invalid callback order");
|
|
2833
|
+
}
|
|
2198
2834
|
const origin = frames[0].consumerArrival;
|
|
2199
2835
|
for (const [index, frame] of frames.entries()) {
|
|
2200
2836
|
const clock = frame.consumerArrival;
|
|
2201
|
-
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)
|
|
2202
2838
|
throw new Error("native cadence source census has invalid clock or order");
|
|
2203
2839
|
const pts = (BigInt(clock.machTicks) - BigInt(origin.machTicks)) * BigInt(clock.timebaseNumer) / BigInt(clock.timebaseDenom);
|
|
2204
2840
|
if (pts < 0n || pts !== BigInt(media[index].ptsNanoseconds) || Math.abs(Number(pts) / 1e6 - decoded.observations[index].atMs) > 1e-3)
|
|
@@ -2211,6 +2847,9 @@ var require_attempt_events = __commonJS({
|
|
|
2211
2847
|
)
|
|
2212
2848
|
);
|
|
2213
2849
|
}
|
|
2850
|
+
while (superseded.delete(expectedCallback)) expectedCallback++;
|
|
2851
|
+
if (superseded.size)
|
|
2852
|
+
throw new Error("native superseded receipts fall outside the callback order");
|
|
2214
2853
|
}
|
|
2215
2854
|
function readDiscreteFrameReceipt(root, sourceRoot, reference, expected) {
|
|
2216
2855
|
if (expected.calibration !== true) assertNormalProofAttempt(root);
|
|
@@ -2444,10 +3083,41 @@ var require_attempt_events = __commonJS({
|
|
|
2444
3083
|
const life = JSON.parse(
|
|
2445
3084
|
fs3.readFileSync(proofBoundPath(root, receipt.lifecycle.path), "utf8")
|
|
2446
3085
|
);
|
|
3086
|
+
for (const [label, auxiliary] of [
|
|
3087
|
+
["ui frame clock", life.uiFrameClock],
|
|
3088
|
+
["delivered input", life.deliveredUIKitInput]
|
|
3089
|
+
]) {
|
|
3090
|
+
if (!auxiliary) continue;
|
|
3091
|
+
const bytes = fs3.readFileSync(proofBoundPath(root, auxiliary.sourceReceipt));
|
|
3092
|
+
if (proofByteHash(bytes) !== auxiliary.sourceReceiptSha256)
|
|
3093
|
+
throw new Error(`native ${label} receipt does not match its recorded hash`);
|
|
3094
|
+
paths.add(auxiliary.sourceReceipt);
|
|
3095
|
+
}
|
|
2447
3096
|
const native = life.nativeFramebuffer;
|
|
2448
3097
|
if (native) {
|
|
2449
3098
|
paths.add(native.receipt);
|
|
3099
|
+
paths.add(native.superseded);
|
|
2450
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
|
+
}
|
|
2451
3121
|
const rows = fs3.readFileSync(proofBoundPath(root, native.receipt), "utf8").trim().split("\n").map(JSON.parse);
|
|
2452
3122
|
for (const row of rows) {
|
|
2453
3123
|
const file = proofBoundPath(
|
|
@@ -6348,6 +7018,11 @@ function sameNativeAccessibilityShape(a, b, bounds) {
|
|
|
6348
7018
|
return true;
|
|
6349
7019
|
}
|
|
6350
7020
|
|
|
7021
|
+
// src/is-record.ts
|
|
7022
|
+
function isRecord(value) {
|
|
7023
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7024
|
+
}
|
|
7025
|
+
|
|
6351
7026
|
// cli/commands/inspect/core.ts
|
|
6352
7027
|
function layoutBoundFontFamily(style) {
|
|
6353
7028
|
const raw = typeof style === "object" && style !== null ? style.fontFamily : void 0;
|
|
@@ -7362,9 +8037,12 @@ var AUTOMATION_SIM_IDLE_REAP_TTL_MS = 5 * 6e4;
|
|
|
7362
8037
|
// src/home-paths.ts
|
|
7363
8038
|
var DAEMON_LOCKFILE_MAX_BYTES = 16 * 1024;
|
|
7364
8039
|
|
|
7365
|
-
// ../peach-engine/src/
|
|
8040
|
+
// ../peach-engine/src/preview/bundle-layout.ts
|
|
7366
8041
|
var import_metro_production_bundle = require("peachsim/metro-production-bundle");
|
|
7367
8042
|
|
|
8043
|
+
// ../peach-engine/src/tenant-bundle-mode.ts
|
|
8044
|
+
var import_metro_production_bundle2 = require("peachsim/metro-production-bundle");
|
|
8045
|
+
|
|
7368
8046
|
// ../peach-engine/src/tenant-runtime-environment.ts
|
|
7369
8047
|
function readBrowserLocation() {
|
|
7370
8048
|
try {
|
|
@@ -7843,7 +8521,7 @@ var PEACH_UPSTREAM_MODULES = {
|
|
|
7843
8521
|
};
|
|
7844
8522
|
|
|
7845
8523
|
// ../peach-engine/src/metro-fingerprint.ts
|
|
7846
|
-
var
|
|
8524
|
+
var import_metro_production_bundle3 = require("peachsim/metro-production-bundle");
|
|
7847
8525
|
var import_react_native_host_modules = require("peachsim/react-native-host-modules");
|
|
7848
8526
|
|
|
7849
8527
|
// src/cloud-contract.ts
|
|
@@ -10636,6 +11314,10 @@ function setPauseAwareTimeout(fn, budgetMs, hostPid) {
|
|
|
10636
11314
|
}
|
|
10637
11315
|
return () => {
|
|
10638
11316
|
timers.delete(timer);
|
|
11317
|
+
if (timers.size === 0 && poll) {
|
|
11318
|
+
clearInterval(poll);
|
|
11319
|
+
poll = void 0;
|
|
11320
|
+
}
|
|
10639
11321
|
};
|
|
10640
11322
|
}
|
|
10641
11323
|
|
|
@@ -10674,7 +11356,10 @@ var BRIDGE_VALUE_FLAGS_TABLE = [
|
|
|
10674
11356
|
{
|
|
10675
11357
|
names: ["--timeout"],
|
|
10676
11358
|
apply: (state, value) => {
|
|
10677
|
-
if (value !== void 0)
|
|
11359
|
+
if (value !== void 0) {
|
|
11360
|
+
state.commandTimeoutMs = Number(value);
|
|
11361
|
+
state.explicitTimeout = true;
|
|
11362
|
+
}
|
|
10678
11363
|
}
|
|
10679
11364
|
},
|
|
10680
11365
|
{
|
|
@@ -10814,7 +11499,7 @@ function createBridge(wsPort, opts = {}) {
|
|
|
10814
11499
|
return {
|
|
10815
11500
|
plane: "local",
|
|
10816
11501
|
async send(cmd, callOpts) {
|
|
10817
|
-
const effectiveTimeoutMs = callOpts?.timeoutMs ?? commandTimeoutMs;
|
|
11502
|
+
const effectiveTimeoutMs = callOpts?.timeoutMs ?? (opts.explicitTimeout ? commandTimeoutMs : callOpts?.defaultTimeoutMs) ?? commandTimeoutMs;
|
|
10818
11503
|
const sendOnce = async (defaultSimId, cmdOverride) => {
|
|
10819
11504
|
await ready;
|
|
10820
11505
|
if (terminalError) throw terminalError;
|
|
@@ -10827,11 +11512,7 @@ function createBridge(wsPort, opts = {}) {
|
|
|
10827
11512
|
const clearTimeoutFn = setPauseAwareTimeout(
|
|
10828
11513
|
() => {
|
|
10829
11514
|
pending.delete(id);
|
|
10830
|
-
reject(
|
|
10831
|
-
new Error(
|
|
10832
|
-
`command timed out after ${Math.round(effectiveTimeoutMs / 1e3)}s`
|
|
10833
|
-
)
|
|
10834
|
-
);
|
|
11515
|
+
reject(new Error(`command timed out after ${effectiveTimeoutMs / 1e3}s`));
|
|
10835
11516
|
},
|
|
10836
11517
|
effectiveTimeoutMs,
|
|
10837
11518
|
lineageBySimId.get(payload.simId)?.hostPid
|
|
@@ -11064,25 +11745,8 @@ function usesDirectLookup(matcher) {
|
|
|
11064
11745
|
return matcher.regexFlags === void 0 && matcher.index === void 0 && matcher.type !== "text";
|
|
11065
11746
|
}
|
|
11066
11747
|
|
|
11067
|
-
// ../peach-engine/src/
|
|
11068
|
-
|
|
11069
|
-
id: "ios",
|
|
11070
|
-
displayName: "iOS",
|
|
11071
|
-
isIOS: true,
|
|
11072
|
-
isAndroid: false,
|
|
11073
|
-
select: (registry) => registry.ios
|
|
11074
|
-
});
|
|
11075
|
-
var ANDROID_ADAPTER = Object.freeze({
|
|
11076
|
-
id: "android",
|
|
11077
|
-
displayName: "Android",
|
|
11078
|
-
isIOS: false,
|
|
11079
|
-
isAndroid: true,
|
|
11080
|
-
select: (registry) => registry.android
|
|
11081
|
-
});
|
|
11082
|
-
var RUNTIME_PLATFORM_ADAPTERS = Object.freeze({
|
|
11083
|
-
ios: IOS_ADAPTER,
|
|
11084
|
-
android: ANDROID_ADAPTER
|
|
11085
|
-
});
|
|
11748
|
+
// ../peach-engine/src/settings/device-spec-bus.ts
|
|
11749
|
+
init_adapter();
|
|
11086
11750
|
|
|
11087
11751
|
// ../peach-engine/src/settings/dynamic-type.ts
|
|
11088
11752
|
function textMetrics(fontSize, lineHeight, leading, ascent) {
|
|
@@ -12097,8 +12761,94 @@ async function dispatchTwoFingerGesture(page, startA, startB, endA, endB, option
|
|
|
12097
12761
|
await page.waitForTimeout(250);
|
|
12098
12762
|
}
|
|
12099
12763
|
|
|
12764
|
+
// ../contrast-runtime-delivery/src/version.ts
|
|
12765
|
+
function isValidRuntimeVersion(value) {
|
|
12766
|
+
return value.length <= 100 && /^[0-9A-Za-z][0-9A-Za-z._+-]*$/.test(value);
|
|
12767
|
+
}
|
|
12768
|
+
|
|
12769
|
+
// src/nativeViewSourceContract.ts
|
|
12770
|
+
function isNativeViewSource(value) {
|
|
12771
|
+
if (!isRecord(value)) return false;
|
|
12772
|
+
if (value.type === "placeholder")
|
|
12773
|
+
return (value.uri === void 0 || typeof value.uri === "string" && value.uri.length > 0) && (value.color === void 0 || typeof value.color === "string");
|
|
12774
|
+
if (value.type === "web") {
|
|
12775
|
+
if (typeof value.uri !== "string") return false;
|
|
12776
|
+
try {
|
|
12777
|
+
const url = new URL(value.uri);
|
|
12778
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
12779
|
+
} catch {
|
|
12780
|
+
return false;
|
|
12781
|
+
}
|
|
12782
|
+
}
|
|
12783
|
+
return value.type === "video" && typeof value.uri === "string" && value.uri.length > 0 && ["paused", "loop", "muted"].every(
|
|
12784
|
+
(key) => value[key] === void 0 || typeof value[key] === "boolean"
|
|
12785
|
+
) && (value.currentTime === void 0 || typeof value.currentTime === "number" && Number.isFinite(value.currentTime) && value.currentTime >= 0) && (value.resizeMode === void 0 || value.resizeMode === "contain" || value.resizeMode === "cover" || value.resizeMode === "stretch");
|
|
12786
|
+
}
|
|
12787
|
+
|
|
12788
|
+
// src/config.ts
|
|
12789
|
+
function readNativeRenderer2(value) {
|
|
12790
|
+
if (value === void 0) return void 0;
|
|
12791
|
+
if (value === "paper" || value === "fabric") return value;
|
|
12792
|
+
throw new Error("nativeRenderer must be paper or fabric");
|
|
12793
|
+
}
|
|
12794
|
+
function isPackageStubs(value) {
|
|
12795
|
+
if (value === "noop") return true;
|
|
12796
|
+
if (!isRecord(value)) return false;
|
|
12797
|
+
return (value.modules === void 0 || isRecord(value.modules) && Object.values(value.modules).every(
|
|
12798
|
+
(stub) => typeof stub === "string" && stub.length > 0
|
|
12799
|
+
)) && (value.views === void 0 || isRecord(value.views) && Object.values(value.views).every(
|
|
12800
|
+
(stub) => typeof stub === "string" && stub.length > 0 || isNativeViewSource(stub)
|
|
12801
|
+
));
|
|
12802
|
+
}
|
|
12803
|
+
var PEACH_CONFIG_QUERY_PARAM = "peachConfig";
|
|
12804
|
+
function isPeachConfig(value) {
|
|
12805
|
+
if (!isRecord(value)) return false;
|
|
12806
|
+
return (value.nativeRenderer === void 0 || value.nativeRenderer === "paper" || value.nativeRenderer === "fabric") && (value.mapKit === void 0 || isRecord(value.mapKit) && typeof value.mapKit.token === "string" && value.mapKit.token.length > 0 && value.mapKit.token === value.mapKit.token.trim()) && (value.runtimeVersion === void 0 || typeof value.runtimeVersion === "string" && value.runtimeVersion === value.runtimeVersion.trim() && isValidRuntimeVersion(value.runtimeVersion)) && [
|
|
12807
|
+
"modules",
|
|
12808
|
+
"packages",
|
|
12809
|
+
"turboModules",
|
|
12810
|
+
"nativeModules",
|
|
12811
|
+
"nativeViews",
|
|
12812
|
+
"env",
|
|
12813
|
+
"network",
|
|
12814
|
+
"settings",
|
|
12815
|
+
"initialProps",
|
|
12816
|
+
"initialState"
|
|
12817
|
+
].every((key) => value[key] === void 0 || isRecord(value[key])) && (value.packages === void 0 || isRecord(value.packages) && Object.values(value.packages).every(isPackageStubs)) && (value.nativeViews === void 0 || isRecord(value.nativeViews) && Object.values(value.nativeViews).every(
|
|
12818
|
+
(entry) => entry === "noop" || isNativeViewSource(entry) || isRecord(entry) && typeof entry.file === "string" && entry.file.length > 0
|
|
12819
|
+
));
|
|
12820
|
+
}
|
|
12821
|
+
function hasOwnKeys(value) {
|
|
12822
|
+
return !!value && Object.keys(value).length > 0;
|
|
12823
|
+
}
|
|
12824
|
+
function hasPeachConfig(config) {
|
|
12825
|
+
if (!config) return false;
|
|
12826
|
+
return config.nativeRenderer !== void 0 || config.mapKit !== void 0 || typeof config.runtimeVersion === "string" && config.runtimeVersion.length > 0 || hasOwnKeys(config.modules) || hasOwnKeys(config.turboModules) || hasOwnKeys(config.nativeModules) || hasOwnKeys(config.nativeViews) || hasOwnKeys(config.env) || hasOwnKeys(config.network?.remap) || hasOwnKeys(config.settings) || hasOwnKeys(config.initialProps) || hasOwnKeys(config.initialState);
|
|
12827
|
+
}
|
|
12828
|
+
function readPeachConfigFromSearchParams(searchParams) {
|
|
12829
|
+
const raw = searchParams.get(PEACH_CONFIG_QUERY_PARAM);
|
|
12830
|
+
if (!raw) return void 0;
|
|
12831
|
+
let parsed;
|
|
12832
|
+
try {
|
|
12833
|
+
parsed = JSON.parse(raw);
|
|
12834
|
+
} catch (error) {
|
|
12835
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
12836
|
+
console.warn(`[peach] invalid ${PEACH_CONFIG_QUERY_PARAM}: ${message}`);
|
|
12837
|
+
return void 0;
|
|
12838
|
+
}
|
|
12839
|
+
if (isRecord(parsed)) readNativeRenderer2(parsed.nativeRenderer);
|
|
12840
|
+
return isPeachConfig(parsed) && hasPeachConfig(parsed) ? parsed : void 0;
|
|
12841
|
+
}
|
|
12842
|
+
|
|
12100
12843
|
// detox/navigation-lifecycle.ts
|
|
12101
12844
|
var import_attempt_events = __toESM(require_attempt_events(), 1);
|
|
12845
|
+
var developerToken = readPeachConfigFromSearchParams(
|
|
12846
|
+
new URL(process.env.PEACH_URL || "http://localhost").searchParams
|
|
12847
|
+
)?.mapKit?.token;
|
|
12848
|
+
function redactDiagnosticText(text) {
|
|
12849
|
+
const withoutConfig = text.replace(/([?&]peachConfig=)[^&\s"'<>]+/g, "$1[redacted]");
|
|
12850
|
+
return developerToken ? withoutConfig.replaceAll(developerToken, "[redacted]") : withoutConfig;
|
|
12851
|
+
}
|
|
12102
12852
|
var DIAGNOSTIC_LIMIT = 40;
|
|
12103
12853
|
var pageDiagnostics = /* @__PURE__ */ new WeakMap();
|
|
12104
12854
|
var activeNavigations = /* @__PURE__ */ new WeakMap();
|
|
@@ -12111,6 +12861,21 @@ function persistDiagnostic(diagnostic) {
|
|
|
12111
12861
|
(0, import_attempt_events.writeAttemptEvent)(diagnostic);
|
|
12112
12862
|
}
|
|
12113
12863
|
function recordPageDiagnostic(page, diagnostic, persist = false) {
|
|
12864
|
+
if ("url" in diagnostic) diagnostic.url = redactDiagnosticText(diagnostic.url);
|
|
12865
|
+
if ("navigationUrl" in diagnostic)
|
|
12866
|
+
diagnostic.navigationUrl = redactDiagnosticText(diagnostic.navigationUrl);
|
|
12867
|
+
if ("message" in diagnostic)
|
|
12868
|
+
diagnostic.message = redactDiagnosticText(diagnostic.message);
|
|
12869
|
+
if ("errorText" in diagnostic && diagnostic.errorText)
|
|
12870
|
+
diagnostic.errorText = redactDiagnosticText(diagnostic.errorText);
|
|
12871
|
+
if ("pageState" in diagnostic && diagnostic.pageState) {
|
|
12872
|
+
if ("url" in diagnostic.pageState)
|
|
12873
|
+
diagnostic.pageState.url = redactDiagnosticText(diagnostic.pageState.url);
|
|
12874
|
+
if ("evaluateError" in diagnostic.pageState)
|
|
12875
|
+
diagnostic.pageState.evaluateError = redactDiagnosticText(
|
|
12876
|
+
diagnostic.pageState.evaluateError
|
|
12877
|
+
);
|
|
12878
|
+
}
|
|
12114
12879
|
const entries = pageDiagnostics.get(page);
|
|
12115
12880
|
if (!entries) return;
|
|
12116
12881
|
entries.push(diagnostic);
|
|
@@ -12217,7 +12982,7 @@ function attachPageDiagnostics(page) {
|
|
|
12217
12982
|
}
|
|
12218
12983
|
async function readSootsimPageState(page) {
|
|
12219
12984
|
try {
|
|
12220
|
-
|
|
12985
|
+
const state = await page.evaluate(() => {
|
|
12221
12986
|
const bootMarks = Reflect.get(window, "__sootsimBootMarks");
|
|
12222
12987
|
return {
|
|
12223
12988
|
bodyText: document.body?.innerText?.slice(0, 500) ?? "",
|
|
@@ -12234,6 +12999,8 @@ async function readSootsimPageState(page) {
|
|
|
12234
12999
|
url: window.location.href
|
|
12235
13000
|
};
|
|
12236
13001
|
});
|
|
13002
|
+
state.url = redactDiagnosticText(state.url);
|
|
13003
|
+
return state;
|
|
12237
13004
|
} catch (error) {
|
|
12238
13005
|
return {
|
|
12239
13006
|
evaluateError: error instanceof Error ? error.message : String(error)
|
|
@@ -12258,14 +13025,14 @@ async function describeSootsimBridgeFailure(page, cause, summary = "timed out wa
|
|
|
12258
13025
|
`page: ${JSON.stringify(state)}`,
|
|
12259
13026
|
`recent page diagnostics:
|
|
12260
13027
|
${JSON.stringify(getSootsimNavigationDiagnostics(page))}`
|
|
12261
|
-
].join("\n")
|
|
13028
|
+
].map(redactDiagnosticText).join("\n")
|
|
12262
13029
|
);
|
|
12263
13030
|
}
|
|
12264
13031
|
async function navigateSootsimPage(page, url, identity) {
|
|
12265
13032
|
attachPageDiagnostics(page);
|
|
12266
13033
|
const navigation = {
|
|
12267
13034
|
...identity,
|
|
12268
|
-
navigationUrl: url,
|
|
13035
|
+
navigationUrl: redactDiagnosticText(url),
|
|
12269
13036
|
navigationId: ++nextNavigationIdentity
|
|
12270
13037
|
};
|
|
12271
13038
|
activeNavigations.set(page, navigation);
|
|
@@ -12318,12 +13085,13 @@ function visibleFraction(node) {
|
|
|
12318
13085
|
function isNodeVisible(node) {
|
|
12319
13086
|
return visibleFraction(node) >= DEFAULT_VISIBILITY_THRESHOLD;
|
|
12320
13087
|
}
|
|
12321
|
-
function focusIsWithin(focused,
|
|
12322
|
-
|
|
13088
|
+
function focusIsWithin(focused, node) {
|
|
13089
|
+
const nativeNodeId = node.nativeTag ?? node.nodeId;
|
|
13090
|
+
return focused.nodeId === nativeNodeId || focused.ancestorNodeIds.includes(nativeNodeId);
|
|
12323
13091
|
}
|
|
12324
13092
|
async function isTextInputNodeFocused(node, page) {
|
|
12325
13093
|
const focused = await page.evaluate(async () => window.__sootsimTest.getFocusedNode());
|
|
12326
|
-
return focused !== null && focusIsWithin(focused, node
|
|
13094
|
+
return focused !== null && focusIsWithin(focused, node);
|
|
12327
13095
|
}
|
|
12328
13096
|
async function isNodeFocused(node, page) {
|
|
12329
13097
|
if (node.isTextInput) return isTextInputNodeFocused(node, page);
|
|
@@ -12766,6 +13534,7 @@ var by = {
|
|
|
12766
13534
|
// detox/index.ts
|
|
12767
13535
|
var import_motion_change = __toESM(require_motion_change(), 1);
|
|
12768
13536
|
var import_proof_frame = __toESM(require_proof_frame(), 1);
|
|
13537
|
+
var WORKLET_FRAME_TRACE_LIMIT = 16384;
|
|
12769
13538
|
var BASE_URL = process.env.PEACH_URL || "http://localhost:5173";
|
|
12770
13539
|
var SCREENSHOT_DIR = process.env.PEACH_SCREENSHOT_DIR || path4.join(process.cwd(), "test", "detox-driver", "screenshots", String(process.pid));
|
|
12771
13540
|
var _browser = null;
|
|
@@ -12773,6 +13542,7 @@ var _browserLaunch = null;
|
|
|
12773
13542
|
var _browserLaunchAbort = null;
|
|
12774
13543
|
var _reapBrowserNow = null;
|
|
12775
13544
|
var _context = null;
|
|
13545
|
+
var _appStorageState;
|
|
12776
13546
|
var _page = null;
|
|
12777
13547
|
var _synchronizationEnabled = false;
|
|
12778
13548
|
var _proofFrameObservationActive = false;
|
|
@@ -12968,14 +13738,17 @@ async function waitForSootsimWorkletsIdle(page, timeout = 3e3) {
|
|
|
12968
13738
|
let readStartedAt = null;
|
|
12969
13739
|
let tenantAtRead = window.__sootsimRenderHost;
|
|
12970
13740
|
let shellAtRead = window.SootSim?.bridges?.shellHost;
|
|
13741
|
+
let peerProgress = null;
|
|
12971
13742
|
let deadlineTimerLateMs = null;
|
|
12972
13743
|
while (Date.now() < deadline) {
|
|
12973
13744
|
readStartedAt = Date.now();
|
|
12974
13745
|
tenantAtRead = window.__sootsimRenderHost;
|
|
12975
13746
|
shellAtRead = window.SootSim?.bridges?.shellHost;
|
|
12976
13747
|
let timer;
|
|
13748
|
+
const peerRead = getPeerStats(false);
|
|
13749
|
+
peerProgress = peerRead && "peachPeerProgress" in peerRead ? peerRead.peachPeerProgress ?? null : null;
|
|
12977
13750
|
const stats = await Promise.race([
|
|
12978
|
-
|
|
13751
|
+
peerRead,
|
|
12979
13752
|
new Promise((resolve3) => {
|
|
12980
13753
|
timer = setTimeout(
|
|
12981
13754
|
() => {
|
|
@@ -13012,6 +13785,7 @@ async function waitForSootsimWorkletsIdle(page, timeout = 3e3) {
|
|
|
13012
13785
|
outstandingRead: readStartedAt === null ? null : {
|
|
13013
13786
|
startedAtMs: readStartedAt - start,
|
|
13014
13787
|
elapsedMs: Date.now() - readStartedAt,
|
|
13788
|
+
peerProgress: peerProgress ? { ...peerProgress } : null,
|
|
13015
13789
|
testBridgeChanged: window.__sootsimTest !== testBridge,
|
|
13016
13790
|
tenant: {
|
|
13017
13791
|
presentAtRequest: !!tenantAtRead,
|
|
@@ -13037,8 +13811,12 @@ async function waitForSootsimWorkletsIdle(page, timeout = 3e3) {
|
|
|
13037
13811
|
);
|
|
13038
13812
|
if (!result?.settled) {
|
|
13039
13813
|
const pending = result?.pending ?? "no peer stats reply";
|
|
13040
|
-
throw
|
|
13041
|
-
|
|
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"
|
|
13042
13820
|
);
|
|
13043
13821
|
}
|
|
13044
13822
|
}
|
|
@@ -13092,7 +13870,8 @@ async function quiesceContextMedia(context) {
|
|
|
13092
13870
|
if (timer !== void 0) clearTimeout(timer);
|
|
13093
13871
|
}
|
|
13094
13872
|
}
|
|
13095
|
-
async function closeContext() {
|
|
13873
|
+
async function closeContext(preserveStorage) {
|
|
13874
|
+
if (!preserveStorage) _appStorageState = void 0;
|
|
13096
13875
|
if (_browserCompromised) {
|
|
13097
13876
|
throw new Error(`sootsim browser is compromised: ${_browserCompromised}`);
|
|
13098
13877
|
}
|
|
@@ -13100,6 +13879,13 @@ async function closeContext() {
|
|
|
13100
13879
|
const context = _context;
|
|
13101
13880
|
const closingContextId = contextIdentity;
|
|
13102
13881
|
try {
|
|
13882
|
+
if (preserveStorage) {
|
|
13883
|
+
_appStorageState = await withLifecycleTimeout(
|
|
13884
|
+
"context.storageState()",
|
|
13885
|
+
closingContextId,
|
|
13886
|
+
() => context.storageState({ indexedDB: true })
|
|
13887
|
+
);
|
|
13888
|
+
}
|
|
13103
13889
|
await quiesceContextMedia(context);
|
|
13104
13890
|
await withLifecycleTimeout("context.close()", closingContextId, () => context.close());
|
|
13105
13891
|
_context = null;
|
|
@@ -13113,7 +13899,8 @@ async function closeContext() {
|
|
|
13113
13899
|
throw error;
|
|
13114
13900
|
}
|
|
13115
13901
|
}
|
|
13116
|
-
async function closeBrowser() {
|
|
13902
|
+
async function closeBrowser(preserveStorage) {
|
|
13903
|
+
if (!preserveStorage) _appStorageState = void 0;
|
|
13117
13904
|
if (!_browser) return;
|
|
13118
13905
|
const browser = _browser;
|
|
13119
13906
|
const reapBrowserNow = _reapBrowserNow;
|
|
@@ -13122,7 +13909,7 @@ async function closeBrowser() {
|
|
|
13122
13909
|
browserIdentity = 0;
|
|
13123
13910
|
let closeError = null;
|
|
13124
13911
|
try {
|
|
13125
|
-
await closeContext();
|
|
13912
|
+
await closeContext(preserveStorage);
|
|
13126
13913
|
} catch (error) {
|
|
13127
13914
|
closeError = error instanceof Error ? error : new Error(String(error));
|
|
13128
13915
|
}
|
|
@@ -13358,7 +14145,7 @@ async function focusElementForTextEntry(el) {
|
|
|
13358
14145
|
await el.tap();
|
|
13359
14146
|
await pollUntil(
|
|
13360
14147
|
() => page.evaluate(async () => window.__sootsimTest.getFocusedNode()),
|
|
13361
|
-
(focused) => focused !== null && focusIsWithin(focused, node
|
|
14148
|
+
(focused) => focused !== null && focusIsWithin(focused, node),
|
|
13362
14149
|
{
|
|
13363
14150
|
timeoutMs: 5e3,
|
|
13364
14151
|
label: `SootSim text input ${node.nodeId} to focus`
|
|
@@ -13747,10 +14534,11 @@ function createSootSimElement(matcher) {
|
|
|
13747
14534
|
}
|
|
13748
14535
|
},
|
|
13749
14536
|
async swipe(direction, speed = "fast", percentage = 0.75, normalizedStartingPointX = 0.5, normalizedStartingPointY = 0.5) {
|
|
14537
|
+
const page = getPage();
|
|
14538
|
+
if (_synchronizationEnabled) await waitForSootsimIdle2(page, 3e3, false, 0);
|
|
13750
14539
|
const node = await findStableNodeByMatcher(matcher, "swipe");
|
|
13751
14540
|
if (!node)
|
|
13752
14541
|
throw new Error(`element not found for swipe: ${JSON.stringify(matcher)}`);
|
|
13753
|
-
const page = getPage();
|
|
13754
14542
|
const { absolutePosition, layout } = node;
|
|
13755
14543
|
const startSootSimX = absolutePosition.x + layout.width * normalizedStartingPointX;
|
|
13756
14544
|
const startSootSimY = absolutePosition.y + layout.height * normalizedStartingPointY;
|
|
@@ -14127,10 +14915,11 @@ var device = {
|
|
|
14127
14915
|
}
|
|
14128
14916
|
}
|
|
14129
14917
|
if (!_page || newInstance || opts?.delete || opts?.recordVideoDir) {
|
|
14130
|
-
await closeContext();
|
|
14918
|
+
await closeContext(!opts?.delete);
|
|
14131
14919
|
const contextOpts = {
|
|
14132
14920
|
hasTouch: true,
|
|
14133
|
-
viewport: { width: 500, height: 900 }
|
|
14921
|
+
viewport: { width: 500, height: 900 },
|
|
14922
|
+
storageState: _appStorageState
|
|
14134
14923
|
};
|
|
14135
14924
|
if (opts?.recordVideoDir) {
|
|
14136
14925
|
fs2.mkdirSync(opts.recordVideoDir, { recursive: true });
|
|
@@ -14235,11 +15024,12 @@ var device = {
|
|
|
14235
15024
|
await waitForSootsimWorkletsIdle(page, 3e3);
|
|
14236
15025
|
},
|
|
14237
15026
|
async terminateApp() {
|
|
14238
|
-
await closeBrowser();
|
|
15027
|
+
await closeBrowser(true);
|
|
14239
15028
|
},
|
|
14240
15029
|
async installApp() {
|
|
14241
15030
|
},
|
|
14242
15031
|
async uninstallApp() {
|
|
15032
|
+
await closeBrowser(false);
|
|
14243
15033
|
},
|
|
14244
15034
|
async openURL(url) {
|
|
14245
15035
|
const page = getPage();
|
|
@@ -14616,6 +15406,14 @@ var device = {
|
|
|
14616
15406
|
definition: import_motion_change.MOTION_CHANGE_DEFINITION,
|
|
14617
15407
|
installGlobal: true
|
|
14618
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
|
+
}
|
|
14619
15417
|
const capturePromise = page.evaluate(
|
|
14620
15418
|
async (input) => {
|
|
14621
15419
|
const captureWindow = window;
|
|
@@ -14707,68 +15505,83 @@ var device = {
|
|
|
14707
15505
|
if (input.hold && (!interact || typeof interact.touchDown !== "function")) {
|
|
14708
15506
|
throw new Error("peach interact bridge is not installed");
|
|
14709
15507
|
}
|
|
14710
|
-
const
|
|
14711
|
-
|
|
14712
|
-
|
|
14713
|
-
|
|
14714
|
-
|
|
14715
|
-
releaseReceipt: null,
|
|
14716
|
-
released: false,
|
|
14717
|
-
x: input.hold?.x ?? 0,
|
|
14718
|
-
y: input.hold?.y ?? 0
|
|
14719
|
-
};
|
|
14720
|
-
const holdEventFrameIndex = (afterMs) => (holdState.pressFrameIndex ?? 0) + controller.framesForOffset(afterMs);
|
|
14721
|
-
const nextHoldEventFrameIndex = () => {
|
|
14722
|
-
if (!input.hold || holdState.released || holdState.pressFrameIndex === null) {
|
|
14723
|
-
return null;
|
|
14724
|
-
}
|
|
14725
|
-
const move = moves[holdState.nextMove];
|
|
14726
|
-
return holdEventFrameIndex(move ? move.afterMs : input.hold.durationMs);
|
|
14727
|
-
};
|
|
14728
|
-
const advanceHold = async (receipt) => {
|
|
14729
|
-
if (!input.hold || !interact || holdState.released) return;
|
|
14730
|
-
if (holdState.pressFrameIndex === null) {
|
|
14731
|
-
holdState.pressFrameIndex = receipt.frameIndex - 1;
|
|
14732
|
-
}
|
|
14733
|
-
for (let move = moves[holdState.nextMove]; move && receipt.frameIndex >= holdEventFrameIndex(move.afterMs); move = moves[holdState.nextMove]) {
|
|
14734
|
-
holdState.nextMove++;
|
|
14735
|
-
holdState.x = move.x;
|
|
14736
|
-
holdState.y = move.y;
|
|
14737
|
-
await interact.touchMove(
|
|
14738
|
-
move.x,
|
|
14739
|
-
move.y,
|
|
14740
|
-
999,
|
|
14741
|
-
controller.initialFrame.virtualTimestampMs + move.afterMs
|
|
15508
|
+
const pairedPeerStats = async (reset = false) => {
|
|
15509
|
+
const stats = await captureWindow.SootSim?.bridges?.test?.getPeerStats?.(reset);
|
|
15510
|
+
if (!stats || !Number.isSafeInteger(stats.replayedFrameCallbacks))
|
|
15511
|
+
throw new Error(
|
|
15512
|
+
"paired worklets clock needs native frame delivery diagnostics"
|
|
14742
15513
|
);
|
|
14743
|
-
|
|
14744
|
-
if (receipt.frameIndex < holdEventFrameIndex(input.hold.durationMs)) return;
|
|
14745
|
-
holdState.released = true;
|
|
14746
|
-
holdState.releaseReceipt = receipt;
|
|
14747
|
-
await interact.touchUp(
|
|
14748
|
-
holdState.x,
|
|
14749
|
-
holdState.y,
|
|
14750
|
-
999,
|
|
14751
|
-
controller.initialFrame.virtualTimestampMs + input.hold.durationMs
|
|
14752
|
-
);
|
|
15514
|
+
return stats;
|
|
14753
15515
|
};
|
|
15516
|
+
if (input.workletFrameTimestampsSeconds) await pairedPeerStats(true);
|
|
15517
|
+
const controller = await lockstep.begin(60, {
|
|
15518
|
+
workletFrameTimestampsSeconds: input.workletFrameTimestampsSeconds
|
|
15519
|
+
});
|
|
15520
|
+
let heldInput = null;
|
|
15521
|
+
const releaseReceipt = () => controller.inputReceipts.find((receipt) => receipt.phase === "up");
|
|
14754
15522
|
const keptFrames = /* @__PURE__ */ new Map();
|
|
15523
|
+
const keepFrame = (receipt, bitmap) => {
|
|
15524
|
+
keptFrames.get(receipt.frameIndex)?.bitmap.close();
|
|
15525
|
+
keptFrames.set(receipt.frameIndex, { bitmap, receipt });
|
|
15526
|
+
};
|
|
14755
15527
|
try {
|
|
15528
|
+
if (input.anchorAtTouch && !input.hold) {
|
|
15529
|
+
throw new Error("peach touch anchoring needs a hold");
|
|
15530
|
+
}
|
|
14756
15531
|
const baselineBitmap = await captureBitmap();
|
|
14757
15532
|
const baseline = regionPixels(baselineBitmap);
|
|
14758
|
-
|
|
15533
|
+
if (input.anchorAtTouch === "down")
|
|
15534
|
+
keepFrame(controller.initialFrame, baselineBitmap);
|
|
15535
|
+
else baselineBitmap.close();
|
|
15536
|
+
if (input.workletFrameTimestampsSeconds && (await pairedPeerStats()).replayedFrameCallbacks > 0)
|
|
15537
|
+
throw new Error("paired worklets callback preceded the Peach trigger");
|
|
14759
15538
|
state.ready = true;
|
|
14760
15539
|
if (input.hold && interact) {
|
|
14761
|
-
const
|
|
14762
|
-
|
|
14763
|
-
|
|
14764
|
-
|
|
14765
|
-
|
|
14766
|
-
|
|
14767
|
-
|
|
14768
|
-
|
|
14769
|
-
|
|
14770
|
-
|
|
14771
|
-
|
|
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();
|
|
14772
15585
|
} else {
|
|
14773
15586
|
while (!state.triggerStarted) {
|
|
14774
15587
|
if (state.cancelled) throw new Error("peach motion capture was cancelled");
|
|
@@ -14793,7 +15606,7 @@ var device = {
|
|
|
14793
15606
|
if (measurement.passedMinimum) {
|
|
14794
15607
|
visibleStart = receipt;
|
|
14795
15608
|
detectedMeasurement = measurement;
|
|
14796
|
-
|
|
15609
|
+
keepFrame(visibleStart, candidateBitmap);
|
|
14797
15610
|
} else {
|
|
14798
15611
|
candidateBitmap.close();
|
|
14799
15612
|
}
|
|
@@ -14802,10 +15615,9 @@ var device = {
|
|
|
14802
15615
|
for (let frame = 0; !visibleStart && frame < detectionFrameLimit; frame++) {
|
|
14803
15616
|
if (state.cancelled) throw new Error("peach motion capture was cancelled");
|
|
14804
15617
|
const receipt = await controller.stepFrame();
|
|
14805
|
-
await advanceHold(receipt);
|
|
14806
15618
|
const candidateBitmap = await captureBitmap();
|
|
14807
|
-
if (input.
|
|
14808
|
-
|
|
15619
|
+
if (input.anchorAtTouch === "down" || input.anchorAtTouch === "up" && releaseReceipt()?.frameIndex === receipt.frameIndex) {
|
|
15620
|
+
keepFrame(receipt, await createImageBitmap(candidateBitmap));
|
|
14809
15621
|
}
|
|
14810
15622
|
const bounds = boundsFor(candidateBitmap);
|
|
14811
15623
|
const measurement = measureChange({
|
|
@@ -14821,8 +15633,7 @@ var device = {
|
|
|
14821
15633
|
previousBitmap = null;
|
|
14822
15634
|
visibleStart = receipt;
|
|
14823
15635
|
detectedMeasurement = measurement;
|
|
14824
|
-
|
|
14825
|
-
keptFrames.set(receipt.frameIndex, candidateBitmap);
|
|
15636
|
+
keepFrame(receipt, candidateBitmap);
|
|
14826
15637
|
break;
|
|
14827
15638
|
}
|
|
14828
15639
|
previousBitmap?.close();
|
|
@@ -14836,23 +15647,19 @@ var device = {
|
|
|
14836
15647
|
}
|
|
14837
15648
|
let driveStart = visibleStart;
|
|
14838
15649
|
let currentReceipt = visibleStart;
|
|
14839
|
-
if (input.
|
|
14840
|
-
|
|
14841
|
-
|
|
14842
|
-
}
|
|
14843
|
-
while (!holdState.released) {
|
|
15650
|
+
if (input.anchorAtTouch === "down") driveStart = controller.initialFrame;
|
|
15651
|
+
if (input.anchorAtTouch === "up") {
|
|
15652
|
+
while (!releaseReceipt()) {
|
|
14844
15653
|
if (state.cancelled) throw new Error("peach motion capture was cancelled");
|
|
14845
15654
|
const receipt = await controller.stepFrame();
|
|
14846
|
-
await advanceHold(receipt);
|
|
14847
15655
|
currentReceipt = receipt;
|
|
14848
|
-
if (
|
|
14849
|
-
keptFrames.set(receipt.frameIndex, await captureBitmap());
|
|
14850
|
-
}
|
|
15656
|
+
if (releaseReceipt()) keepFrame(receipt, await captureBitmap());
|
|
14851
15657
|
}
|
|
14852
|
-
|
|
15658
|
+
const released = releaseReceipt();
|
|
15659
|
+
if (!released) {
|
|
14853
15660
|
throw new Error("peach release anchoring found no release frame");
|
|
14854
15661
|
}
|
|
14855
|
-
driveStart =
|
|
15662
|
+
driveStart = { ...released, tenantFrame: null };
|
|
14856
15663
|
}
|
|
14857
15664
|
const resolvedAnchors = /* @__PURE__ */ new Map();
|
|
14858
15665
|
const anchorRecords = [];
|
|
@@ -14877,7 +15684,6 @@ var device = {
|
|
|
14877
15684
|
while (searchReceipt.virtualTimestampMs < searchStartVts) {
|
|
14878
15685
|
if (state.cancelled) throw new Error("peach motion capture was cancelled");
|
|
14879
15686
|
searchReceipt = await controller.stepFrame();
|
|
14880
|
-
await advanceHold(searchReceipt);
|
|
14881
15687
|
}
|
|
14882
15688
|
currentReceipt = searchReceipt;
|
|
14883
15689
|
let anchorBitmap = await captureBitmap();
|
|
@@ -14889,7 +15695,6 @@ var device = {
|
|
|
14889
15695
|
for (let frame = 0; frame < detectionFrameLimit; frame++) {
|
|
14890
15696
|
if (state.cancelled) throw new Error("peach motion capture was cancelled");
|
|
14891
15697
|
const receipt = await controller.stepFrame();
|
|
14892
|
-
await advanceHold(receipt);
|
|
14893
15698
|
const candidateBitmap = await captureBitmap();
|
|
14894
15699
|
const bounds = boundsFor(candidateBitmap);
|
|
14895
15700
|
const candidate = regionPixels(candidateBitmap);
|
|
@@ -14926,8 +15731,7 @@ var device = {
|
|
|
14926
15731
|
`peach motion anchor ${anchor.id} found no onset past ${onsetThresholdPercent}% within ${input.detectionTimeoutMs}ms of ${anchor.notBeforeMs}ms (pan peak ${panPeakPercent.toFixed(2)}%, later peak ${latePeakPercent.toFixed(2)}%): the gate or the search start is wrong for this surface`
|
|
14927
15732
|
);
|
|
14928
15733
|
}
|
|
14929
|
-
|
|
14930
|
-
keptFrames.set(anchorReceipt.frameIndex, anchorBitmap);
|
|
15734
|
+
keepFrame(anchorReceipt, anchorBitmap);
|
|
14931
15735
|
resolvedAnchors.set(anchor.id, anchorReceipt);
|
|
14932
15736
|
anchorRecords.push({
|
|
14933
15737
|
changedPixelPercent: onsetMeasurement.changePercent,
|
|
@@ -14946,21 +15750,17 @@ var device = {
|
|
|
14946
15750
|
const kept = keptFrames.get(stageFrameIndex);
|
|
14947
15751
|
if (kept && stageFrameIndex <= currentReceipt.frameIndex) {
|
|
14948
15752
|
snapshots.push({
|
|
14949
|
-
actualMs:
|
|
15753
|
+
actualMs: kept.receipt.virtualTimestampMs - anchorReceipt.virtualTimestampMs,
|
|
14950
15754
|
anchorId,
|
|
14951
|
-
bitmap: await createImageBitmap(kept),
|
|
14952
|
-
frameIndex:
|
|
15755
|
+
bitmap: await createImageBitmap(kept.bitmap),
|
|
15756
|
+
frameIndex: kept.receipt.frameIndex,
|
|
14953
15757
|
label: stage.label,
|
|
14954
15758
|
offsetMs: stage.offsetMs,
|
|
14955
|
-
virtualTimestampMs:
|
|
15759
|
+
virtualTimestampMs: kept.receipt.virtualTimestampMs
|
|
14956
15760
|
});
|
|
14957
15761
|
continue;
|
|
14958
15762
|
}
|
|
14959
|
-
for (let eventFrameIndex = nextHoldEventFrameIndex(); eventFrameIndex !== null && stageFrameIndex > eventFrameIndex; eventFrameIndex = nextHoldEventFrameIndex()) {
|
|
14960
|
-
await advanceHold(await controller.stepFrame());
|
|
14961
|
-
}
|
|
14962
15763
|
const receipt = await controller.stepToOffset(anchorReceipt, stage.offsetMs);
|
|
14963
|
-
await advanceHold(receipt);
|
|
14964
15764
|
currentReceipt = receipt;
|
|
14965
15765
|
snapshots.push({
|
|
14966
15766
|
actualMs: receipt.virtualTimestampMs - anchorReceipt.virtualTimestampMs,
|
|
@@ -14972,12 +15772,13 @@ var device = {
|
|
|
14972
15772
|
virtualTimestampMs: receipt.virtualTimestampMs
|
|
14973
15773
|
});
|
|
14974
15774
|
}
|
|
15775
|
+
if (input.workletFrameTimestampsSeconds) controller.endWorkletFrameReceipt();
|
|
14975
15776
|
let releasedOffsetMs = 0;
|
|
14976
|
-
while (
|
|
15777
|
+
while (input.hold && !releaseReceipt()) {
|
|
14977
15778
|
const receipt = await controller.stepFrame();
|
|
14978
|
-
await advanceHold(receipt);
|
|
14979
15779
|
releasedOffsetMs = receipt.virtualTimestampMs - driveStart.virtualTimestampMs;
|
|
14980
15780
|
}
|
|
15781
|
+
await heldInput;
|
|
14981
15782
|
if (input.settleMs > 0) {
|
|
14982
15783
|
const maximumPrimaryMs = snapshots.reduce(
|
|
14983
15784
|
(maximum, snapshot2) => Math.max(
|
|
@@ -14993,6 +15794,17 @@ var device = {
|
|
|
14993
15794
|
await controller.stepFrame();
|
|
14994
15795
|
await new Promise((resolve3) => setTimeout(resolve3, 0));
|
|
14995
15796
|
}
|
|
15797
|
+
if (input.workletFrameTimestampsSeconds) {
|
|
15798
|
+
const stats = await pairedPeerStats();
|
|
15799
|
+
const first = stats.nativeFrameFirstTimestampMs;
|
|
15800
|
+
const firstFrameIndex = first === null ? null : Math.round(
|
|
15801
|
+
(first - controller.initialFrame.virtualTimestampMs) / (1e3 / 60)
|
|
15802
|
+
);
|
|
15803
|
+
if (firstFrameIndex !== 1)
|
|
15804
|
+
throw new Error(
|
|
15805
|
+
`paired worklets first delivery did not match the next UI frame: ${firstFrameIndex}`
|
|
15806
|
+
);
|
|
15807
|
+
}
|
|
14996
15808
|
return {
|
|
14997
15809
|
inputReceipts: controller.inputReceipts,
|
|
14998
15810
|
anchors: anchorRecords,
|
|
@@ -15013,15 +15825,16 @@ var device = {
|
|
|
15013
15825
|
}
|
|
15014
15826
|
};
|
|
15015
15827
|
} finally {
|
|
15016
|
-
for (const
|
|
15828
|
+
for (const kept of keptFrames.values()) kept.bitmap.close();
|
|
15017
15829
|
controller.end();
|
|
15018
15830
|
delete registry[input.token];
|
|
15019
15831
|
}
|
|
15020
15832
|
},
|
|
15021
15833
|
{
|
|
15834
|
+
workletFrameTimestampsSeconds: options.workletFrameTimestampsSeconds,
|
|
15022
15835
|
detectionTimeoutMs: options.detectionTimeoutMs ?? 3e3,
|
|
15023
15836
|
frame: options.frame ?? null,
|
|
15024
|
-
|
|
15837
|
+
anchorAtTouch: options.anchorAtTouch ?? null,
|
|
15025
15838
|
hold: options.hold ?? null,
|
|
15026
15839
|
motionChangeDefinition: import_motion_change.MOTION_CHANGE_DEFINITION,
|
|
15027
15840
|
platform: device.getPlatform(),
|
|
@@ -15031,17 +15844,24 @@ var device = {
|
|
|
15031
15844
|
token
|
|
15032
15845
|
}
|
|
15033
15846
|
);
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
const registry = window.__sootsimVisibleChangeCaptures;
|
|
15037
|
-
return registry?.[captureToken]?.ready === true;
|
|
15038
|
-
},
|
|
15039
|
-
token,
|
|
15040
|
-
{ timeout: 5e3 }
|
|
15041
|
-
);
|
|
15042
|
-
const triggerStartMs = Date.now();
|
|
15847
|
+
let captureFailure;
|
|
15848
|
+
let triggerStartMs = 0;
|
|
15043
15849
|
let triggerCompletedMs = 0;
|
|
15044
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();
|
|
15045
15865
|
const triggerPromise = (async () => {
|
|
15046
15866
|
if (!options.hold) {
|
|
15047
15867
|
if (typeof options.trigger !== "function") {
|
|
@@ -15077,6 +15897,7 @@ var device = {
|
|
|
15077
15897
|
}
|
|
15078
15898
|
};
|
|
15079
15899
|
} catch (error) {
|
|
15900
|
+
captureFailure = { error };
|
|
15080
15901
|
await page.evaluate((captureToken) => {
|
|
15081
15902
|
const registry = window.__sootsimVisibleChangeCaptures;
|
|
15082
15903
|
const capture = registry?.[captureToken];
|
|
@@ -15086,6 +15907,55 @@ var device = {
|
|
|
15086
15907
|
await capturePromise.catch(() => {
|
|
15087
15908
|
});
|
|
15088
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
|
+
}
|
|
15089
15959
|
}
|
|
15090
15960
|
},
|
|
15091
15961
|
// FAST multi-stage capture for sub-second animation diffs. each stage
|
|
@@ -15483,7 +16353,7 @@ async function cleanup() {
|
|
|
15483
16353
|
_browserLaunchAbort?.abort(new Error("Peach driver cleaned up"));
|
|
15484
16354
|
await _browserLaunch?.catch(() => {
|
|
15485
16355
|
});
|
|
15486
|
-
await closeBrowser();
|
|
16356
|
+
await closeBrowser(false);
|
|
15487
16357
|
}
|
|
15488
16358
|
// Annotate the CommonJS export names for ESM import in node:
|
|
15489
16359
|
0 && (module.exports = {
|