| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- /**
- * 票 39 真机走查脚本(puppeteer):粗→精影像交接。
- *
- * **本仓现状**:本地 puppeteer 不可用(`@puppeteer/browsers` 目录为空,`ERR_MODULE_NOT_FOUND`),
- * 故本票的真机证据由维护者授权的 DSH 浏览器逐项采集(读数见票面「证据」一节)。
- * 本脚本按既有走查写法保留,供修复 puppeteer 后一键复跑;逻辑与当时的手工采集**逐条对应**。
- *
- * **不做的事(诚实边界)**:
- * · 「全程无肉眼可辨的方形跳变」是**维护者的目视判据**,本脚本只提供可观测读数与截图,不代判;
- * · 矩形**内部**随地形 LOD 的逐级换挡**不由整层 alpha 修复**(决策 08 明确)——
- * 本脚本只如实记录它是否可辨,修复评估归票 40。
- *
- * 运行(需先起 `yarn dev`,并把浏览器标签页切到前台 —— 隐藏标签页 rAF 冻结,强度推不动):
- * node .scratch/cockpit-shell-panel-layout/verify/t39-handover-walkthrough.mjs
- * 按 AGENTS.md:本脚本不在改代码后自动运行,由维护者明确要求时才跑。
- */
- import { existsSync } from 'node:fs'
- import { resolve } from 'node:path'
- const BASE = process.env.COCKPIT_URL || 'http://127.0.0.1:8000/#/cockpit'
- const results = []
- const check = (id, what, ok, detail) => {
- results.push({ id, what, ok })
- console.log(`${ok ? 'PASS' : 'FAIL'} ${id} ${what}${ok ? '' : `\n ${detail}`}`)
- }
- const puppeteerPath = resolve('node_modules/puppeteer')
- if (!existsSync(puppeteerPath)) {
- console.error('未找到本地 puppeteer(本仓已知问题:@puppeteer/browsers 为空目录)。')
- console.error('本票真机证据改由 DSH 浏览器采集,逐条记录见票面「证据」一节。')
- process.exit(2)
- }
- const puppeteer = (await import('puppeteer')).default
- const browser = await puppeteer.launch({ headless: 'new', args: ['--window-size=1733,1000'] })
- const page = await browser.newPage()
- await page.setViewport({ width: 1733, height: 1000 })
- const errors = []
- page.on('pageerror', (error) => errors.push(String(error && error.message)))
- page.on('console', (message) => {
- if (message.type() === 'error') errors.push(message.text())
- })
- const sleep = (ms) => new Promise((done) => setTimeout(done, ms))
- const readHandover = () => page.evaluate(() => {
- const api = window.__cockpitMap
- const host = document.querySelector('.cockpit-shell__map .map-canvas')
- return {
- handover: api ? api.getHandoverState() : null,
- fog: api ? api.getFogState() : null,
- areaState: host ? host.dataset.mapAreaState : null,
- hostAlpha: host ? host.dataset.mapHandoverAlpha : null,
- hostPhase: host ? host.dataset.mapHandoverPhase || host.dataset.mapHandover : null,
- hostBlock: host ? host.dataset.mapHandoverBlock : null,
- }
- })
- try {
- await page.goto(BASE, { waitUntil: 'domcontentloaded' })
- await page.waitForSelector('.cockpit-shell__map .map-canvas[data-map-engine="ready"]', { timeout: 90000 })
- // ---------- A1 全球/引导态:精细层必须是隐藏的 ----------
- {
- const initial = await readHandover()
- check(
- 'A1-global-hidden',
- '全球/引导态:精细层 alpha=0(保留粗底图),相位 global',
- initial.handover && initial.handover.phase === 'global' && initial.handover.alpha === 0,
- JSON.stringify(initial.handover),
- )
- check(
- 'A1b-hidden-still-prefetches',
- '隐藏期间仍可继续预取瓦片(**不是** show=false)—— 看不见≠停止加载',
- initial.handover && initial.handover.layers > 0,
- `layers=${initial.handover && initial.handover.layers}`,
- )
- }
- // ---------- A2 进入:雾先形成保护,才允许揭示 ----------
- {
- await page.evaluate(() => {
- const intro = document.querySelector('[data-cockpit-intro]')
- if (intro) intro.click()
- window.__cockpitMap.goHome({ immediate: false })
- })
- // 采样整段进入过程:记录 (fogStrength, alpha) 的先后关系。
- const samples = []
- for (let i = 0; i < 40; i++) {
- samples.push(await readHandover())
- await sleep(150)
- }
- const revealedEarly = samples.some((sample) => sample.handover && sample.handover.alpha > 0.05
- && sample.fog && sample.fog.strength < 0.99)
- check(
- 'A2-fog-before-imagery',
- '进入途中:**不得**在雾强度未到位(<0.99)时就把精细层推到可见(>0.05)',
- !revealedEarly,
- '采样中发现"影像先亮、雾后到"的帧 —— 正是决策 08 记的"矩形边缘先暴露"',
- )
- const settled = await readHandover()
- check(
- 'A2b-revealed-after-fog',
- '雾到位且瓦片就绪后,精细层应揭示(alpha→1)',
- settled.handover && (settled.handover.phase === 'revealed' || settled.handover.phase === 'entering'),
- JSON.stringify(settled.handover),
- )
- check(
- 'A2c-tiles-signal-is-proxy',
- '瓦片读数**如实标注**是代理信号(不宣称目标视野逐像素就绪)',
- settled.handover && settled.handover.tilesReadyIsProxy === true,
- JSON.stringify(settled.handover && settled.handover.tilesReadyIsProxy),
- )
- }
- // ---------- A3 返回:影像收起后**才**撤雾(反向交接) ----------
- {
- const order = []
- await page.evaluate(() => window.__cockpitMap.returnGlobal())
- for (let i = 0; i < 40; i++) {
- const sample = await readHandover()
- order.push({ alpha: sample.handover && sample.handover.alpha, fog: sample.fog && sample.fog.strength })
- await sleep(120)
- }
- // 在 alpha 仍 > 0.05 的任何一帧,雾都不该已经被撤到 0 —— 那就是"影像还在,保护没了"。
- const droppedWhileVisible = order.some((frame) => frame.alpha > 0.05 && frame.fog <= 0.01)
- check(
- 'A3-fog-held-until-hidden',
- '返回途中:精细层仍可见(alpha>0.05)时**不得**已把雾撤净',
- !droppedWhileVisible,
- '采样中发现"影像还在、雾已撤"的帧 —— 会重现矩形边缘',
- )
- const last = order[order.length - 1]
- check(
- 'A3b-both-settled',
- '返回结束:精细层归 0 且雾归 0',
- last && last.alpha <= 0.001 && last.fog <= 0.001,
- JSON.stringify(last),
- )
- // 同步性:影像与雾的推进**重叠**(不是"影像收完→停顿→雾才动")。
- const alphaMoves = order.filter((frame) => frame.alpha > 0.001 && frame.alpha < 0.999).length
- const fogMoves = order.filter((frame) => frame.fog > 0.001 && frame.fog < 0.999).length
- check(
- 'A3c-overlap-not-serial',
- '影像与雾的收束**有重叠**(不是串行等待),符合"高度与雾同步"',
- alphaMoves > 0 && fogMoves > 0,
- `alpha 过渡帧=${alphaMoves},雾过渡帧=${fogMoves}`,
- )
- }
- // ---------- A4 取消 / 快速反向:只认最新目标 ----------
- {
- await page.evaluate(() => {
- window.__cockpitMap.goHome({ immediate: true })
- window.__cockpitMap.enterArea()
- })
- await sleep(300)
- await page.evaluate(() => window.__cockpitMap.returnGlobal())
- await sleep(120)
- await page.evaluate(() => window.__cockpitMap.enterArea())
- await sleep(2500)
- const after = await readHandover()
- check(
- 'A4-latest-target-wins',
- '进→返回→再进:最终必须落在"已进区"的目标上,旧的反向不得生效',
- after.areaState === 'locked' && after.handover && after.handover.alpha > 0.5,
- JSON.stringify({ area: after.areaState, alpha: after.handover && after.handover.alpha }),
- )
- check(
- 'A4b-no-half-transparent-stuck',
- '快速反向不得留下半透明卡死态(落定值必须是 0 或 1)',
- after.handover && (after.handover.alpha === 0 || after.handover.alpha === 1),
- `alpha=${after.handover && after.handover.alpha}`,
- )
- }
- // ---------- A5 无"已遮边"承诺 ----------
- {
- const state = await readHandover()
- check(
- 'A5-no-false-promise',
- '读数**不得**提供"边缘已遮住"的承诺(就绪≠已遮住)',
- state.handover && !('protected' in state.handover) && !('coversEdges' in state.handover),
- JSON.stringify(state.handover),
- )
- }
- check('A6-no-page-errors', '全程无页面级报错', errors.length === 0, JSON.stringify(errors.slice(0, 3)))
- } finally {
- await browser.close()
- }
- const failed = results.filter((item) => !item.ok)
- console.log(`\nT39 走查:${results.length - failed.length}/${results.length} 通过`)
- if (failed.length) process.exit(1)
|