Skip to content

Commit 0ecbc6b

Browse files
committed
fix: scan
1 parent 3be36b0 commit 0ecbc6b

1 file changed

Lines changed: 22 additions & 25 deletions

File tree

web/app/composables/useCardAutoScan.ts

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,20 @@ export interface UseCardAutoScanOptions {
3232
const FRAME_MS = 100
3333
/** Long edge of the downscaled frame sent for detection (more = sharper quad). */
3434
const PROC_EDGE = 640
35-
/** "No card" detections before re-arming after a shot (card removed). */
36-
const LOST_TICKS_REARM = 3
37-
/** Floor between two captures (anti double-shot). */
38-
const MIN_COOLDOWN_MS = 1500
35+
/**
36+
* Floor between two captures. Short on purpose: as soon as it elapses the next
37+
* confirmed card fires, even if the previous card is still in frame — no more
38+
* "remove the card to re-arm" gate (the cause of stuck "Présentez une carte").
39+
*/
40+
const MIN_COOLDOWN_MS = 600
3941
/**
4042
* How many shots we take in a burst — we keep the sharpest for OCR. Pikacheck-
41-
* style: a slightly larger burst over a longer window so the user can pivot
42-
* the card during capture (revealing a glary corner, exposing missing text…)
43-
* and the sharpest / clearest angle still wins.
43+
* style: a longer window so the user has time to pivot the card during capture
44+
* (revealing a glary corner, exposing missing text…) and the best angle wins.
4445
*/
45-
const BURST_COUNT = 3
46+
const BURST_COUNT = 4
4647
/** Gap between burst shots — wide enough for hand movement to expose new info. */
47-
const BURST_INTERVAL_MS = 280
48+
const BURST_INTERVAL_MS = 300
4849
/** Lerp weight (new vs previous) when tracking the displayed quad. */
4950
const SMOOTH_LERP = 0.5
5051
/** Drift above this fraction of the long edge ⇒ new scene, snap instead of lerp. */
@@ -97,7 +98,6 @@ export function useCardAutoScan(opts: UseCardAutoScanOptions) {
9798
let displayedCorners: Pt[] | null = null
9899
let pendingCorners: Pt[] | null = null
99100
let missTicks = 0
100-
let lostTicks = 0
101101
let lastCaptureAt = 0
102102

103103
let burstCorners: Pt[] | null = null
@@ -303,7 +303,6 @@ export function useCardAutoScan(opts: UseCardAutoScanOptions) {
303303
}
304304
const file = new File([blob], `card-${Date.now()}.jpg`, { type: 'image/jpeg' })
305305
lastCaptureAt = Date.now()
306-
lostTicks = 0
307306
void Promise.resolve(onCapture(file)).finally(finishCooldown)
308307
},
309308
'image/jpeg',
@@ -335,19 +334,20 @@ export function useCardAutoScan(opts: UseCardAutoScanOptions) {
335334
lastCorners = null
336335
displayedCorners = null
337336
quad.value = null
338-
if (phase.value === 'cooldown') {
339-
lostTicks += 1
340-
if (lostTicks >= LOST_TICKS_REARM) {
341-
phase.value = 'watching'
342-
}
343-
} else if (phase.value !== 'idle' && !capturing) {
337+
if (phase.value !== 'idle' && !capturing) {
344338
phase.value = 'watching'
345339
}
346340
}
347341
return
348342
}
349343
missTicks = 0
350344

345+
// Cooldown is now purely time-based — once it elapses, the next confirmed
346+
// quad fires regardless of whether the previous card left the frame.
347+
if (phase.value === 'cooldown' && Date.now() - lastCaptureAt >= MIN_COOLDOWN_MS) {
348+
phase.value = 'watching'
349+
}
350+
351351
// Voting: wait for two consecutive detections within VOTE_DRIFT_FRAC of
352352
// each other before trusting the result. Hand-shake / texture flicker
353353
// produces wildly varying quads and gets filtered here.
@@ -367,20 +367,18 @@ export function useCardAutoScan(opts: UseCardAutoScanOptions) {
367367
displayedCorners = smoothCorners(displayedCorners, confirmed, longEdge)
368368
const shaped = forceCardShape(displayedCorners)
369369
quad.value = [shaped[0]!, shaped[1]!, shaped[2]!, shaped[3]!]
370-
lostTicks = 0
371370

372371
lastCorners = corners
373372

374-
if (phase.value === 'cooldown' || capturing) {
373+
if (capturing) {
375374
return
376375
}
377376

378-
// No "hold steady" wait — Pikacheck-style. As soon as a card is confirmed
379-
// (vote passed) and we're past the cooldown, we shoot a burst. The user is
380-
// free to pivot/move the card during the burst; each shot uses the freshest
381-
// corners and the sharpest wins.
377+
// No "hold steady" wait and no "card must leave" gate — Pikacheck-style.
378+
// As soon as a card is confirmed (vote passed) and the short time cooldown
379+
// is past, we shoot a burst. The user is free to swap cards directly; each
380+
// burst shot uses the freshest corners so a pivot during capture wins.
382381
if (busy.value || Date.now() - lastCaptureAt < MIN_COOLDOWN_MS) {
383-
phase.value = 'watching'
384382
return
385383
}
386384

@@ -498,7 +496,6 @@ export function useCardAutoScan(opts: UseCardAutoScanOptions) {
498496
burstCorners = null
499497
burstShots = []
500498
burstAwaiting = 0
501-
lostTicks = 0
502499
quad.value = null
503500
ready.value = false
504501
phase.value = 'idle'

0 commit comments

Comments
 (0)