@sekyro/web 0.0.0-stage → 0.1.0-rc.2
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/CHANGELOG.md +17 -0
- package/LICENSE +20 -0
- package/README.md +92 -2
- package/dist/capture-constraints-BujkZcfb.d.cts +21 -0
- package/dist/capture-constraints-BujkZcfb.d.ts +21 -0
- package/dist/capture-session-v3-BJ8ciaWl.d.ts +457 -0
- package/dist/capture-session-v3-CAoWM8iT.d.cts +457 -0
- package/dist/chunk-BPYVZUJA.js +852 -0
- package/dist/chunk-BPYVZUJA.js.map +1 -0
- package/dist/chunk-LOTAJDRE.js +1094 -0
- package/dist/chunk-LOTAJDRE.js.map +1 -0
- package/dist/chunk-MWNFSNMP.js +52 -0
- package/dist/chunk-MWNFSNMP.js.map +1 -0
- package/dist/chunk-NILNKLK6.js +50 -0
- package/dist/chunk-NILNKLK6.js.map +1 -0
- package/dist/chunk-NWF46H7K.js +359 -0
- package/dist/chunk-NWF46H7K.js.map +1 -0
- package/dist/chunk-WY6U7AIK.js +193 -0
- package/dist/chunk-WY6U7AIK.js.map +1 -0
- package/dist/chunk-YVFF4Q4G.js +327 -0
- package/dist/chunk-YVFF4Q4G.js.map +1 -0
- package/dist/client/index.cjs +357 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +39 -0
- package/dist/client/index.d.ts +39 -0
- package/dist/client/index.js +15 -0
- package/dist/client/index.js.map +1 -0
- package/dist/core/index.cjs +1605 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +87 -0
- package/dist/core/index.d.ts +87 -0
- package/dist/core/index.js +139 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/v3/detect-worker.cjs +392 -0
- package/dist/core/v3/detect-worker.cjs.map +1 -0
- package/dist/core/v3/detect-worker.d.cts +1 -0
- package/dist/core/v3/detect-worker.d.ts +1 -0
- package/dist/core/v3/detect-worker.js +13 -0
- package/dist/core/v3/detect-worker.js.map +1 -0
- package/dist/create-verification-CjLnJ0w6.d.ts +348 -0
- package/dist/create-verification-EnZ13uY4.d.cts +348 -0
- package/dist/detect-worker-CKXHlXx9.d.cts +105 -0
- package/dist/detect-worker-CKXHlXx9.d.ts +105 -0
- package/dist/detector-scanic-4C6IEW2K.js +787 -0
- package/dist/detector-scanic-4C6IEW2K.js.map +1 -0
- package/dist/hosted-client-BpvBqu9f.d.cts +408 -0
- package/dist/hosted-client-BpvBqu9f.d.ts +408 -0
- package/dist/index.cjs +2636 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/index.cjs +4166 -0
- package/dist/internal/index.cjs.map +1 -0
- package/dist/internal/index.d.cts +431 -0
- package/dist/internal/index.d.ts +431 -0
- package/dist/internal/index.js +647 -0
- package/dist/internal/index.js.map +1 -0
- package/dist/popup/index.cjs +76 -0
- package/dist/popup/index.cjs.map +1 -0
- package/dist/popup/index.d.cts +66 -0
- package/dist/popup/index.d.ts +66 -0
- package/dist/popup/index.js +11 -0
- package/dist/popup/index.js.map +1 -0
- package/dist/scanic-ml-stub-S2OZ6HEF.js +10 -0
- package/dist/scanic-ml-stub-S2OZ6HEF.js.map +1 -0
- package/package.json +118 -4
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// src/core/v3/detector-blaze.ts
|
|
2
|
+
var BLAZE_INIT_TIMEOUT_MS = 1e4;
|
|
3
|
+
var BlazeDetector = class _BlazeDetector {
|
|
4
|
+
constructor(inner, fallbackReason) {
|
|
5
|
+
this.tier = "blaze";
|
|
6
|
+
this.inner = inner;
|
|
7
|
+
if (fallbackReason !== void 0) this.fallbackReason = fallbackReason;
|
|
8
|
+
}
|
|
9
|
+
static async create(assetBaseUrl, fallbackReason) {
|
|
10
|
+
const vision = await import("@mediapipe/tasks-vision");
|
|
11
|
+
const fileset = await vision.FilesetResolver.forVisionTasks(assetBaseUrl);
|
|
12
|
+
const inner = await vision.FaceDetector.createFromOptions(fileset, {
|
|
13
|
+
baseOptions: {
|
|
14
|
+
modelAssetPath: `${assetBaseUrl}/blaze_face_short_range.tflite`,
|
|
15
|
+
// CPU delegate — the iOS-Safari-safe path (see header). NEVER 'GPU'.
|
|
16
|
+
delegate: "CPU"
|
|
17
|
+
},
|
|
18
|
+
runningMode: "IMAGE"
|
|
19
|
+
});
|
|
20
|
+
return new _BlazeDetector(inner, fallbackReason);
|
|
21
|
+
}
|
|
22
|
+
async detect(source, sourceWidth, sourceHeight) {
|
|
23
|
+
let detections;
|
|
24
|
+
try {
|
|
25
|
+
detections = this.inner.detect(source).detections;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const best = pickMostConfident(detections);
|
|
30
|
+
if (best?.boundingBox === void 0) return null;
|
|
31
|
+
const size = intrinsicSize(source);
|
|
32
|
+
const eyes = extractEyes(best.keypoints, size);
|
|
33
|
+
const observation = {
|
|
34
|
+
box: {
|
|
35
|
+
x: best.boundingBox.originX,
|
|
36
|
+
y: best.boundingBox.originY,
|
|
37
|
+
width: best.boundingBox.width,
|
|
38
|
+
height: best.boundingBox.height
|
|
39
|
+
},
|
|
40
|
+
...eyes !== void 0 ? { eyes } : {},
|
|
41
|
+
score: best.categories[0]?.score ?? 0
|
|
42
|
+
};
|
|
43
|
+
return toStreamPixels(observation, source, sourceWidth, sourceHeight);
|
|
44
|
+
}
|
|
45
|
+
destroy() {
|
|
46
|
+
try {
|
|
47
|
+
this.inner.close();
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
function pickMostConfident(detections) {
|
|
53
|
+
let best;
|
|
54
|
+
let bestScore = -1;
|
|
55
|
+
for (const detection of detections) {
|
|
56
|
+
const score = detection.categories[0]?.score ?? 0;
|
|
57
|
+
if (score > bestScore) {
|
|
58
|
+
best = detection;
|
|
59
|
+
bestScore = score;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return best;
|
|
63
|
+
}
|
|
64
|
+
function extractEyes(keypoints, size) {
|
|
65
|
+
const a = keypoints?.[0];
|
|
66
|
+
const b = keypoints?.[1];
|
|
67
|
+
if (a === void 0 || b === void 0) return void 0;
|
|
68
|
+
const toSource = (p) => ({ x: p.x * size.width, y: p.y * size.height });
|
|
69
|
+
const [pa, pb] = [toSource(a), toSource(b)];
|
|
70
|
+
return pa.x <= pb.x ? { left: pa, right: pb } : { left: pb, right: pa };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/core/v3/detector-native.ts
|
|
74
|
+
function smokeSource() {
|
|
75
|
+
if (typeof OffscreenCanvas === "function") return new OffscreenCanvas(2, 2);
|
|
76
|
+
const canvas = document.createElement("canvas");
|
|
77
|
+
canvas.width = 2;
|
|
78
|
+
canvas.height = 2;
|
|
79
|
+
return canvas;
|
|
80
|
+
}
|
|
81
|
+
async function probeNativeDetector() {
|
|
82
|
+
const ctor = globalThis.FaceDetector;
|
|
83
|
+
if (typeof ctor !== "function") return { ok: false, reason: "unsupported" };
|
|
84
|
+
try {
|
|
85
|
+
const instance = new ctor({ fastMode: true, maxDetectedFaces: 1 });
|
|
86
|
+
await instance.detect(smokeSource());
|
|
87
|
+
return { ok: true, instance };
|
|
88
|
+
} catch (err) {
|
|
89
|
+
return { ok: false, reason: `smoke_failed:${err instanceof Error ? err.message : String(err)}` };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
var NativeDetector = class {
|
|
93
|
+
constructor(inner, fallbackReason) {
|
|
94
|
+
this.tier = "native";
|
|
95
|
+
this.inner = inner;
|
|
96
|
+
if (fallbackReason !== void 0) this.fallbackReason = fallbackReason;
|
|
97
|
+
}
|
|
98
|
+
async detect(source, sourceWidth, sourceHeight) {
|
|
99
|
+
let faces;
|
|
100
|
+
try {
|
|
101
|
+
faces = await this.inner.detect(source);
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const face = faces[0];
|
|
106
|
+
if (face === void 0) return null;
|
|
107
|
+
const eyes = extractEyes2(face.landmarks);
|
|
108
|
+
const observation = {
|
|
109
|
+
box: {
|
|
110
|
+
x: face.boundingBox.x,
|
|
111
|
+
y: face.boundingBox.y,
|
|
112
|
+
width: face.boundingBox.width,
|
|
113
|
+
height: face.boundingBox.height
|
|
114
|
+
},
|
|
115
|
+
...eyes !== void 0 ? { eyes } : {},
|
|
116
|
+
// The Shape Detection API exposes no confidence — the smoke-tested
|
|
117
|
+
// native tier treats any detection as confident.
|
|
118
|
+
score: 1
|
|
119
|
+
};
|
|
120
|
+
return toStreamPixels(observation, source, sourceWidth, sourceHeight);
|
|
121
|
+
}
|
|
122
|
+
destroy() {
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
function extractEyes2(landmarks) {
|
|
126
|
+
if (landmarks === void 0) return void 0;
|
|
127
|
+
const eyePoints = landmarks.filter((landmark) => landmark.type === "eye").map((landmark) => landmark.locations[0]).filter((point) => point !== void 0);
|
|
128
|
+
const [a, b] = eyePoints;
|
|
129
|
+
if (a === void 0 || b === void 0) return void 0;
|
|
130
|
+
return a.x <= b.x ? { left: a, right: b } : { left: b, right: a };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/core/v3/detector.ts
|
|
134
|
+
var DEFAULT_V3_ASSET_BASE_URL = "/models-v3";
|
|
135
|
+
var DEFAULT_TIER_ORDER = ["native", "blaze"];
|
|
136
|
+
var DetectorLadderError = class extends Error {
|
|
137
|
+
constructor(fallbackReason) {
|
|
138
|
+
super(`no detector tier available (${fallbackReason})`);
|
|
139
|
+
this.name = "DetectorLadderError";
|
|
140
|
+
this.fallbackReason = fallbackReason;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
function intrinsicSize(source) {
|
|
144
|
+
const candidate = source;
|
|
145
|
+
if (typeof candidate.videoWidth === "number" && candidate.videoWidth > 0) {
|
|
146
|
+
return { width: candidate.videoWidth, height: candidate.videoHeight ?? 0 };
|
|
147
|
+
}
|
|
148
|
+
if (typeof candidate.naturalWidth === "number" && candidate.naturalWidth > 0) {
|
|
149
|
+
return { width: candidate.naturalWidth, height: candidate.naturalHeight ?? 0 };
|
|
150
|
+
}
|
|
151
|
+
const width = typeof candidate.width === "number" ? candidate.width : 0;
|
|
152
|
+
const height = typeof candidate.height === "number" ? candidate.height : 0;
|
|
153
|
+
return { width, height };
|
|
154
|
+
}
|
|
155
|
+
function toStreamPixels(observation, source, streamWidth, streamHeight) {
|
|
156
|
+
const size = intrinsicSize(source);
|
|
157
|
+
const scaleX = size.width > 0 ? streamWidth / size.width : 1;
|
|
158
|
+
const scaleY = size.height > 0 ? streamHeight / size.height : 1;
|
|
159
|
+
const scalePoint = (p) => ({ x: p.x * scaleX, y: p.y * scaleY });
|
|
160
|
+
return {
|
|
161
|
+
box: {
|
|
162
|
+
x: observation.box.x * scaleX,
|
|
163
|
+
y: observation.box.y * scaleY,
|
|
164
|
+
width: observation.box.width * scaleX,
|
|
165
|
+
height: observation.box.height * scaleY
|
|
166
|
+
},
|
|
167
|
+
...observation.eyes !== void 0 ? { eyes: { left: scalePoint(observation.eyes.left), right: scalePoint(observation.eyes.right) } } : {},
|
|
168
|
+
score: observation.score
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function describeError(err) {
|
|
172
|
+
return err instanceof Error ? err.message : String(err);
|
|
173
|
+
}
|
|
174
|
+
async function createDetector(options) {
|
|
175
|
+
const order = options.preferred ?? DEFAULT_TIER_ORDER;
|
|
176
|
+
const reasons = [];
|
|
177
|
+
for (const tier of order) {
|
|
178
|
+
if (tier === "native") {
|
|
179
|
+
const probed = await probeNativeDetector();
|
|
180
|
+
if (probed.ok) {
|
|
181
|
+
return new NativeDetector(probed.instance, reasonOf(reasons));
|
|
182
|
+
}
|
|
183
|
+
reasons.push(`native:${probed.reason}`);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
return await withTimeout(
|
|
188
|
+
BlazeDetector.create(
|
|
189
|
+
options.v3AssetBaseUrl ?? DEFAULT_V3_ASSET_BASE_URL,
|
|
190
|
+
reasonOf(reasons)
|
|
191
|
+
),
|
|
192
|
+
options.blazeInitTimeoutMs ?? BLAZE_INIT_TIMEOUT_MS
|
|
193
|
+
);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
reasons.push(
|
|
196
|
+
err instanceof InitTimeoutError ? "blaze:blaze_init_timeout" : `blaze:${describeError(err)}`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
throw new DetectorLadderError(reasonOf(reasons) ?? "no_tier_available");
|
|
201
|
+
}
|
|
202
|
+
function reasonOf(reasons) {
|
|
203
|
+
return reasons.length > 0 ? reasons.join("; ") : void 0;
|
|
204
|
+
}
|
|
205
|
+
var InitTimeoutError = class extends Error {
|
|
206
|
+
};
|
|
207
|
+
function withTimeout(promise, deadlineMs) {
|
|
208
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
209
|
+
const timer = setTimeout(() => {
|
|
210
|
+
rejectPromise(new InitTimeoutError(`init exceeded ${deadlineMs}ms`));
|
|
211
|
+
promise.then(
|
|
212
|
+
(value) => value.destroy(),
|
|
213
|
+
() => void 0
|
|
214
|
+
);
|
|
215
|
+
}, deadlineMs);
|
|
216
|
+
promise.then(
|
|
217
|
+
(value) => {
|
|
218
|
+
clearTimeout(timer);
|
|
219
|
+
resolvePromise(value);
|
|
220
|
+
},
|
|
221
|
+
(err) => {
|
|
222
|
+
clearTimeout(timer);
|
|
223
|
+
rejectPromise(err);
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/core/v3/detect-worker.ts
|
|
230
|
+
var WORKER_PROTOCOL_VERSION = 1;
|
|
231
|
+
function workerOffloadSupported() {
|
|
232
|
+
return typeof OffscreenCanvas === "function" && typeof createImageBitmap === "function" && typeof Worker === "function";
|
|
233
|
+
}
|
|
234
|
+
function installWorkerDetectorHost(scope, deps = {}) {
|
|
235
|
+
const create = deps.createDetector ?? createDetector;
|
|
236
|
+
let detector = null;
|
|
237
|
+
scope.onmessage = (event) => {
|
|
238
|
+
const message = event.data;
|
|
239
|
+
if (message.v !== WORKER_PROTOCOL_VERSION) {
|
|
240
|
+
message.bitmap?.close?.();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (message.type === "init") {
|
|
244
|
+
void create(message.options).then((created) => {
|
|
245
|
+
detector = created;
|
|
246
|
+
const ready = {
|
|
247
|
+
v: WORKER_PROTOCOL_VERSION,
|
|
248
|
+
type: "ready",
|
|
249
|
+
tier: created.tier,
|
|
250
|
+
...created.fallbackReason !== void 0 ? { fallbackReason: created.fallbackReason } : {}
|
|
251
|
+
};
|
|
252
|
+
scope.postMessage(ready);
|
|
253
|
+
});
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (message.type === "detect") {
|
|
257
|
+
const respond = (observation) => {
|
|
258
|
+
const result = {
|
|
259
|
+
v: WORKER_PROTOCOL_VERSION,
|
|
260
|
+
type: "result",
|
|
261
|
+
id: message.id,
|
|
262
|
+
observation
|
|
263
|
+
};
|
|
264
|
+
scope.postMessage(result);
|
|
265
|
+
message.bitmap.close?.();
|
|
266
|
+
};
|
|
267
|
+
if (detector === null) {
|
|
268
|
+
respond(null);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
void detector.detect(message.bitmap, message.streamWidth, message.streamHeight).then(respond, () => respond(null));
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (typeof self !== "undefined" && typeof self.importScripts === "function") {
|
|
276
|
+
installWorkerDetectorHost(self);
|
|
277
|
+
}
|
|
278
|
+
var WorkerDetectorProxy = class _WorkerDetectorProxy {
|
|
279
|
+
constructor(worker, ready) {
|
|
280
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
281
|
+
this.nextId = 1;
|
|
282
|
+
this.worker = worker;
|
|
283
|
+
this.tier = ready.tier;
|
|
284
|
+
if (ready.fallbackReason !== void 0) this.fallbackReason = ready.fallbackReason;
|
|
285
|
+
worker.addEventListener("message", (event) => {
|
|
286
|
+
const message = event.data;
|
|
287
|
+
if (message.v !== WORKER_PROTOCOL_VERSION || message.type !== "result") return;
|
|
288
|
+
const resolve = this.pending.get(message.id);
|
|
289
|
+
this.pending.delete(message.id);
|
|
290
|
+
resolve?.(message.observation);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
static create(worker, options, readyTimeoutMs = 1e4) {
|
|
294
|
+
return new Promise((resolve, reject) => {
|
|
295
|
+
const timer = setTimeout(() => {
|
|
296
|
+
worker.removeEventListener("message", onReady);
|
|
297
|
+
worker.terminate();
|
|
298
|
+
reject(new Error(`worker detector not ready within ${readyTimeoutMs}ms`));
|
|
299
|
+
}, readyTimeoutMs);
|
|
300
|
+
const onReady = (event) => {
|
|
301
|
+
const message = event.data;
|
|
302
|
+
if (message.v !== WORKER_PROTOCOL_VERSION || message.type !== "ready") return;
|
|
303
|
+
clearTimeout(timer);
|
|
304
|
+
worker.removeEventListener("message", onReady);
|
|
305
|
+
resolve(new _WorkerDetectorProxy(worker, message));
|
|
306
|
+
};
|
|
307
|
+
worker.addEventListener("message", onReady);
|
|
308
|
+
const init = { v: WORKER_PROTOCOL_VERSION, type: "init", options };
|
|
309
|
+
worker.postMessage(init);
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
async detect(source, sourceWidth, sourceHeight) {
|
|
313
|
+
let bitmap;
|
|
314
|
+
try {
|
|
315
|
+
if (typeof ImageBitmap !== "undefined" && source instanceof ImageBitmap) {
|
|
316
|
+
bitmap = source;
|
|
317
|
+
} else if (typeof createImageBitmap === "function") {
|
|
318
|
+
bitmap = await createImageBitmap(source);
|
|
319
|
+
} else {
|
|
320
|
+
bitmap = source;
|
|
321
|
+
}
|
|
322
|
+
} catch {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
const id = this.nextId;
|
|
326
|
+
this.nextId += 1;
|
|
327
|
+
return new Promise((resolve) => {
|
|
328
|
+
this.pending.set(id, resolve);
|
|
329
|
+
const message = {
|
|
330
|
+
v: WORKER_PROTOCOL_VERSION,
|
|
331
|
+
type: "detect",
|
|
332
|
+
id,
|
|
333
|
+
bitmap,
|
|
334
|
+
streamWidth: sourceWidth,
|
|
335
|
+
streamHeight: sourceHeight
|
|
336
|
+
};
|
|
337
|
+
this.worker.postMessage(message, [bitmap]);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
destroy() {
|
|
341
|
+
for (const resolve of this.pending.values()) resolve(null);
|
|
342
|
+
this.pending.clear();
|
|
343
|
+
this.worker.terminate();
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
export {
|
|
348
|
+
DEFAULT_V3_ASSET_BASE_URL,
|
|
349
|
+
DEFAULT_TIER_ORDER,
|
|
350
|
+
DetectorLadderError,
|
|
351
|
+
intrinsicSize,
|
|
352
|
+
toStreamPixels,
|
|
353
|
+
createDetector,
|
|
354
|
+
WORKER_PROTOCOL_VERSION,
|
|
355
|
+
workerOffloadSupported,
|
|
356
|
+
installWorkerDetectorHost,
|
|
357
|
+
WorkerDetectorProxy
|
|
358
|
+
};
|
|
359
|
+
//# sourceMappingURL=chunk-NWF46H7K.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/v3/detector-blaze.ts","../src/core/v3/detector-native.ts","../src/core/v3/detector.ts","../src/core/v3/detect-worker.ts"],"sourcesContent":["// BlazeFace short-range tier (Task 7) — MediaPipe tasks-vision FaceDetector,\n// the modern default: strong on large/close faces, ~15–40ms on low-end\n// WASM+SIMD. tasks-vision is imported LAZILY (only when this tier is\n// selected) and every asset is SELF-HOSTED under the v3 asset base (hosted\n// CSP is connect-src 'self' — no CDN): the wasm fileset via\n// FilesetResolver.forVisionTasks(base) and the model at\n// `${base}/blaze_face_short_range.tflite` (synced by\n// identity-hosted/scripts/sync-models-v3.mjs, sha256-manifested).\n//\n// DELEGATE = 'CPU' (2026-07-09, capture-v3 hardening — the load-bearing fix):\n// tasks-vision defaults `baseOptions.delegate` to the WebGL/GPU delegate,\n// which HANGS on iOS Safari — every field capture blew the 10s init deadline\n// (`blaze:blaze_init_timeout`) and silently fell to the deprecated face-api\n// floor at ~2.4fps. BlazeFace short-range is tiny (~229 KB) and fast on the\n// WASM+SIMD CPU delegate, so we pin CPU UNCONDITIONALLY: reliability across\n// iOS Safari + Chrome beats the marginal desktop-GPU speedup (and the GPU\n// path is exactly the bug). A GPU-first-with-CPU-fallback dance would only\n// re-introduce the hang risk inside the init budget for no real win.\nimport { type Detector, type FaceObservation, intrinsicSize, toStreamPixels } from \"./detector\";\n\n/** createFromOptions must resolve within this window or the ladder falls\n * through with `blaze_init_timeout` (overridable via CreateDetectorOptions).\n * Raised 5s→10s: on iOS Safari the COLD wasm SIMD compile of tasks-vision can\n * exceed 5s on the FIRST (unwarmed) capture. Since the face-api floor was\n * dropped (2026-07-09), blowing this deadline is now a HARD capture failure\n * (error{kind:'models'} — retryable), not a silent degrade, so the CPU\n * delegate above (which makes init actually complete on iOS) is what keeps\n * this deadline reachable. The hosted warm path\n * (identity-hosted/lib/warm-models.ts) pre-warms this tier in idle time so\n * capture usually reuses a hot instance; this larger deadline only covers the\n * un-warmed fallback (Data-Saver, deep-link straight to liveness). */\nexport const BLAZE_INIT_TIMEOUT_MS = 10_000;\n\ninterface TasksVisionDetection {\n boundingBox?: { originX: number; originY: number; width: number; height: number };\n categories: Array<{ score: number }>;\n /** NORMALIZED to the input image (0..1) — tasks-vision docs; index 0 =\n * right eye, 1 = left eye (SUBJECT perspective). We re-order by image-x\n * (the FaceObservation convention), so the doc order is informative only. */\n keypoints?: Array<{ x: number; y: number }>;\n}\n\ninterface TasksVisionFaceDetector {\n detect(source: unknown): { detections: TasksVisionDetection[] };\n close(): void;\n}\n\nexport class BlazeDetector implements Detector {\n readonly tier = \"blaze\" as const;\n readonly fallbackReason?: string;\n private readonly inner: TasksVisionFaceDetector;\n\n private constructor(inner: TasksVisionFaceDetector, fallbackReason?: string) {\n this.inner = inner;\n if (fallbackReason !== undefined) this.fallbackReason = fallbackReason;\n }\n\n static async create(assetBaseUrl: string, fallbackReason?: string): Promise<BlazeDetector> {\n const vision = await import(\"@mediapipe/tasks-vision\");\n const fileset = await vision.FilesetResolver.forVisionTasks(assetBaseUrl);\n const inner = (await vision.FaceDetector.createFromOptions(fileset, {\n baseOptions: {\n modelAssetPath: `${assetBaseUrl}/blaze_face_short_range.tflite`,\n // CPU delegate — the iOS-Safari-safe path (see header). NEVER 'GPU'.\n delegate: \"CPU\",\n },\n runningMode: \"IMAGE\",\n })) as unknown as TasksVisionFaceDetector;\n return new BlazeDetector(inner, fallbackReason);\n }\n\n async detect(\n source: CanvasImageSource,\n sourceWidth: number,\n sourceHeight: number,\n ): Promise<FaceObservation | null> {\n let detections: TasksVisionDetection[];\n try {\n detections = this.inner.detect(source).detections;\n } catch {\n return null; // never rejects mid-loop\n }\n const best = pickMostConfident(detections);\n if (best?.boundingBox === undefined) return null;\n const size = intrinsicSize(source);\n const eyes = extractEyes(best.keypoints, size);\n const observation: FaceObservation = {\n box: {\n x: best.boundingBox.originX,\n y: best.boundingBox.originY,\n width: best.boundingBox.width,\n height: best.boundingBox.height,\n },\n ...(eyes !== undefined ? { eyes } : {}),\n score: best.categories[0]?.score ?? 0,\n };\n return toStreamPixels(observation, source, sourceWidth, sourceHeight);\n }\n\n destroy(): void {\n try {\n this.inner.close();\n } catch {\n // wasm teardown failures are inert\n }\n }\n}\n\nfunction pickMostConfident(detections: TasksVisionDetection[]): TasksVisionDetection | undefined {\n let best: TasksVisionDetection | undefined;\n let bestScore = -1;\n for (const detection of detections) {\n const score = detection.categories[0]?.score ?? 0;\n if (score > bestScore) {\n best = detection;\n bestScore = score;\n }\n }\n return best;\n}\n\n/** Keypoints 0/1 (the eye pair) — normalized → SOURCE px, ordered by image-x. */\nfunction extractEyes(\n keypoints: Array<{ x: number; y: number }> | undefined,\n size: { width: number; height: number },\n): FaceObservation[\"eyes\"] | undefined {\n const a = keypoints?.[0];\n const b = keypoints?.[1];\n if (a === undefined || b === undefined) return undefined;\n const toSource = (p: { x: number; y: number }) => ({ x: p.x * size.width, y: p.y * size.height });\n const [pa, pb] = [toSource(a), toSource(b)];\n return pa.x <= pb.x ? { left: pa, right: pb } : { left: pb, right: pa };\n}\n","// Native FaceDetector tier (Task 6) — the Shape Detection API: free,\n// hardware-backed where implemented (Chrome/Android, some WebViews). Probed\n// with a 1-frame smoke detect because several browsers EXPOSE the constructor\n// but throw `Face detection service unavailable` at runtime.\nimport { type Detector, type FaceObservation, toStreamPixels } from \"./detector\";\n\ninterface NativeLandmark {\n type: string;\n locations: Array<{ x: number; y: number }>;\n}\n\ninterface NativeDetectedFace {\n boundingBox: { x: number; y: number; width: number; height: number };\n landmarks?: NativeLandmark[];\n}\n\nexport interface NativeFaceDetectorLike {\n detect(source: CanvasImageSource): Promise<NativeDetectedFace[]>;\n}\n\ntype NativeGlobal = typeof globalThis & {\n FaceDetector?: new (options: { fastMode: boolean; maxDetectedFaces: number }) => NativeFaceDetectorLike;\n};\n\nfunction smokeSource(): CanvasImageSource {\n if (typeof OffscreenCanvas === \"function\") return new OffscreenCanvas(2, 2);\n const canvas = document.createElement(\"canvas\");\n canvas.width = 2;\n canvas.height = 2;\n return canvas;\n}\n\n/** Constructor + 1-frame smoke detect (must not throw; result irrelevant). */\nexport async function probeNativeDetector(): Promise<\n { ok: true; instance: NativeFaceDetectorLike } | { ok: false; reason: string }\n> {\n const ctor = (globalThis as NativeGlobal).FaceDetector;\n if (typeof ctor !== \"function\") return { ok: false, reason: \"unsupported\" };\n try {\n const instance = new ctor({ fastMode: true, maxDetectedFaces: 1 });\n await instance.detect(smokeSource());\n return { ok: true, instance };\n } catch (err) {\n return { ok: false, reason: `smoke_failed:${err instanceof Error ? err.message : String(err)}` };\n }\n}\n\nexport class NativeDetector implements Detector {\n readonly tier = \"native\" as const;\n readonly fallbackReason?: string;\n private readonly inner: NativeFaceDetectorLike;\n\n constructor(inner: NativeFaceDetectorLike, fallbackReason?: string) {\n this.inner = inner;\n if (fallbackReason !== undefined) this.fallbackReason = fallbackReason;\n }\n\n async detect(\n source: CanvasImageSource,\n sourceWidth: number,\n sourceHeight: number,\n ): Promise<FaceObservation | null> {\n let faces: NativeDetectedFace[];\n try {\n faces = await this.inner.detect(source);\n } catch {\n return null; // never rejects mid-loop\n }\n const face = faces[0];\n if (face === undefined) return null;\n const eyes = extractEyes(face.landmarks);\n const observation: FaceObservation = {\n box: {\n x: face.boundingBox.x,\n y: face.boundingBox.y,\n width: face.boundingBox.width,\n height: face.boundingBox.height,\n },\n ...(eyes !== undefined ? { eyes } : {}),\n // The Shape Detection API exposes no confidence — the smoke-tested\n // native tier treats any detection as confident.\n score: 1,\n };\n return toStreamPixels(observation, source, sourceWidth, sourceHeight);\n }\n\n destroy(): void {\n // The native detector holds no resources the page must release.\n }\n}\n\n/** Two `eye` landmarks → {left, right} by image-x (see FaceObservation). */\nfunction extractEyes(\n landmarks: NativeLandmark[] | undefined,\n): FaceObservation[\"eyes\"] | undefined {\n if (landmarks === undefined) return undefined;\n const eyePoints = landmarks\n .filter((landmark) => landmark.type === \"eye\")\n .map((landmark) => landmark.locations[0])\n .filter((point): point is { x: number; y: number } => point !== undefined);\n const [a, b] = eyePoints;\n if (a === undefined || b === undefined) return undefined;\n return a.x <= b.x ? { left: a, right: b } : { left: b, right: a };\n}\n","// v3 Detector abstraction (capture program Task 6) — one detection seam with\n// capability-probed tiers. NEW parallel module: nothing in the v1 core\n// imports from v3/*, and the calibration-pinned v1 gate math is untouched.\n//\n// Ladder (DEFAULT_TIER_ORDER): `native` (the browser's FaceDetector — free,\n// hardware-backed where it exists) → `blaze` (MediaPipe BlazeFace short-range,\n// Task 7). The legacy `faceapi` floor was DROPPED (2026-07-09, capture-v3\n// hardening): field telemetry showed BlazeFace never initialized (the GPU\n// delegate hung on iOS → `blaze_init_timeout`) so EVERY capture silently ran\n// the deprecated ~2.4fps face-api floor. With the CPU-delegate blaze fix,\n// face-api is dead weight — and a floor that masks a blaze regression is worse\n// than none, so the ladder is now native→blaze with NO fallback.\n//\n// Contract change: createDetector still never throws while a tier can be\n// built, but with no floor it REJECTS (DetectorLadderError) once the ladder is\n// exhausted — a CLEAN, non-hanging failure carrying the per-tier reasons.\n// CaptureSessionV3.run() maps that rejection to error{kind:'models'}\n// (retryable; the hosted UI renders a retry). A skipped tier still records a\n// `fallbackReason` (exposed on the returned Detector) so a native→blaze\n// fall-through stays observable in telemetry.\nimport { BLAZE_INIT_TIMEOUT_MS, BlazeDetector } from \"./detector-blaze\";\nimport { NativeDetector, probeNativeDetector } from \"./detector-native\";\n\nexport interface FaceObservation {\n /** Stream-pixel box. */\n box: { x: number; y: number; width: number; height: number };\n /** Stream-pixel eye centers when the tier provides them (native:\n * landmarks?, blaze: keypoints). Convention: `left` = smaller x in IMAGE\n * coordinates — gate math only uses the midpoint and |roll|, so the\n * person-left/image-left distinction never matters. */\n eyes?: { left: { x: number; y: number }; right: { x: number; y: number } };\n score: number;\n}\n\nexport interface Detector {\n readonly tier: \"native\" | \"blaze\";\n /** Why higher tiers were skipped (telemetry) — absent when the first\n * preference won. */\n readonly fallbackReason?: string;\n /** Detect on a (possibly downscaled) snapshot; `sourceWidth/Height` are the\n * STREAM dimensions the observation must be normalized to. Returns the\n * single most-confident face or null; NEVER rejects. */\n detect(\n source: CanvasImageSource,\n sourceWidth: number,\n sourceHeight: number,\n ): Promise<FaceObservation | null>;\n destroy(): void;\n}\n\nexport interface CreateDetectorOptions {\n /** v3 self-hosted assets base (tasks-vision wasm + blaze tflite) — hosted\n * serves `/models-v3`, a sha256-manifested directory. */\n v3AssetBaseUrl?: string;\n preferred?: Detector[\"tier\"][];\n /** Test/tuning override for the blaze init deadline. */\n blazeInitTimeoutMs?: number;\n}\n\nexport const DEFAULT_V3_ASSET_BASE_URL = \"/models-v3\";\n\nexport const DEFAULT_TIER_ORDER: Detector[\"tier\"][] = [\"native\", \"blaze\"];\n\n/** Thrown by createDetector when the ENTIRE tier ladder is exhausted. Since the\n * face-api floor was dropped (2026-07-09) there is no last resort, so blaze\n * failing on an already-native-less platform leaves NO detector. Carries the\n * joined per-tier reasons ('native:unsupported; blaze:blaze_init_timeout') as\n * `fallbackReason` for telemetry. Callers MUST map this to a retryable models\n * error — never a hang (CaptureSessionV3.run → error{kind:'models'}). */\nexport class DetectorLadderError extends Error {\n readonly fallbackReason: string;\n constructor(fallbackReason: string) {\n super(`no detector tier available (${fallbackReason})`);\n this.name = \"DetectorLadderError\";\n this.fallbackReason = fallbackReason;\n }\n}\n\n/** Intrinsic pixel size of a snapshot source (video → videoWidth, image →\n * naturalWidth, canvas/bitmap → width). Property-sniffed so worker-side\n * sources (OffscreenCanvas, ImageBitmap) work without instanceof. */\nexport function intrinsicSize(source: CanvasImageSource): { width: number; height: number } {\n const candidate = source as {\n videoWidth?: number;\n videoHeight?: number;\n naturalWidth?: number;\n naturalHeight?: number;\n width?: number | { baseVal?: { value?: number } };\n height?: number | { baseVal?: { value?: number } };\n };\n if (typeof candidate.videoWidth === \"number\" && candidate.videoWidth > 0) {\n return { width: candidate.videoWidth, height: candidate.videoHeight ?? 0 };\n }\n if (typeof candidate.naturalWidth === \"number\" && candidate.naturalWidth > 0) {\n return { width: candidate.naturalWidth, height: candidate.naturalHeight ?? 0 };\n }\n const width = typeof candidate.width === \"number\" ? candidate.width : 0;\n const height = typeof candidate.height === \"number\" ? candidate.height : 0;\n return { width, height };\n}\n\n/** Scale a source-space observation to stream px. Exported for adapters. */\nexport function toStreamPixels(\n observation: FaceObservation,\n source: CanvasImageSource,\n streamWidth: number,\n streamHeight: number,\n): FaceObservation {\n const size = intrinsicSize(source);\n const scaleX = size.width > 0 ? streamWidth / size.width : 1;\n const scaleY = size.height > 0 ? streamHeight / size.height : 1;\n const scalePoint = (p: { x: number; y: number }) => ({ x: p.x * scaleX, y: p.y * scaleY });\n return {\n box: {\n x: observation.box.x * scaleX,\n y: observation.box.y * scaleY,\n width: observation.box.width * scaleX,\n height: observation.box.height * scaleY,\n },\n ...(observation.eyes !== undefined\n ? { eyes: { left: scalePoint(observation.eyes.left), right: scalePoint(observation.eyes.right) } }\n : {}),\n score: observation.score,\n };\n}\n\nfunction describeError(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nexport async function createDetector(options: CreateDetectorOptions): Promise<Detector> {\n const order = options.preferred ?? DEFAULT_TIER_ORDER;\n const reasons: string[] = [];\n for (const tier of order) {\n if (tier === \"native\") {\n const probed = await probeNativeDetector();\n if (probed.ok) {\n return new NativeDetector(probed.instance, reasonOf(reasons));\n }\n reasons.push(`native:${probed.reason}`);\n continue;\n }\n // tier === \"blaze\": smoke test = createFromOptions resolving within the\n // deadline (wasm fetch/compile can hang on hostile networks — never stall\n // the ladder).\n try {\n return await withTimeout(\n BlazeDetector.create(\n options.v3AssetBaseUrl ?? DEFAULT_V3_ASSET_BASE_URL,\n reasonOf(reasons),\n ),\n options.blazeInitTimeoutMs ?? BLAZE_INIT_TIMEOUT_MS,\n );\n } catch (err) {\n reasons.push(\n err instanceof InitTimeoutError\n ? \"blaze:blaze_init_timeout\"\n : `blaze:${describeError(err)}`,\n );\n }\n }\n // Ladder exhausted — the face-api floor is gone, so there is NO fallback.\n // REJECT cleanly with the recorded reasons; CaptureSessionV3.run() maps this\n // to a retryable error{kind:'models'} (a bounded failure the hosted UI\n // retries) rather than hanging. NEVER return a null-yielding detector here.\n throw new DetectorLadderError(reasonOf(reasons) ?? \"no_tier_available\");\n}\n\nfunction reasonOf(reasons: string[]): string | undefined {\n return reasons.length > 0 ? reasons.join(\"; \") : undefined;\n}\n\nclass InitTimeoutError extends Error {}\n\nfunction withTimeout<T extends { destroy(): void }>(\n promise: Promise<T>,\n deadlineMs: number,\n): Promise<T> {\n return new Promise<T>((resolvePromise, rejectPromise) => {\n const timer = setTimeout(() => {\n rejectPromise(new InitTimeoutError(`init exceeded ${deadlineMs}ms`));\n // A late resolution after the deadline is an orphan (wasm/GPU\n // delegate) — release it instead of leaking until navigation.\n promise.then(\n (value) => value.destroy(),\n () => undefined,\n );\n }, deadlineMs);\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolvePromise(value);\n },\n (err) => {\n clearTimeout(timer);\n rejectPromise(err);\n },\n );\n });\n}\n","// Worker offload (Task 9) — detection off the main thread when the platform\n// allows (OffscreenCanvas + createImageBitmap + Worker). BOTH protocol sides\n// live in this file so the tiny versioned protocol (v:1) can never drift:\n// - installWorkerDetectorHost: runs INSIDE the worker (auto-installed when\n// this module loads in a DedicatedWorkerGlobalScope; tsup builds it as\n// its own entry so hosts can `new Worker(new URL(...))` the dist file);\n// - WorkerDetectorProxy: main-thread Detector implementation that posts\n// ImageBitmap frames as transferables and resolves results by id.\nimport {\n type CreateDetectorOptions,\n createDetector as defaultCreateDetector,\n type Detector,\n type FaceObservation,\n} from \"./detector\";\n\nexport const WORKER_PROTOCOL_VERSION = 1;\n\ninterface InitMessage {\n v: typeof WORKER_PROTOCOL_VERSION;\n type: \"init\";\n options: CreateDetectorOptions;\n}\ninterface DetectMessage {\n v: typeof WORKER_PROTOCOL_VERSION;\n type: \"detect\";\n id: number;\n bitmap: ImageBitmap;\n streamWidth: number;\n streamHeight: number;\n}\ninterface ReadyMessage {\n v: typeof WORKER_PROTOCOL_VERSION;\n type: \"ready\";\n tier: Detector[\"tier\"];\n fallbackReason?: string;\n}\ninterface ResultMessage {\n v: typeof WORKER_PROTOCOL_VERSION;\n type: \"result\";\n id: number;\n observation: FaceObservation | null;\n}\n\ntype ToWorker = InitMessage | DetectMessage;\ntype ToMain = ReadyMessage | ResultMessage;\n\n/** The slice of Worker/scope surface the protocol needs — test-stubbable. */\nexport interface WorkerScopeLike {\n onmessage: ((event: { data: unknown }) => void) | null;\n postMessage(data: unknown): void;\n}\nexport interface WorkerLike {\n postMessage(data: unknown, transfer?: Transferable[]): void;\n addEventListener(type: \"message\", listener: (event: { data: unknown }) => void): void;\n removeEventListener(type: \"message\", listener: (event: { data: unknown }) => void): void;\n terminate(): void;\n}\n\n/** Feature gate: bitmap capture + transfer + workers must all exist. */\nexport function workerOffloadSupported(): boolean {\n return (\n typeof OffscreenCanvas === \"function\" &&\n typeof createImageBitmap === \"function\" &&\n typeof Worker === \"function\"\n );\n}\n\n/** Worker-side message host. `deps.createDetector` is a test seam. */\nexport function installWorkerDetectorHost(\n scope: WorkerScopeLike,\n deps: { createDetector?: (options: CreateDetectorOptions) => Promise<Detector> } = {},\n): void {\n const create = deps.createDetector ?? defaultCreateDetector;\n let detector: Detector | null = null;\n scope.onmessage = (event) => {\n const message = event.data as ToWorker;\n if (message.v !== WORKER_PROTOCOL_VERSION) {\n // Ignore unknown versions — but never strand a transferred bitmap.\n (message as { bitmap?: { close?: () => void } }).bitmap?.close?.();\n return;\n }\n if (message.type === \"init\") {\n void create(message.options).then((created) => {\n detector = created;\n const ready: ReadyMessage = {\n v: WORKER_PROTOCOL_VERSION,\n type: \"ready\",\n tier: created.tier,\n ...(created.fallbackReason !== undefined\n ? { fallbackReason: created.fallbackReason }\n : {}),\n };\n scope.postMessage(ready);\n });\n return;\n }\n if (message.type === \"detect\") {\n const respond = (observation: FaceObservation | null) => {\n const result: ResultMessage = {\n v: WORKER_PROTOCOL_VERSION,\n type: \"result\",\n id: message.id,\n observation,\n };\n scope.postMessage(result);\n (message.bitmap as { close?: () => void }).close?.();\n };\n if (detector === null) {\n respond(null);\n return;\n }\n void detector\n .detect(message.bitmap, message.streamWidth, message.streamHeight)\n .then(respond, () => respond(null));\n }\n };\n}\n\n// Auto-install when this module IS the worker entry (DedicatedWorkerGlobalScope\n// has importScripts; window does not). Inert in main-thread bundles/tests.\ndeclare const self: (WorkerScopeLike & { importScripts?: unknown }) | undefined;\nif (typeof self !== \"undefined\" && typeof self.importScripts === \"function\") {\n installWorkerDetectorHost(self);\n}\n\n/** Main-side Detector over the worker protocol. Same interface as the\n * in-thread detectors — Task 10's engine cannot tell the difference. */\nexport class WorkerDetectorProxy implements Detector {\n readonly tier: Detector[\"tier\"];\n readonly fallbackReason?: string;\n private readonly worker: WorkerLike;\n private readonly pending = new Map<number, (observation: FaceObservation | null) => void>();\n private nextId = 1;\n\n private constructor(worker: WorkerLike, ready: ReadyMessage) {\n this.worker = worker;\n this.tier = ready.tier;\n if (ready.fallbackReason !== undefined) this.fallbackReason = ready.fallbackReason;\n worker.addEventListener(\"message\", (event) => {\n const message = event.data as ToMain;\n if (message.v !== WORKER_PROTOCOL_VERSION || message.type !== \"result\") return;\n const resolve = this.pending.get(message.id);\n this.pending.delete(message.id);\n resolve?.(message.observation);\n });\n }\n\n static create(\n worker: WorkerLike,\n options: CreateDetectorOptions,\n readyTimeoutMs = 10_000,\n ): Promise<WorkerDetectorProxy> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n worker.removeEventListener(\"message\", onReady);\n worker.terminate();\n reject(new Error(`worker detector not ready within ${readyTimeoutMs}ms`));\n }, readyTimeoutMs);\n const onReady = (event: { data: unknown }) => {\n const message = event.data as ToMain;\n if (message.v !== WORKER_PROTOCOL_VERSION || message.type !== \"ready\") return;\n clearTimeout(timer);\n worker.removeEventListener(\"message\", onReady);\n resolve(new WorkerDetectorProxy(worker, message));\n };\n worker.addEventListener(\"message\", onReady);\n const init: InitMessage = { v: WORKER_PROTOCOL_VERSION, type: \"init\", options };\n worker.postMessage(init);\n });\n }\n\n async detect(\n source: CanvasImageSource,\n sourceWidth: number,\n sourceHeight: number,\n ): Promise<FaceObservation | null> {\n // Only ImageBitmap is Transferable — convert other sources (video,\n // canvas) via createImageBitmap (the feature gate guarantees it). A\n // failed conversion yields null, never a DataCloneError.\n let bitmap: ImageBitmap;\n try {\n if (typeof ImageBitmap !== \"undefined\" && source instanceof ImageBitmap) {\n bitmap = source;\n } else if (typeof createImageBitmap === \"function\") {\n bitmap = await createImageBitmap(source as ImageBitmapSource);\n } else {\n // No conversion surface (stubbed environments) — trust the caller.\n bitmap = source as ImageBitmap;\n }\n } catch {\n return null;\n }\n const id = this.nextId;\n this.nextId += 1;\n return new Promise((resolve) => {\n this.pending.set(id, resolve);\n const message: DetectMessage = {\n v: WORKER_PROTOCOL_VERSION,\n type: \"detect\",\n id,\n bitmap,\n streamWidth: sourceWidth,\n streamHeight: sourceHeight,\n };\n this.worker.postMessage(message, [bitmap]);\n });\n }\n\n destroy(): void {\n // Settle in-flight detects (an awaiting scheduler run must not hang\n // busy=true forever) before tearing the worker down.\n for (const resolve of this.pending.values()) resolve(null);\n this.pending.clear();\n this.worker.terminate();\n }\n}\n"],"mappings":"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","names":["extractEyes"]}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EXPOSURE_BAND
|
|
3
|
+
} from "./chunk-MWNFSNMP.js";
|
|
4
|
+
|
|
5
|
+
// src/core/doc/doc-catalog.ts
|
|
6
|
+
var ASPECT_BANDS = {
|
|
7
|
+
"ID-1": { center: 1.586, tolerance: 0.15 },
|
|
8
|
+
"ID-3": { center: 1.42, tolerance: 0.2 }
|
|
9
|
+
};
|
|
10
|
+
function aspectBand(format) {
|
|
11
|
+
return ASPECT_BANDS[format];
|
|
12
|
+
}
|
|
13
|
+
var MR_DOC_CATALOG = [
|
|
14
|
+
{
|
|
15
|
+
docType: "carte_identite",
|
|
16
|
+
format: "ID-1",
|
|
17
|
+
sides: ["recto", "verso"],
|
|
18
|
+
mrz: "td1",
|
|
19
|
+
// verso; carries the NNI with ICAO check digits (findings §6)
|
|
20
|
+
portraitSide: "recto",
|
|
21
|
+
aspectRatio: 1.586,
|
|
22
|
+
identifierSource: "mrz"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
docType: "passport",
|
|
26
|
+
format: "ID-3",
|
|
27
|
+
sides: ["single"],
|
|
28
|
+
mrz: "td3",
|
|
29
|
+
portraitSide: "single",
|
|
30
|
+
// data page
|
|
31
|
+
aspectRatio: 1.42,
|
|
32
|
+
identifierSource: "mrz"
|
|
33
|
+
}
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// src/core/doc/doc-gate-v1.ts
|
|
37
|
+
var DOC_DETECT_WIDTH = 480;
|
|
38
|
+
var DOC_GATE_V1_DEFAULTS = Object.freeze({
|
|
39
|
+
MIN_AREA_FRAC: 0.15,
|
|
40
|
+
EDGE_CLIP_MARGIN_PX: 4,
|
|
41
|
+
STABLE_TICKS: 3,
|
|
42
|
+
IOU_STABLE_MIN: 0.85,
|
|
43
|
+
RISE_PER_S: 0.9,
|
|
44
|
+
FALL_PER_S: 0.25,
|
|
45
|
+
GUIDE_EXPOSURE: 0.6,
|
|
46
|
+
GUIDE_SHARPNESS: 0.35,
|
|
47
|
+
ID3_BAND_MIN_WIDTH_FRAC: 0.55,
|
|
48
|
+
ID3_BAND_ASPECT_MIN: 4,
|
|
49
|
+
ID3_BAND_ASPECT_MAX: 40
|
|
50
|
+
});
|
|
51
|
+
var clamp01 = (value) => Math.min(1, Math.max(0, value));
|
|
52
|
+
function quadAreaFrac(quad, frameWidth, frameHeight) {
|
|
53
|
+
const pts = [quad.topLeft, quad.topRight, quad.bottomRight, quad.bottomLeft];
|
|
54
|
+
let doubled = 0;
|
|
55
|
+
for (let i = 0; i < 4; i++) {
|
|
56
|
+
const a = pts[i];
|
|
57
|
+
const b = pts[(i + 1) % 4];
|
|
58
|
+
doubled += a.x * b.y - b.x * a.y;
|
|
59
|
+
}
|
|
60
|
+
const frameArea = frameWidth * frameHeight;
|
|
61
|
+
return frameArea > 0 ? Math.abs(doubled / 2) / frameArea : 0;
|
|
62
|
+
}
|
|
63
|
+
function quadIou(a, b) {
|
|
64
|
+
const boxA = bbox(a);
|
|
65
|
+
const boxB = bbox(b);
|
|
66
|
+
const x1 = Math.max(boxA.x1, boxB.x1);
|
|
67
|
+
const y1 = Math.max(boxA.y1, boxB.y1);
|
|
68
|
+
const x2 = Math.min(boxA.x2, boxB.x2);
|
|
69
|
+
const y2 = Math.min(boxA.y2, boxB.y2);
|
|
70
|
+
const inter = Math.max(0, x2 - x1) * Math.max(0, y2 - y1);
|
|
71
|
+
const areaA = (boxA.x2 - boxA.x1) * (boxA.y2 - boxA.y1);
|
|
72
|
+
const areaB = (boxB.x2 - boxB.x1) * (boxB.y2 - boxB.y1);
|
|
73
|
+
const union = areaA + areaB - inter;
|
|
74
|
+
return union > 0 ? inter / union : 0;
|
|
75
|
+
}
|
|
76
|
+
function quadBounds(quad) {
|
|
77
|
+
const box = bbox(quad);
|
|
78
|
+
return { width: box.x2 - box.x1, height: box.y2 - box.y1 };
|
|
79
|
+
}
|
|
80
|
+
function bbox(quad) {
|
|
81
|
+
const pts = [quad.topLeft, quad.topRight, quad.bottomRight, quad.bottomLeft];
|
|
82
|
+
return {
|
|
83
|
+
x1: Math.min(...pts.map((p) => p.x)),
|
|
84
|
+
y1: Math.min(...pts.map((p) => p.y)),
|
|
85
|
+
x2: Math.max(...pts.map((p) => p.x)),
|
|
86
|
+
y2: Math.max(...pts.map((p) => p.y))
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function quadAspect(quad) {
|
|
90
|
+
const top = Math.hypot(quad.topRight.x - quad.topLeft.x, quad.topRight.y - quad.topLeft.y);
|
|
91
|
+
const bottom = Math.hypot(
|
|
92
|
+
quad.bottomRight.x - quad.bottomLeft.x,
|
|
93
|
+
quad.bottomRight.y - quad.bottomLeft.y
|
|
94
|
+
);
|
|
95
|
+
const left = Math.hypot(quad.bottomLeft.x - quad.topLeft.x, quad.bottomLeft.y - quad.topLeft.y);
|
|
96
|
+
const right = Math.hypot(
|
|
97
|
+
quad.bottomRight.x - quad.topRight.x,
|
|
98
|
+
quad.bottomRight.y - quad.topRight.y
|
|
99
|
+
);
|
|
100
|
+
const horizontal = (top + bottom) / 2;
|
|
101
|
+
const vertical = (left + right) / 2;
|
|
102
|
+
if (horizontal <= 0 || vertical <= 0) return 0;
|
|
103
|
+
return Math.max(horizontal, vertical) / Math.min(horizontal, vertical);
|
|
104
|
+
}
|
|
105
|
+
var DocGateV1 = class {
|
|
106
|
+
constructor(format, overrides = {}) {
|
|
107
|
+
this.p = 0;
|
|
108
|
+
this.lastTickAt = null;
|
|
109
|
+
this.lastQuad = null;
|
|
110
|
+
this.streak = 0;
|
|
111
|
+
this.format = format;
|
|
112
|
+
this.t = { ...DOC_GATE_V1_DEFAULTS, ...overrides };
|
|
113
|
+
}
|
|
114
|
+
reset() {
|
|
115
|
+
this.p = 0;
|
|
116
|
+
this.lastTickAt = null;
|
|
117
|
+
this.lastQuad = null;
|
|
118
|
+
this.streak = 0;
|
|
119
|
+
}
|
|
120
|
+
tick(input) {
|
|
121
|
+
const { quad, quality, timestampMs } = input;
|
|
122
|
+
const dtSeconds = this.lastTickAt === null ? 0 : Math.max(0, (timestampMs - this.lastTickAt) / 1e3);
|
|
123
|
+
this.lastTickAt = timestampMs;
|
|
124
|
+
let geometryKey = null;
|
|
125
|
+
if (quad === null) {
|
|
126
|
+
geometryKey = "dcv1.no_doc";
|
|
127
|
+
} else if (this.format === "ID-3") {
|
|
128
|
+
const box = quadBounds(quad);
|
|
129
|
+
const aspect = box.height > 0 ? box.width / box.height : 0;
|
|
130
|
+
if (box.width < this.t.ID3_BAND_MIN_WIDTH_FRAC * input.frameWidth) {
|
|
131
|
+
geometryKey = "dcv1.come_closer";
|
|
132
|
+
} else if (this.edgeClipped(quad, input.frameWidth, input.frameHeight)) {
|
|
133
|
+
geometryKey = "dcv1.fit_document";
|
|
134
|
+
} else if (aspect < this.t.ID3_BAND_ASPECT_MIN || aspect > this.t.ID3_BAND_ASPECT_MAX) {
|
|
135
|
+
geometryKey = "dcv1.wrong_shape";
|
|
136
|
+
}
|
|
137
|
+
} else if (quadAreaFrac(quad, input.frameWidth, input.frameHeight) < this.t.MIN_AREA_FRAC) {
|
|
138
|
+
geometryKey = "dcv1.come_closer";
|
|
139
|
+
} else if (this.edgeClipped(quad, input.frameWidth, input.frameHeight)) {
|
|
140
|
+
geometryKey = "dcv1.fit_document";
|
|
141
|
+
} else {
|
|
142
|
+
const band = aspectBand(this.format);
|
|
143
|
+
const aspect = quadAspect(quad);
|
|
144
|
+
if (Math.abs(aspect - band.center) > band.tolerance) geometryKey = "dcv1.wrong_shape";
|
|
145
|
+
}
|
|
146
|
+
const plausible = geometryKey === null;
|
|
147
|
+
const exposureOk = clamp01(quality.exposure) >= this.t.GUIDE_EXPOSURE;
|
|
148
|
+
const sharpnessOk = clamp01(quality.sharpness) >= this.t.GUIDE_SHARPNESS;
|
|
149
|
+
const qualityOk = exposureOk && sharpnessOk;
|
|
150
|
+
if (plausible && qualityOk && quad !== null) {
|
|
151
|
+
const held = this.lastQuad === null || quadIou(quad, this.lastQuad) >= this.t.IOU_STABLE_MIN;
|
|
152
|
+
this.streak = held ? this.streak + 1 : 1;
|
|
153
|
+
} else {
|
|
154
|
+
this.streak = 0;
|
|
155
|
+
}
|
|
156
|
+
this.lastQuad = quad;
|
|
157
|
+
const good = plausible && qualityOk;
|
|
158
|
+
this.p = clamp01(this.p + (good ? this.t.RISE_PER_S : -this.t.FALL_PER_S) * dtSeconds);
|
|
159
|
+
const ready = good && this.streak >= this.t.STABLE_TICKS;
|
|
160
|
+
let guidanceKey;
|
|
161
|
+
if (geometryKey !== null) {
|
|
162
|
+
guidanceKey = geometryKey;
|
|
163
|
+
} else if (!exposureOk) {
|
|
164
|
+
const bandMid = (EXPOSURE_BAND[0] + EXPOSURE_BAND[1]) / 2;
|
|
165
|
+
guidanceKey = quality.meanLuma < bandMid ? "dcv1.more_light" : "dcv1.too_bright";
|
|
166
|
+
} else if (!sharpnessOk) {
|
|
167
|
+
guidanceKey = "dcv1.steady";
|
|
168
|
+
} else {
|
|
169
|
+
guidanceKey = "dcv1.hold_still";
|
|
170
|
+
}
|
|
171
|
+
return { at: timestampMs, quad, plausible, p: this.p, guidanceKey, ready, stableTicks: this.streak };
|
|
172
|
+
}
|
|
173
|
+
edgeClipped(quad, frameWidth, frameHeight) {
|
|
174
|
+
const margin = this.t.EDGE_CLIP_MARGIN_PX;
|
|
175
|
+
const pts = [quad.topLeft, quad.topRight, quad.bottomRight, quad.bottomLeft];
|
|
176
|
+
return pts.some(
|
|
177
|
+
(p) => p.x <= margin || p.y <= margin || p.x >= frameWidth - margin || p.y >= frameHeight - margin
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export {
|
|
183
|
+
ASPECT_BANDS,
|
|
184
|
+
aspectBand,
|
|
185
|
+
MR_DOC_CATALOG,
|
|
186
|
+
DOC_DETECT_WIDTH,
|
|
187
|
+
DOC_GATE_V1_DEFAULTS,
|
|
188
|
+
quadAreaFrac,
|
|
189
|
+
quadIou,
|
|
190
|
+
quadBounds,
|
|
191
|
+
DocGateV1
|
|
192
|
+
};
|
|
193
|
+
//# sourceMappingURL=chunk-WY6U7AIK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/doc/doc-catalog.ts","../src/core/doc/doc-gate-v1.ts"],"sourcesContent":["// @sekyro/web/core — MR document catalog (Task 33 / D1 docTypes\n// facet, findings §4 verbatim). Pure DATA the hosted guided scanner reads\n// (which sides to prompt, where the MRZ band sits, portrait side, physical\n// aspect) and Phase 7's capability matrix joins. `docType` strings stay\n// VERBATIM with kyc-docs-v2's `document_type` — the doc executor branches on\n// them, no rename layer. `carte_residence` is intentionally ABSENT (DOC-6:\n// residence permits are out of MVP; the executor fails them `unreadable`).\n\n/** Physical geometry the scanner frames (ISO/IEC 7810 / ICAO 9303). */\nexport type DocFormat = \"ID-1\" | \"ID-3\";\n\nexport type DocSide = \"recto\" | \"verso\" | \"single\";\n\nexport interface DocCatalogEntry {\n /** kyc-docs-v2 `document_type`, verbatim. */\n docType: \"carte_identite\" | \"passport\";\n format: DocFormat;\n /** Capture sequence the hosted doc step walks, in order. */\n sides: readonly DocSide[];\n /** MRZ geometry: td1 = 3×30 (ID-1 verso), td3 = 2×44 (passport data page). */\n mrz: \"none\" | \"td1\" | \"td3\";\n /** Which side carries the holder photo (drives portrait expectations). */\n portraitSide: DocSide;\n /** Physical width/height ratio of the framed page. */\n aspectRatio: number;\n /** Where the subject identifier (NNI) is parsed from, when the doc carries one. */\n identifierSource?: \"mrz\";\n}\n\n/**\n * Plausibility aspect bands per format (Task 34 gate input): ID-1 cards are\n * 85.6×53.98mm → 1.586; an ID-3 passport DATA PAGE frames at ≈125×88mm → 1.42\n * (wider tolerance — page curl and hinge shadow skew the detected quad).\n */\nexport const ASPECT_BANDS: Record<DocFormat, { center: number; tolerance: number }> = {\n \"ID-1\": { center: 1.586, tolerance: 0.15 },\n \"ID-3\": { center: 1.42, tolerance: 0.2 },\n};\n\n/** Aspect band for a format — helper the DocGateV1 plausibility check calls. */\nexport function aspectBand(format: DocFormat): { center: number; tolerance: number } {\n return ASPECT_BANDS[format];\n}\n\n/** The MR docTypes facet (findings §4). Order = the hosted picker's display order. */\nexport const MR_DOC_CATALOG: readonly DocCatalogEntry[] = [\n {\n docType: \"carte_identite\",\n format: \"ID-1\",\n sides: [\"recto\", \"verso\"],\n mrz: \"td1\", // verso; carries the NNI with ICAO check digits (findings §6)\n portraitSide: \"recto\",\n aspectRatio: 1.586,\n identifierSource: \"mrz\",\n },\n {\n docType: \"passport\",\n format: \"ID-3\",\n sides: [\"single\"],\n mrz: \"td3\",\n portraitSide: \"single\", // data page\n aspectRatio: 1.42,\n identifierSource: \"mrz\",\n },\n];\n","// DocGateV1 (Task 34) — the document twin of FaceGateV3: plausibility gate\n// (min-area / aspect band / edge-clip) + quality (sharpness/exposure on the\n// quad crop, same frame-quality measures) + an IoU stability streak. The gate\n// NEVER fires on the Task-20 spike's failure modes (too-far/degenerate quad,\n// low-contrast lock, chip-lock, edge-crop) — each yields its taxonomy guidance\n// key (`dcv1.*`) instead, mirroring the face gate's no-luck-firing posture.\n//\n// 100% deterministic: the clock is a tick INPUT (timestampMs); nothing here\n// reads Date.now or performance.now, and detection cost lives OUTSIDE (the\n// session schedules scanic at the device profile's cadence).\nimport { EXPOSURE_BAND } from \"../v3/frame-quality\";\nimport { aspectBand, type DocFormat } from \"./doc-catalog\";\n\n/** Detect-frame width the session downscales to (findings §1: 25ms median).\n * Lives HERE (not detector-scanic.ts) so the session can import it without\n * statically pulling the scanic chunk into host bundles. */\nexport const DOC_DETECT_WIDTH = 480;\n\nexport interface DocQuadPoint {\n x: number;\n y: number;\n}\n\n/** Detected document quad, image-space pixels (scanic CornerPoints shape). */\nexport interface DocQuad {\n topLeft: DocQuadPoint;\n topRight: DocQuadPoint;\n bottomRight: DocQuadPoint;\n bottomLeft: DocQuadPoint;\n}\n\nexport interface DocGateThresholds {\n /** Quad area / frame area below which the doc is too far. */\n MIN_AREA_FRAC: number;\n /** A corner this close to (or past) a frame edge counts as clipped. */\n EDGE_CLIP_MARGIN_PX: number;\n /** Consecutive stable plausible+quality ticks required to fire. */\n STABLE_TICKS: number;\n /** Quad-bbox IoU with the previous tick at/above which the streak holds. */\n IOU_STABLE_MIN: number;\n /** Integrator slopes, per second — mirrors FaceGateV3's gentle drain. */\n RISE_PER_S: number;\n FALL_PER_S: number;\n /** Quality guidance trigger levels (same semantics as the face gate). */\n GUIDE_EXPOSURE: number;\n GUIDE_SHARPNESS: number;\n /** ID-3 (MRZ-band mode): band width / frame width floor. */\n ID3_BAND_MIN_WIDTH_FRAC: number;\n /** ID-3 (MRZ-band mode): band aspect (w/h) sanity bounds. */\n ID3_BAND_ASPECT_MIN: number;\n ID3_BAND_ASPECT_MAX: number;\n}\n\n/** Frozen defaults (plan Task 34 — do not retune without a golden update). */\nexport const DOC_GATE_V1_DEFAULTS: Readonly<DocGateThresholds> = Object.freeze({\n MIN_AREA_FRAC: 0.15,\n EDGE_CLIP_MARGIN_PX: 4,\n STABLE_TICKS: 3,\n IOU_STABLE_MIN: 0.85,\n RISE_PER_S: 0.9,\n FALL_PER_S: 0.25,\n GUIDE_EXPOSURE: 0.6,\n GUIDE_SHARPNESS: 0.35,\n ID3_BAND_MIN_WIDTH_FRAC: 0.55,\n ID3_BAND_ASPECT_MIN: 4,\n ID3_BAND_ASPECT_MAX: 40,\n});\n\nexport interface DocGateQuality {\n /** frame-quality.sharpness on the quad crop. */\n sharpness: number;\n /** frame-quality.exposure on the quad crop. */\n exposure: number;\n /** Mean crop luma — picks more_light vs too_bright. */\n meanLuma: number;\n}\n\nexport interface DocGateTickInput {\n timestampMs: number;\n quad: DocQuad | null;\n quality: DocGateQuality;\n frameWidth: number;\n frameHeight: number;\n}\n\n/** Per-tick output — the scanner chrome contract. */\nexport interface DocGateTick {\n at: number;\n quad: DocQuad | null;\n /** Geometry passed: present × big enough × in frame × right shape. */\n plausible: boolean;\n /** Integrated progress 0..1 (the overlay ring). */\n p: number;\n /** dcv1.* message key (host translates; never prose). */\n guidanceKey: string;\n /** Fire signal: plausible × quality × STABLE_TICKS consecutive high-IoU ticks. */\n ready: boolean;\n /** Current stability streak length (diagnostics/telemetry). */\n stableTicks: number;\n}\n\nconst clamp01 = (value: number) => Math.min(1, Math.max(0, value));\n\n/** Shoelace area of the quad as a fraction of the frame area. */\nexport function quadAreaFrac(quad: DocQuad, frameWidth: number, frameHeight: number): number {\n const pts = [quad.topLeft, quad.topRight, quad.bottomRight, quad.bottomLeft];\n let doubled = 0;\n for (let i = 0; i < 4; i++) {\n const a = pts[i] as DocQuadPoint;\n const b = pts[(i + 1) % 4] as DocQuadPoint;\n doubled += a.x * b.y - b.x * a.y;\n }\n const frameArea = frameWidth * frameHeight;\n return frameArea > 0 ? Math.abs(doubled / 2) / frameArea : 0;\n}\n\n/** IoU of the two quads' axis-aligned bounding boxes (stability metric). */\nexport function quadIou(a: DocQuad, b: DocQuad): number {\n const boxA = bbox(a);\n const boxB = bbox(b);\n const x1 = Math.max(boxA.x1, boxB.x1);\n const y1 = Math.max(boxA.y1, boxB.y1);\n const x2 = Math.min(boxA.x2, boxB.x2);\n const y2 = Math.min(boxA.y2, boxB.y2);\n const inter = Math.max(0, x2 - x1) * Math.max(0, y2 - y1);\n const areaA = (boxA.x2 - boxA.x1) * (boxA.y2 - boxA.y1);\n const areaB = (boxB.x2 - boxB.x1) * (boxB.y2 - boxB.y1);\n const union = areaA + areaB - inter;\n return union > 0 ? inter / union : 0;\n}\n\n/** Axis-aligned bounds (width/height) of a quad — the ID-3 band-mode geometry. */\nexport function quadBounds(quad: DocQuad): { width: number; height: number } {\n const box = bbox(quad);\n return { width: box.x2 - box.x1, height: box.y2 - box.y1 };\n}\n\nfunction bbox(quad: DocQuad): { x1: number; y1: number; x2: number; y2: number } {\n const pts = [quad.topLeft, quad.topRight, quad.bottomRight, quad.bottomLeft];\n return {\n x1: Math.min(...pts.map((p) => p.x)),\n y1: Math.min(...pts.map((p) => p.y)),\n x2: Math.max(...pts.map((p) => p.x)),\n y2: Math.max(...pts.map((p) => p.y)),\n };\n}\n\n/** Orientation-agnostic aspect ratio (longer side pair / shorter side pair). */\nfunction quadAspect(quad: DocQuad): number {\n const top = Math.hypot(quad.topRight.x - quad.topLeft.x, quad.topRight.y - quad.topLeft.y);\n const bottom = Math.hypot(\n quad.bottomRight.x - quad.bottomLeft.x,\n quad.bottomRight.y - quad.bottomLeft.y,\n );\n const left = Math.hypot(quad.bottomLeft.x - quad.topLeft.x, quad.bottomLeft.y - quad.topLeft.y);\n const right = Math.hypot(\n quad.bottomRight.x - quad.topRight.x,\n quad.bottomRight.y - quad.topRight.y,\n );\n const horizontal = (top + bottom) / 2;\n const vertical = (left + right) / 2;\n if (horizontal <= 0 || vertical <= 0) return 0;\n return Math.max(horizontal, vertical) / Math.min(horizontal, vertical);\n}\n\nexport class DocGateV1 {\n private readonly t: DocGateThresholds;\n private readonly format: DocFormat;\n private p = 0;\n private lastTickAt: number | null = null;\n private lastQuad: DocQuad | null = null;\n private streak = 0;\n\n constructor(format: DocFormat, overrides: Partial<DocGateThresholds> = {}) {\n this.format = format;\n this.t = { ...DOC_GATE_V1_DEFAULTS, ...overrides };\n }\n\n reset(): void {\n this.p = 0;\n this.lastTickAt = null;\n this.lastQuad = null;\n this.streak = 0;\n }\n\n tick(input: DocGateTickInput): DocGateTick {\n const { quad, quality, timestampMs } = input;\n const dtSeconds =\n this.lastTickAt === null ? 0 : Math.max(0, (timestampMs - this.lastTickAt) / 1000);\n this.lastTickAt = timestampMs;\n\n // ── plausibility (geometry), taxonomy priority order ─────────────────────\n // ID-3 runs in MRZ-BAND mode (round 3): the \"quad\" is the detected MRZ\n // stroke band's rect — page-edge quads are unreliable on open booklets,\n // so the band IS the passport's detection signal. Geometry checks swap to\n // band semantics (width fraction instead of area; wide-aspect sanity).\n let geometryKey: string | null = null;\n if (quad === null) {\n geometryKey = \"dcv1.no_doc\";\n } else if (this.format === \"ID-3\") {\n const box = quadBounds(quad);\n const aspect = box.height > 0 ? box.width / box.height : 0;\n if (box.width < this.t.ID3_BAND_MIN_WIDTH_FRAC * input.frameWidth) {\n geometryKey = \"dcv1.come_closer\";\n } else if (this.edgeClipped(quad, input.frameWidth, input.frameHeight)) {\n geometryKey = \"dcv1.fit_document\";\n } else if (aspect < this.t.ID3_BAND_ASPECT_MIN || aspect > this.t.ID3_BAND_ASPECT_MAX) {\n geometryKey = \"dcv1.wrong_shape\";\n }\n } else if (quadAreaFrac(quad, input.frameWidth, input.frameHeight) < this.t.MIN_AREA_FRAC) {\n geometryKey = \"dcv1.come_closer\";\n } else if (this.edgeClipped(quad, input.frameWidth, input.frameHeight)) {\n geometryKey = \"dcv1.fit_document\";\n } else {\n const band = aspectBand(this.format);\n const aspect = quadAspect(quad);\n if (Math.abs(aspect - band.center) > band.tolerance) geometryKey = \"dcv1.wrong_shape\";\n }\n const plausible = geometryKey === null;\n\n // ── quality on the quad crop (same measures as the face gate) ────────────\n const exposureOk = clamp01(quality.exposure) >= this.t.GUIDE_EXPOSURE;\n const sharpnessOk = clamp01(quality.sharpness) >= this.t.GUIDE_SHARPNESS;\n const qualityOk = exposureOk && sharpnessOk;\n\n // ── stability streak: consecutive plausible+quality ticks with high IoU ──\n if (plausible && qualityOk && quad !== null) {\n const held = this.lastQuad === null || quadIou(quad, this.lastQuad) >= this.t.IOU_STABLE_MIN;\n this.streak = held ? this.streak + 1 : 1;\n } else {\n this.streak = 0;\n }\n this.lastQuad = quad;\n\n // ── integrator (gentle drain, FaceGateV3 shape) ───────────────────────────\n const good = plausible && qualityOk;\n this.p = clamp01(this.p + (good ? this.t.RISE_PER_S : -this.t.FALL_PER_S) * dtSeconds);\n\n const ready = good && this.streak >= this.t.STABLE_TICKS;\n\n // Guidance: geometry first (taxonomy priority), then light, then blur,\n // then \"hold still\" while the stability streak builds / holds.\n let guidanceKey: string;\n if (geometryKey !== null) {\n guidanceKey = geometryKey;\n } else if (!exposureOk) {\n const bandMid = (EXPOSURE_BAND[0] + EXPOSURE_BAND[1]) / 2;\n guidanceKey = quality.meanLuma < bandMid ? \"dcv1.more_light\" : \"dcv1.too_bright\";\n } else if (!sharpnessOk) {\n guidanceKey = \"dcv1.steady\";\n } else {\n guidanceKey = \"dcv1.hold_still\";\n }\n\n return { at: timestampMs, quad, plausible, p: this.p, guidanceKey, ready, stableTicks: this.streak };\n }\n\n private edgeClipped(quad: DocQuad, frameWidth: number, frameHeight: number): boolean {\n const margin = this.t.EDGE_CLIP_MARGIN_PX;\n const pts = [quad.topLeft, quad.topRight, quad.bottomRight, quad.bottomLeft];\n return pts.some(\n (p) =>\n p.x <= margin ||\n p.y <= margin ||\n p.x >= frameWidth - margin ||\n p.y >= frameHeight - margin,\n );\n }\n}\n"],"mappings":"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","names":[]}
|