| Rev |
Age |
Author |
Path |
Log message |
Diff |
| 37447 |
5 h 4 m |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Selenium: one browser per brand instead of one per pool
Oppo and Realme each ran secondary and tertiary as separate @Scheduled jobs, so
each brand opened two ChromeDrivers at once and the fleet ran four. Each driver
tree costs roughly 850MB. This box co-hosts a 9.4GB tomcat with ~3GB available
and has been OOM-killed twice this month -- tomcat was the victim both times, so
peak concurrency is the thing that matters here.
Combined into one job per brand. Nothing downstream changes: the two pools differ
only in which named query fills them, and both already funnel into the same
updateActivationDate -> checkWarranty -> saveActivation path. They are disjoint by
construction (secondary excludes anything with a FofoLineItem, tertiary is
FofoLineItem-based); distinct() is insurance, not a fix for a known overlap.
Sizing matters, because merging SERIALISES work that used to run in parallel and
keeping the old batch sizes would quietly cost throughput. Measured post-cap at
10.2s/imei (oppo, down from 14.6 after r37445) and 14.2s/imei (realme), solving
M * 86400 / (300 + M*t):
oppo 2 parallel jobs x10 = 4,299/day -> merged 15+15 = 4,277/day (parity)
realme 2 parallel jobs x10 = 3,910/day -> merged 10+10 = 2,959/day (-24%)
Oppo is sized to hold parity because it is already short of its 4,798/day need.
Realme is left at 20 -- it needs 2,243/day, so it can absorb the dip in exchange
for shorter batches and a shorter-lived browser.
What this saves and does not save: total driver-SECONDS are roughly unchanged,
which is the point of resizing. PEAK concurrent drivers halves from 4 to 2.
Also skips starting a browser at all when both pools come back empty -- currently
never true, but it costs nothing and a browser launched to do nothing is pure
waste on this box.
checkOppoImeiStatus/Tertiary and the realme equivalents are left in place for
manual invocation; they are simply no longer scheduled. |
|
| 37446 |
5 h 13 m |
amit |
/trunk/ |
Per-brand batch sizes, and stop chrome forking a GPU process it cannot use
maxResults was hardcoded in the shared repository methods, so Oppo and Vivo were
forced to the same secondary batch (10) and all three to the same tertiary (10).
It is now a parameter, set per brand at the call site.
Sizing is arithmetic, from measured IN-BATCH per-imei time. Solving
M * 86400 / (300 + M*t) = needed/day:
brand needed/day t M required set to
Vivo 9,407 0.8s 36 50 clears, ~12,700/day
Realme 1,973 13.4s 10 10 was 5 = ~1,177/day, short
Oppo 4,798 14.6s 88 10 HELD, see below
Correcting an earlier measurement of mine: I reported Vivo at 13.6s per imei and
concluded its backlog could not be cleared. That averaged across the ~300s idle
gaps BETWEEN batches. In-batch it is 0.8s -- Vivo is 17x faster than I said, is
idle ~97% of the time, and 50 clears its pool comfortably. There is no wait in
the Vivo path; it is simply fast.
Oppo is deliberately NOT raised. At 14.6s it would need M=88, which means
20-minute batches and near-permanent chrome sessions. But that 14.6s predates the
retry cap (r37445), which cuts exhausted imeis from 20 attempts to 7 and should
drop it sharply. Re-measure before sizing Oppo, rather than guessing high on a
box with 3GB free.
Also: --disable-gpu, --disable-dev-shm-usage, --disable-software-rasterizer on
both selenium tasks. Headless needs no GPU yet chrome forks a gpu-process per
browser -- 6 were alive across the fleet, pure overhead. No behaviour change.
Batch size does not raise peak concurrency (fixedDelay means one batch per job at
a time, so never more than 4 drivers). It raises DUTY CYCLE, which converts
chrome's footprint from intermittent to sustained. That matters here: tomcat is
9.4GB, available is ~3GB, and the two OOM kills this month both took tomcat.
Cron-only deploy. The dao signature change has no callers outside cron. |
|
| 37445 |
6 h 53 m |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Oppo/Realme: cap captcha retries per tick at 7 and 12, measured per brand
Most captcha work goes to imeis that never succeed. Measured over the last
window: oppo 6,245 attempts for 308 successes, realme 13,842 for 399. The bulk
is imeis grinding all 20 attempts in one already-refused session and getting
nothing -- oppo 279 of them, realme 610.
The imei is NOT abandoned. A captcha failure is a technical failure, not an
answer, so it stays pending and the next tick retries it in ~5 minutes with a
fresh page and session -- which the data suggests beats continuing in a session
the widget has refused seven times. This caps grinding, not retrying.
Per-brand caps, because the distributions genuinely differ:
OPPO cap 7 97.7% of successes kept, 58.4% of work saved (3,648 attempts)
Pre-glide, 33% of oppo's successes came from attempts 8-20 and 20 was the
right number. Post-glide (r37440) that is 2%: the drag lands first or second
try. cap 5 keeps 95.5%/saves 67.7%, cap 10 keeps 99.0%/saves 44.8%.
REALME cap 12 96.0% kept, 35.7% saved (4,937 attempts)
NOT 7. Realme has no glide, so its successes still spread to attempt 10+ and
a cap of 7 would cost it 16.5%. It also burns more than twice oppo's work, so
the gentler cap still saves more in absolute terms.
Combined: 8,585 of 20,087 attempts saved, ~43% less browser work, for ~3% fewer
successes per tick -- and those imeis come back next tick anyway. Less Chrome
work matters on this box: it co-hosts a 9.4GB tomcat, has 3.5GB free and has been
OOM-killed twice this month.
The two caps are independent constants and must not be synced. If glide is ported
to realme, expect its curve to shift left as oppo's did and its cap can drop to
~7 -- but measure it, do not assume it.
Windows are ~2h (oppo's only ~30 min post-glide), so the right numbers may drift.
Worth re-reading the attempt histogram after a full day. |
|
| 37444 |
8 h 4 m |
ranu |
/trunk/profitmandi-fofo/src/main/webapp/WEB-INF/views/ftl/ |
rbm drr dashboard new ui looks |
|
| 37443 |
8 h 7 m |
ranu |
/trunk/profitmandi-dao/src/main/java/com/spice/profitmandi/dao/model/ |
rbm drr dashboard new ui looks |
|
| 37442 |
8 h 12 m |
ranu |
/trunk/ |
rbm drr dashboard new ui looks |
|
| 37441 |
8 h 38 m |
ranu |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
bi access to gaurav mathur |
|
| 37440 |
12 h 13 m |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Oppo: slide the drag in eased sub-moves instead of jumping
Aim is solved and it did not finish the job: r37438 lands the ring 0-1px from the
hole on 103 of 104 drags and oppo still converts ~15%, with POSITION_MISMATCH on
the rest. So the widget is refusing drags that end in the right place.
Dingxiang scores trajectory and velocity as well as final position and returns the
same POSITION_MISMATCH for both, so our verdict cannot separate "wrong place" from
"wrong-looking movement". The circumstantial case for the latter:
oppo 0.91s hold, 2-3 discrete jumps -> 15%
realme 1.93s hold, slower and more -> 54-74%
and r37438 made oppo FASTER by removing the per-step screenshots, moving it further
from realme's profile rather than closer.
glide() traverses each leg as up to 18 small sub-moves on an ease-out curve
(1-(1-t)^2, quick then settling) with a 45ms pause between, so the pointer
describes a continuous decelerating path instead of teleporting.
Nothing about the DISTANCE changes -- it is still exactly what the derived ratio
says, and verified to land on it precisely: a 116px leg becomes
[16,15,13,13,11,10,9,8,6,6,4,3,1,1] and sums to 116. Negative legs work the same
way for overshoot correction.
To be explicit, since this is the thing not to get wrong: the ratio remains fully
derived. It starts at zero and comes only from measured movement, per attempt and
per step. Splitting a leg into sub-moves gives MORE measured legs, not fewer, so
the estimate sharpens rather than being replaced by an assumption. No constant is
introduced -- MAX_GLIDE_STEPS, GLIDE_PX_PER_STEP and GLIDE_PAUSE_MS describe the
path, not the relationship between mouse px and ring px.
Expected hold ~1.7s against realme's 1.93s. Oppo only.
This is a hypothesis test, not a known fix. POSITION_MISMATCH rate answers it in
one tick; if it does not move, drag dynamics is eliminated and the remaining
suspect is Hough picking the wrong circle pair on oppo's imagery, which needs
visual evidence rather than another code change. |
|
| 37439 |
12 h 28 m |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Oppo: drop the stale nudgeFor javadoc that still referenced ASSUMED_RATIO
Comment only, no code change.
nudgeFor() and ASSUMED_RATIO were removed in r37438, but the javadoc describing
them was left orphaned above ALIGNED_PX, still explaining how to size a probe from
an assumed px-per-px. That is precisely the thing not to reintroduce, and a stale
comment arguing for it is an invitation to do so.
Replaced with a note on why there is no assumed ratio: it is derived from real
movement on every attempt and re-derived on every step within it. The constant was
tuned twice (1.7 -> 0.67 -> 1.7) against measurements contaminated by the leaked
slider press, and never helped. The widget's px-per-px genuinely varies by
environment (1.4-1.5 in production, 1.1-1.2 locally) and by brand, which is the
reason it has to be measured rather than assumed. |
|
| 37438 |
19 h 2 m |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Oppo: aim by tracking the ring element, not by re-detecting circles
r37437's closed loop could not close. Production, 13 minutes:
step 0 (probe) ok 36
lost the circles 32 (25 of them on step 1, right after the main move)
aligned within 2px 0
The probe worked and derived sane ratios (0.75-1.00), then the next measurement
returned null nearly every time and the loop bailed.
The cause is structural, not a tuning miss. getMatCircles2 needs Hough to resolve
TWO circles of similar radius, and as the ring closes on the hole they overlap and
stop being two. The measurement dies exactly at convergence, so no loop built on
"distance between two detected circles" can ever verify success. The old refine()
hit the same wall and silently released, which is why it never helped either.
So detection is now used ONCE, for what it is good at -- locating the hole while
the two are still far apart -- and from then on we track the ring's OWN element,
dx_captcha_basic_sub-slider_, whose rect stays readable however close it gets.
Validated on the live widget before committing:
bg element page x=556, width 288 CSS
ring centre page 598 -> 42 relative to bg
dark target 138 relative to bg -> page 694, gap 96px
The ring sits in the same 0-288 CSS space as OpenCV's x values, so bg.x + circleX
is directly comparable to the ring's position. That holds because getMatCircles2
crops by CSS coordinates and chrome runs --force-device-scale-factor=1. A 96px gap
matches the range production logs as "Distance is".
Also validated earlier that the ring's rect tracks a drag cleanly the whole way in
(+10 mouse -> +11 ring, repeatably), which is the property the circle detection
lacks.
The ratio is still derived per move and is now signed, so overshoot corrects itself
rather than needing to be detected. No screenshot or Hough pass per step either, so
the drag is quicker as well.
Oppo only. Realme is converting 78% and is not touched. |
|
| 37437 |
19 h 15 m |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Oppo: derive the drag ratio from every move, and survive a stale slider
Two changes, both oppo only. Realme is converting 82% and is left alone.
1. No assumed ratio -- aim() is a closed loop.
ASSUMED_RATIO only ever sized the FIRST probe, but the old path then derived
px-per-px once from that single probe and trusted it for the main move and the
correction alike. refine() re-measured the gap but reused the same ratio and
never checked whether its own correction landed, so one noisy probe poisoned
everything after it.
aim() seeds with a small fixed 12px nudge -- small enough that it cannot cross
the hole -- then re-derives the ratio from what each move actually achieved and
blends it with the running estimate. Up to 5 steps, stopping as soon as the gap
is within ALIGNED_PX.
It can also see overshoot, which a single pass cannot: the gap is |c1 - c2| and
cannot tell "not there yet" from "gone past", but if a move makes the gap GROW
we have crossed the hole, and the next step flips sign.
This removes the constant rather than retuning it, so it is immune to the scale
differences we measured between environments -- 1.1-1.2 px per px locally
against 0.67-0.86 in production. Supersedes r37433 and r37436 for oppo.
calibrate(), refine() and nudgeFor() are now dead and removed.
2. Re-find the slider when it goes stale instead of dropping the imei.
54 of 284 oppo presses (19%) died on StaleElementReferenceException, against 0
of 90 on realme -- the first asymmetry found between the two. Every one aborted
the attempt and forced a full page reload.
The logs place it exactly: "Margin Left before-" succeeds, then the catch fires,
so the handle goes stale between that read and moveToElement. It happens on
retry iterations (before- 4px, 1px), right after the doubleClick refresh swaps
the widget DOM -- the element found at the top of the attempt is already
detached by the time we act on it.
Now re-finds and retries once. The margin-left reads are also wrapped: they are
diagnostics and must never be the reason an attempt is abandoned.
Neither change is verified in production yet. The stale fix is the one with a
measured asymmetry behind it; aim() is a design fix for a mechanism that was
provably fragile, not a fix for a measured cause of oppo's 13%. |
|
| 37436 |
19 h 19 m |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Oppo: back to ASSUMED_RATIO 1.7; 0.67 was fitted to contaminated data
r37433 set oppo to 0.67 on the strength of 3,694 production calibrations. It did
not help: 14% before, 13% after, across two independent windows.
Those 3,694 measurements were all taken while the slider press was leaking
between attempts (fixed in r37435), so 79% of them started from a corrupted
pointer state. The value was fitted to noise.
With that confound removed the clean comparison is realme: identical widget,
identical calibration code, ASSUMED_RATIO 1.7, converting 82% of drags against
oppo's 13%. Measured post-fix on comparable gaps:
Oppo 0.67 -> probe 52px, closes 37% of a 122px gap -> 13% success
Realme 1.7 -> probe 31px, closes 11% of a 105px gap -> 82% success
The smaller probe is the one that lands. A probe that eats a third of the gap
risks crossing the target during calibration, and the gap is measured as
|c1 - c2|, which cannot distinguish "not there yet" from "gone past" -- so the
derived ratio comes back wrong and the main move is wrong with it.
This converges on realme's value but is not a blind sync: it is the one
configuration observed to work on this widget once the press bug was gone. The
constants stay per-brand and independently documented.
Not a certainty. r37435 improved realme by 18 points and left oppo flat, so
something about oppo may still differ; this tests the cheapest remaining
explanation before going after circle detection. |
|
| 37435 |
19 h 29 m |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Oppo/Realme: always release the slider; 79% of presses were left held
Found by following the observation that the dragger repositions itself on click.
clickAndHold() is issued at the top of every attempt, but release() sits inside
the moveByOffset > 0 branch. An attempt that fails circle detection, or produces
an unusable calibration, therefore leaves the mouse button DOWN.
Measured over 21 minutes of production:
Oppo 506 presses, 107 released -> 399 never released = 79%
Realme 247 presses, 58 released -> 189 never released = 77%
Actions is one instance per driver, created OUTSIDE the imei loop, so the stuck
press does not end with the attempt -- it leaks into the next attempt and on into
the next imei. The following clickAndHold then lands on an already-held button,
so the widget never repositions the handle and the drag simply continues from
wherever the pointer was abandoned.
That matches what the logs show. Validated locally against both live widgets:
a clean press repositions the handle +22px on the first pixel of movement (half
the 45px handle: moveToElement centres the pointer, the widget aligns to it), and
the transfer is then linear -- 1.10 piece px per mouse px on oppo, 1.20 on realme.
Production instead shows a jump of +0px on 31 oppo attempts and -34..-41px on
another 39, and oppo starts 13% of attempts with margin-left already >= 30px
(mean 9.2px) against realme's 0% (mean 3.2px). Those are the fingerprints of a
drag resuming rather than starting.
Tracks the press in a flag and releases on every exit: before the retry refresh,
and again after the loop so the widget-not-found break cannot carry a held button
into the next imei. Release is wrapped, so a failure to release is logged rather
than killing the batch.
Ruled out while getting here, so as not to re-chase: the reposition is NOT an
animation race (position is stable from t=0 to 1s despite transition:all), and it
is NOT asymmetric between the brands -- both snap +22px identically, so this bug
does not by itself explain oppo 14% vs realme 64%. Both brands should improve. |
|
| 37434 |
20 h 7 m |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Oppo: capture the captcha verify separately from the puzzle refetch
Diagnostic only, no behaviour change.
r37433's verdict logging shows all 58 captured oppo captcha responses are
/api/a (the puzzle fetch) and none are /api/v1 (the verify), while realme shows
/api/v1 on 20 of 25 failures. The tempting reading is that oppo's drag is never
submitted for verification at all.
That reading is not safe. __oppoCaptcha is last-captcha-call-wins, and the
widget refetches a puzzle immediately after a rejected verify. Oppo then waits
up to 5s for the error element before reading the signal -- ample time for the
refetch to overwrite the verdict. Realme reads off the lookup response instead,
which is why its verdict survives.
So "never verified" and "verified, rejected, then overwritten" both fit, and
they need opposite fixes: the first is a broken gesture, the second is aim.
Keeps the last /api/v1 body in its own variable plus a count, so the failure
line now reads verifies=N lastVerify=... and the two are distinguishable on the
next tick.
Ruled out already, so as not to re-chase: drag duration. Time held between
"Distance is" and "Move click performed" is oppo 1.4s mean / 2s max against
realme 1.6s / 3s -- effectively identical, so the widget is not abandoning
oppo's drag for slowness. |
|
| 37433 |
22 h 21 m |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Oppo/Realme: split the slider tuning, and give Oppo its own failure verdict
The two classes drive the same dingxiang widget and have been edited in lockstep,
but they do not behave alike: measured over the last 11 hours as successes per
completed drag, oppo runs 11-17% and realme 60%. A change proven on one is not
evidence for the other, so the tuning constants are now documented as per-brand
and must not be synced.
Correcting the record while I am here: oppo has NOT regressed. Earlier "43%" and
"48%" figures were successes over FAILURES, and a small widget-verdict sample --
not successes over drags. On the consistent denominator oppo has been 12-15%
throughout, across r37422, r37424 and r37432 alike.
Oppo: ASSUMED_RATIO 1.7 -> 0.67.
It only sizes the calibration probe: nudge ~= gap / (2 * ratio), intended to
close half the gap. 1.7 came from one hand measurement. Production says
otherwise -- 3,694 oppo calibrations average 0.67 (realme 0.70 over 1,683), so
the probe has been closing about a fifth of the gap, not a half, and the ratio
is then derived from that needlessly small and noisy sample. Mean gap is 120px
for both, and 28% of oppo calibrations already overshoot.
Oppo: log the captcha widget's verdict on the failure branch.
Realme has carried this since r37428 and oppo never did, which is why realme's
failures name POSITION_MISMATCH and oppo's 85% say nothing. Without it there is
no way to tell a bad drag from a widget that never rendered.
Realme: comment only, no behaviour change. Its ratio is wrong in the same way,
but it is wrong while converting 60%, so oppo carries the corrected value first
and realme follows only if it helps. |
|
| 37432 |
1 d 0 h |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Oppo: reinstall the capture hook after every navigation
Oppo installed the response hook after the first driver.get only. The other
three navigations -- the "field already filled" reset, the widget-not-found
reload, and the per-imei reset at the end of the loop -- did not.
Every navigation destroys the JS context, so __oppoCapInstalled, __oppoResp and
__oppoCaptcha are all wiped and never recreated. Oppo therefore runs blind from
the second imei of a batch onwards, permanently.
Live evidence from the 00:49-01:03 window after deploying r37431:
Success at attempt 12
Activation lookup for 1 <- the first imei, right after the initial get
No getDeviceInfo response 11
captcha said null on all 11
Both variables null on every miss is the signature: not a failed drag, an
absent hook. Realme does reinstall at all four of its navigations (r37423) and
in the same window captured 30 lookups from 30 successes, on the same box with
the same hook code -- so the hook itself is fine.
Deliberately not touching Oppo's success signal in this change. It still infers
success from the bar-inform error element being absent, which is the false
success pattern r37426 replaced for Realme, and it may well need the same
treatment. But the null hook explains the misses on its own, and changing both
at once would make the next measurement unattributable.
Not compile-verified: the local build was interrupted. The change is three calls
to an existing private method in the same class. |
|
| 37431 |
1 d 1 h |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Realme/Oppo: capture the lookup off Response.prototype, not window.fetch
Realme solves its captcha well -- the widget's own verify endpoint says
success:true on 526 of 829 attempts, 63% -- and records nothing. Every one of
those solved captchas is logged as "Failed" and the imei re-queued.
The lookup is not missing, it is invisible. realme's client calls Nuxt's $fetch
(ofetch), which takes its reference to globalThis.fetch when its bundle is
evaluated. That happens before driver.get() returns, so by the time
installResponseCapture wraps window.fetch the client is already holding the
original. r37425 added that wrapper and could never have worked.
Verified on the live page: the same $fetch request is invisible to a
window.fetch wrapper and visible to a Response.prototype one.
Response.prototype.json/.text are resolved per call, so patching them catches
any client whenever it took its fetch reference -- the same reason the
XMLHttpRequest.prototype.open patch has always worked (it is how the captcha
verdict gets captured today).
The clone is read with the ORIGINAL reader. Calling the patched one recurses
forever and hangs the page; that is not theoretical, it hung a test run.
Applied to Oppo as well. Its XHR path works today (673 lookups, 0 missed), so
this is defence against the same trap following any migration -- which is what
r37425 was reaching for and did not achieve.
Expected: Realme goes from 0 rows to roughly its captcha rate. Unchanged:
59% of attempts still never find the circles, same ceiling as Oppo. |
|
| 37430 |
1 d 5 h |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Realme: widen the lookup match and report what the page actually called
The captcha is being solved -- the widget returns success:true with a token --
but the lookup response is never captured, so awaitResponse times out and every
attempt records as failed. 19 'success' against 53 'failed' this hour with 0
lookups, while the widget's own verdicts are mostly success:true.
Matches on the customer-api host as well as the active/check path, in case the
endpoint moved with the page rebuild. A failed attempt now also logs the
non-captcha URLs the page did call, so if it is still missed the log names what
to match on rather than leaving it to guesswork. |
|
| 37429 |
1 d 5 h |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Oppo: wait for the lookup response instead of reading it once
Oppo read window.__oppoResp immediately after the success signal, which races the
request that produces it: in the 20:00 hour 17 successes yielded 16 'no
getDeviceInfo response' and a single lookup, against 673 lookups and 0 misses
earlier in the day.
Now polls for up to 8s, matching Realme (r37426). A failed read also reports the
captcha widget's own verdict, so a miss says whether the drag was refused
(POSITION_MISMATCH) or passed and the lookup simply did not arrive.
Widget verdicts confirm the calibration work: 14 success:true against 18
POSITION_MISMATCH is 43% real aim accuracy, up from ~9% before r37422. |
|
| 37428 |
1 d 5 h |
amit |
/trunk/profitmandi-cron/src/main/java/com/smartdukaan/cron/scheduled/ |
Oppo/Realme: record the captcha widget's own verdict alongside the lookup
r37426 made the lookup response the success signal, which is authoritative but
silent about WHY a drag was refused -- a timeout looks identical whether the aim
was wrong, the widget never rendered, or the page changed again.
The captcha widget answers that itself: it calls captcha-ind-sec.heytapmobile.com
and its replies carry a status and message. The hook now keeps the last such
response, and a failed attempt logs it, so 'Failed = 3' becomes 'Failed = 3
(captcha said: ...)'.
Note what the widget does NOT give us: its init call returns the puzzle's y
coordinate but not the x, which is the secret being protected. So the circle
detection still has to find the horizontal target -- there is no shortcut there,
and that remains the largest loss (60% of Oppo attempts never find the circles). |
|