fix(camera): remove post-capture photo revalidation

This commit is contained in:
xsl
2026-10-16 16:01:25 +08:00
parent 6a628f837b
commit 5e07985b19
9 changed files with 54 additions and 67 deletions
+18
View File
@@ -0,0 +1,18 @@
'use strict';
const assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path'),vm=require('node:vm');
const source=fs.readFileSync(path.join(__dirname,'../js/app.js'),'utf8');
assert(!source.includes('checkStill'), 'no still-photo detection helper or call remains');
const fileFunction=source.slice(source.indexOf(' function readPhotoFile('),source.indexOf(' function showCamStatus('));
let read,load;const completed=[];
const ctx={camStep:0,camSeq:1,IS_MOCK:false,captureDiagnostics:[],Math,
FileReader:class{readAsDataURL(){this.result='data:image/jpeg;base64,fixture';read=()=>this.onload();}},
Image:class{constructor(){this.width=640;this.height=640;load=()=>this.onload();}},
document:{createElement(){return {getContext(){return {drawImage(){}};},toDataURL(){return 'saved-jpeg';}};}},
onPhotoCaptured(url){completed.push(url);},toast(){throw Error('unexpected failure');}};
vm.createContext(ctx);vm.runInContext(fileFunction,ctx);
for(let step=0;step<3;step++){ctx.camStep=step;ctx.readPhotoFile({});read();load();assert.equal(ctx.captureDiagnostics[step].check_stage,'none');assert.equal(completed.length,step+1);}
ctx.readPhotoFile({});read();ctx.camSeq++;load();assert.equal(completed.length,3,'canceled file callback cannot advance');
const Diagnostics=require('../lib/capture-diagnostics');
const normalized=Diagnostics.normalize({version:'test',views:{front:{mode:'live',check_stage:'preview'},left:{mode:'file',check_stage:'none'},right:{check_stage:'invented'}}});
assert.equal(normalized.views.front.check_stage,'preview');assert.equal(normalized.views.left.check_stage,'none');assert.equal(normalized.views.right.check_stage,undefined);
console.log('PASS: no saved-photo recheck, file captures advance directly, stale callbacks ignored, preview-only diagnostics explicit');
+13 -3
View File
@@ -96,6 +96,14 @@ try {
assert.equal(await page.locator('#s-cam.cam-manual').count(), 0, 'real automatic pipeline, not manual bypass');
await page.evaluate(() => {
window.__holdSamples = []; window.__hints = []; window.__evaluations = [];
window.__stillDetections = 0;
const detectFaces = faceapi.detectAllFaces;
faceapi.detectAllFaces = function(input, options) {
// Fixture setup uses sample-file images to align the synthetic video;
// captured/uploaded JPEGs are data URLs and must never be detected again.
if (input instanceof HTMLImageElement && input.src.startsWith('data:')) { window.__stillDetections++; throw Error('post-capture detection must not run'); }
return detectFaces.call(this, input, options);
};
const evaluate = CaptureQuality.evaluate;
CaptureQuality.evaluate = function(d, options) {
const result = evaluate(d, options);
@@ -118,7 +126,7 @@ try {
assert.equal(await page.evaluate(() => window.__voice.some(row => /^hint-turn-|^step[23]-|^shot-done$/.test(row.id))), false, 'no turn/next-step/completion audio before a successful front photo');
await page.evaluate(() => { window.__cameraFixture.light(.6); return window.__cameraFixture.set('C3_180.jpg'); });
await page.waitForFunction(()=>document.querySelector('#cam-hint').textContent.includes('建议到光线明亮处拍摄'));
// Keep the dim sample through countdown AND final JPEG validation.
// Keep the dim sample through countdown and capture, without a JPEG recheck.
try { await page.waitForFunction(() => document.querySelector('#cam-step').textContent.includes('2/3')); }
catch (error) {
console.log(await page.evaluate(async () => {
@@ -151,14 +159,16 @@ try {
for (const key of ['front', 'left', 'right']) {
const row = body.capture_diagnostics.views[key];
assert.equal(row.mode, 'live'); assert.equal(row.direction_checked, true);
assert.equal(row.check_stage, 'preview');
assert(row.score >= (key==='front'?.25:.3));
if(key==='front') assert(row.luma < 110, 'dim JPEG still passes final validation');
if(key==='front') assert(row.luma < 110, 'dim preview still permits capture');
assert(Math.abs(row.roll) <= 22);
}
assert(Math.abs(body.capture_diagnostics.views.front.yaw) <= 18);
assert(body.capture_diagnostics.views.left.yaw <= -24 && body.capture_diagnostics.views.left.yaw >= -84);
assert(body.capture_diagnostics.views.right.yaw >= 24 && body.capture_diagnostics.views.right.yaw <= 84);
assert(await page.evaluate(() => document.querySelector('#cam-video').srcObject === null), 'camera released after success');
assert.equal(await page.evaluate(() => window.__stillDetections),0,'all three photos proceed without still-image detection');
assert.deepEqual(errors, []);
console.log('PASS: actual local models + automatic three-view capture + final JPEG revalidation; invalid front stays on step 1 with neutral audio, no early turn; actual decoded shot-done finishes before side guidance; wrong-direction frames blocked; approved samples/camera cleanup; no paid call/backend writes');
console.log('PASS: actual local models + automatic three-view capture without JPEG revalidation; invalid preview stays on its step, neutral audio; shot-done finishes before side guidance; camera cleanup; no paid call/backend writes');
} finally { await browser.close(); }
-1
View File
@@ -15,7 +15,6 @@ try {
window.__countdown = {
start: function(step) { camStep=step; currentScreen='s-cam'; camLive=true; shutterLock=false;
captureFromVideo=function(){window.__shots.push(step);return 'fixture';};
checkStill=function(){return Promise.resolve(null);};
onPhotoCaptured=function(){window.__completed.push(step);}; doCapture(); },
again:doCapture, cancel:leaveCamera,
busy:function(){return shutterLock;}
+1 -1
View File
@@ -123,7 +123,7 @@ assert.equal(Diagnostics.assess(raw).views.right.status, 'valid');
const normalized = Diagnostics.normalize({ version: Quality.config.version, secret: 'discard', views: { front: { mode: 'live', yaw: 1, score: .9, luma: Infinity, secret: 'discard' }, left: { mode: 'unchecked', reason: 'model-unavailable' } } });
assert.equal(normalized.policy.capture.holdMs, 450);
assert.equal(normalized.policy.capture.minFrames, 2);
assert.equal(normalized.version, '2026-10-16-light-advisory-v10');
assert.equal(normalized.version, '2026-10-16-preview-only-v11');
assert.equal(normalized.views.front.luma, undefined);
assert(!JSON.stringify(normalized).includes('discard'));
assert.equal(Diagnostics.normalize({ version: '<script>' }).version, 'unknown');
+1
View File
@@ -43,6 +43,7 @@ assert.equal(pattern, 30);
// are stubs; the relaxed shared hold policy now requires 450ms/2 frames.
let now = 10000, buzzes = 0, captures = 0;
const state = {
captureDiagnostics: [],
missFrames: 0, MISS_FRAMES_MAX: 3, FRONT_MISS_FRAMES_MAX: 6, frontFaceSeen:false, camVideo: { videoHeight: 600 }, FACE_NEAR: .30, FACE_FAR: .68,
faceOffsetX: () => 0, CENTER_TOL: .18, CAM_STEPS: [{ target: 'front' }], camStep: 0,
HeadPose: { check: () => ({ ok: true }) }, sideYawFlip: false, faceLuma: () => 100,