feat: integrate brighter A3 soft ribbon hints with cached target glow
This commit is contained in:
@@ -0,0 +1 @@
|
||||
PASS 28/28:A3四尺寸、七类真实关卡,静态带面未逐帧重建,目标呼吸使用缓存。浏览器验收,微信真机待验。 320×568 / 第4关;320×568 / 第28关;320×568 / 第9关;320×568 / 第65关;320×568 / 第29关;320×568 / 第66关;320×568 / 第163关;375×812 / 第4关;375×812 / 第28关;375×812 / 第9关;375×812 / 第65关;375×812 / 第29关;375×812 / 第66关;375×812 / 第163关;390×844 / 第4关;390×844 / 第28关;390×844 / 第9关;390×844 / 第65关;390×844 / 第29关;390×844 / 第66关;390×844 / 第163关;414×896 / 第4关;414×896 / 第28关;414×896 / 第9关;414×896 / 第65关;414×896 / 第29关;414×896 / 第66关;414×896 / 第163关
|
||||
@@ -0,0 +1,136 @@
|
||||
|
||||
> numberswipe-main@1.0.0 check
|
||||
> npm run build && npm test && npm run smoke
|
||||
|
||||
|
||||
> numberswipe-main@1.0.0 build
|
||||
> node build.mjs
|
||||
|
||||
Built campaign-200-r2: 200 levels
|
||||
|
||||
> numberswipe-main@1.0.0 test
|
||||
> node --test *.test.mjs
|
||||
|
||||
✔ 手机没有浏览器音频接口时,原生播放器仍能播放全部 36 步和六类反馈音 (8.9435ms)
|
||||
✔ 关闭音效停止播放;再次开启会试听并保存偏好;后台中断停止所有播放器 (1.1639ms)
|
||||
✔ 延迟通关音在静音或进入后台时取消,不在返回后突然播放 (47.0186ms)
|
||||
✔ 音频错误可诊断,不再静默吞掉 (0.8142ms)
|
||||
✔ 所有音频均为非静音 PCM;原版第13步比第1步高一个八度 (49.8985ms)
|
||||
✔ 教学和组合规则用具体动作及奇偶例子解释,没有未解释的术语 (39.0574ms)
|
||||
✔ 过关提示同步使用实际播放起点,非请求时间 (1.1057ms)
|
||||
✔ 过关短句使用用户提供的3秒WAV,不与旧和弦叠加 (7.5766ms)
|
||||
✔ jingle end, cancellation and playback error restore BGM without restarting (6.6654ms)
|
||||
✔ muted effects never duck BGM and unrelated effect errors cannot end jingle ducking (0.5332ms)
|
||||
✔ hide and music preference do not revive playback through a late jingle callback (0.5819ms)
|
||||
✔ BGMB is absent from the runtime manifest and music package (0.9783ms)
|
||||
✔ merged irregular outlines retain holes and never draw internal unit edges (187.7866ms)
|
||||
✔ all 200 boards fit four screen budgets with stable safe anchors and real shared entrances (1687.1491ms)
|
||||
✔ guides through every irregular block stay inside its occupied shape (67.4851ms)
|
||||
✔ visited, conflicting and hinted category cells retain their material identity (15.4878ms)
|
||||
✔ five font subsets match their manifest and fit the package budget (6.3089ms)
|
||||
✔ upstream adapter fails closed if the inspected render seams change (4.4174ms)
|
||||
✔ procedural cushion pixels preserve holes and light the upper bevel without a border (39.8189ms)
|
||||
✔ visible glyph bounds are centered independently of font ascent and side bearings (2.0616ms)
|
||||
✔ stage with zero depth casts alpha shadows without painting a solid backing (12.7032ms)
|
||||
✔ native width-only TextMetrics still centers the rasterized glyph ink (5.3524ms)
|
||||
✔ fixed channels remain anchored at their block centers after number enlargement (2251.9192ms)
|
||||
✔ checkpoint ribbons trigger on correct arrival only and never duplicate per frame (7.9589ms)
|
||||
✔ checkpoint pulse is small, centered in time and settles without looping (0.8892ms)
|
||||
✔ event batches preserve every landing and sequence but commit only highest audio and haptic (8.628ms)
|
||||
✔ completion waits at most 120ms and ignores late audio callbacks (0.7635ms)
|
||||
✔ light haptics are limited, checkpoint replaces light, and completion adds only one delayed short pulse (0.9155ms)
|
||||
✔ unsupported graded haptic falls back to one short pulse, never long vibration (4.573ms)
|
||||
✔ haptic disable, preview and lifecycle cancellation suppress delayed or expired work (1.2133ms)
|
||||
✔ late failed haptic requests cannot revive old pulses and ungraded completion falls back once (16.0315ms)
|
||||
✔ rectangles, concave shapes and hexagons require geometric inward distance without treating internal seams as walls (58.0774ms)
|
||||
✔ one high-speed native move records all intermediate cells and completion without duplicate final node (333.9406ms)
|
||||
✔ legal but off-solution moves use public rules, while invalid clues only produce error (513.6096ms)
|
||||
✔ drag near a previous-block boundary cannot undo; deeper entry can, and undo button remains immediate (263.342ms)
|
||||
✔ tutorial, redraw, hint and restored saved routes do not generate player feedback (316.5441ms)
|
||||
✔ pressure and rebound have bounded amplitude and duration (0.7293ms)
|
||||
✔ interpolation cannot duplicate the same failed-cell event within one native movement (0.4654ms)
|
||||
✔ nodes are driven by explicit events even when multiple arrive before rendering; at most two ribbons survive (54.316ms)
|
||||
✔ audio fast moves merge to latest pitch and completion stops the whole four-channel pool (4.2573ms)
|
||||
✔ settings persist haptic preference independently and chapter cards always use three columns (196.5803ms)
|
||||
✔ original audio is preserved and high pitches lose energy rather than growing sharper (20.3321ms)
|
||||
✔ empty, partial and wrong routes reveal the first incomplete full segment (8.1753ms)
|
||||
✔ completion hides the hint; repeat clicks cannot skip; undo and restart return to earlier segments (1.1961ms)
|
||||
✔ all 200 levels reveal consecutive segments without changing the player path (3772.7025ms)
|
||||
✔ hint draws skip player edges and mask visited cells on their own layer; compositing is restored (3.0586ms)
|
||||
✔ parity is a segment anchor; colour is not; flow moves continuously across cells (2.7027ms)
|
||||
✔ soft teaching ribbon preserves visited masks without arrows in reduced motion (3.0311ms)
|
||||
✔ hint rounds shared guide junctions and keeps separate runs disconnected (2.0014ms)
|
||||
✔ 实际教学关动画:basic (5.7751ms)
|
||||
✔ 实际教学关动画:walls (1.3457ms)
|
||||
✔ 实际教学关动画:straight (3.5357ms)
|
||||
✔ 实际教学关动画:vertical (5.928ms)
|
||||
✔ 实际教学关动画:corner (2.104ms)
|
||||
✔ 实际教学关动画:colour (1.229ms)
|
||||
✔ 实际教学关动画:parity (0.7272ms)
|
||||
✔ 实际教学关动画:rhythm-door (2.0488ms)
|
||||
✔ 实际教学关动画:hex (16.839ms)
|
||||
✔ 实际教学关动画:unknown-start (8.1501ms)
|
||||
✔ 200关的组合规则均能找到对应真实教学关 (146.9163ms)
|
||||
✔ 教学减速,结束停留1秒后自动循环 (3.789ms)
|
||||
✔ music loops independently, persists mute and cancels in background (2.5341ms)
|
||||
✔ old arrows retain distance, rush to endpoint and fresh arrows move at base speed (1.0276ms)
|
||||
✔ settings have exactly three independent preferences, and hint has its own cue (531.0682ms)
|
||||
✔ actual fixed numeric node uses checkpoint pitch, final cell uses complete chord (357.6796ms)
|
||||
✔ selected menu and BGMA loop are stereo PCM WAV with complete musical durations (12.9153ms)
|
||||
✔ selected runtime WAVs retain the original durations after sample-rate adaptation (46.8893ms)
|
||||
✔ gameplay ambient uses lower scene volume and keeps independent mute and resume behavior (0.7041ms)
|
||||
✔ all 200 levels: valid numeric, parity and direction arrivals play checkpoint pitch; final arrival only completes (53800.1232ms)
|
||||
✔ drag back and undo button both vibrate lightly; disabling vibration suppresses both (82.8582ms)
|
||||
✔ haptic cadence is approximately 15 percent denser without changing light or medium grades (3.0517ms)
|
||||
✔ regular and special scale settle at 90ms with a larger special peak (0.3485ms)
|
||||
✔ rendered undo target and nonnumeric checkpoint animate immediately without adding ribbons (9.0174ms)
|
||||
✔ quick undo cancels removed node rebound, including last-step removal (3.4522ms)
|
||||
✔ navigation buttons use the same selection cue and settings include uniform colons (63.494ms)
|
||||
✔ early next during confetti cancels warming; settled ceremony reuses its background (285.4005ms)
|
||||
✔ font corpus includes every settings label including music le glyph (1.8083ms)
|
||||
✔ first launch goes directly to gameplay (493.2614ms)
|
||||
✔ resume unfinished route after exit from gameplay (615.7597ms)
|
||||
✔ resume unfinished route after exit from selection (504.8706ms)
|
||||
✔ completed level resumes next unfinished, including leaving from cleared screen (183.7206ms)
|
||||
✔ replaying a cleared level does not replace the unfinished save (188.5401ms)
|
||||
✔ legacy saves, malformed data and full completion have valid startup targets (219.7827ms)
|
||||
✔ selection pages expose fifteen cards and fit phone safe areas (514.0614ms)
|
||||
✔ hint-only frames reuse static UI, and player input invalidates it (151.9144ms)
|
||||
✔ hint curves are cached and distance lookup is stable at boundaries (2.1228ms)
|
||||
✔ new-mechanic lesson reuses the board while the continuous cursor advances (130.4526ms)
|
||||
✔ BGMA stays continuous across level changes and completion redraws (2.7072ms)
|
||||
✔ subpackage completion never plays a stale scene after hiding, muting, or clearing (0.6587ms)
|
||||
✔ music load failure is silent and one real resume can retry; muted pending loads stay silent (0.6025ms)
|
||||
✔ runtime uses supplied WAV hashes and ships music as a subpackage with a small main bundle (266.2246ms)
|
||||
✔ title is centered and selection settings share the progress and level-range row (925.6168ms)
|
||||
✔ completion ducks BGMA until jingle ends and next level never restarts it (252.8562ms)
|
||||
✔ title artwork is a transparent, high resolution PNG packaged locally (12.136ms)
|
||||
✔ asynchronous native image load requests a repaint and draws the artwork without a font substitute (1.315ms)
|
||||
✔ artwork stays above progress pills and clear of settings; fifteen cards still fit all phone sizes (1184.7768ms)
|
||||
✔ A3 width, one wide patch and brighter body preserve original design proportions (7.0053ms)
|
||||
✔ ribbon caches its body, draws source-atop only offscreen and starts each frame clean (13.5722ms)
|
||||
✔ one patch travels at 60px/s on the full route and does not restart when player advances (0.5319ms)
|
||||
✔ reduced motion and missing canvas use a static ribbon without main-board compositing (0.6184ms)
|
||||
✔ real target contours and smoothed routes stay attached to actual 200-level geometry (2580.5724ms)
|
||||
✔ live target breath uses cached board layers, arrival and background stop obsolete visuals (273.3177ms)
|
||||
✔ undo button reverses seven played steps as 7,6,5,4,3,2,1 (723.4609ms)
|
||||
✔ drag reverses seven played steps as 7,6,5,4,3,2,1 (316.3682ms)
|
||||
✔ completion amplitude is reduced 15 percent while node, undo and restart volumes stay unchanged (2.476ms)
|
||||
✔ fast reverse coalesces to the latest descending pitch and cannot replay an old forward note (2.3642ms)
|
||||
✔ all prepared routes match the immutable level hash and public rules (32.8606ms)
|
||||
✔ entering next level draws the live board once and preserves the running BGMA player (2786.6811ms)
|
||||
✔ next-board warming advances one item per frame and cancels on deadline, replacement or hide (0.8861ms)
|
||||
✔ idle material warming never changes the player path or emits sound and vibration (113.63ms)
|
||||
ℹ tests 110
|
||||
ℹ suites 0
|
||||
ℹ pass 110
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 54956.9338
|
||||
|
||||
> numberswipe-main@1.0.0 smoke
|
||||
> node smoke.mjs
|
||||
|
||||
PASS 200/200: r2源文件哈希一致、唯一解、微信发布包触摸通关、撤销/重开/指路、存档/音效设置恢复、前后台切换。
|
||||
@@ -0,0 +1 @@
|
||||
PASS:200/200关,点击A3指路前后和撤销重画后,玩家已走格子的实际Canvas填色像素一致。
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,69 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>数字一笔 · 明亮教具室试玩</title>
|
||||
<style>
|
||||
@font-face{font-family:NumberSwipeSans500;src:url(../fonts/NumberSwipeSans500.ttf)}
|
||||
@font-face{font-family:NumberSwipeSans600;src:url(../fonts/NumberSwipeSans600.ttf)}
|
||||
@font-face{font-family:NumberSwipeSans700;src:url(../fonts/NumberSwipeSans700.ttf)}
|
||||
@font-face{font-family:NumberSwipeDigits800;src:url(../fonts/NumberSwipeDigits800.ttf)}
|
||||
@font-face{font-family:NumberSwipePlayful;src:url(../fonts/NumberSwipePlayful.ttf)}
|
||||
*{box-sizing:border-box}body{margin:0;background:#fff6df;color:#43382f;font:15px NumberSwipeSans500,sans-serif}header{padding:28px 32px 20px;border-bottom:1px solid #e5d7ba}h1{margin:0 0 8px;font:25px NumberSwipeSans700}p{margin:8px 0;line-height:1.7;color:#71604f}main{display:flex;gap:32px;padding:28px 32px;align-items:flex-start}.tools{width:270px;position:sticky;top:24px}.tools h2{font-size:17px;margin:24px 0 10px}.tools button,select{font:14px NumberSwipeSans600;cursor:pointer;border:1px solid #d8c4a0;background:#fffbf2;color:#43382f;border-radius:12px;padding:12px;margin:0 6px 8px 0}.tools button:hover{border-color:#6552d9;color:#5140b7}.tools select{width:100%}.phone{border:1px solid #d8c4a0;border-radius:24px;overflow:hidden;box-shadow:0 12px 34px #43382f14;background:#ffe69a}canvas{display:block;touch-action:none}.note{font-size:13px}#status{background:#ece7ff;color:#5140b7;border-radius:12px;padding:12px;margin-top:16px}a{color:#5140b7}@media(max-width:750px){main{padding:16px;flex-direction:column}.tools{width:100%;position:static}.phone{align-self:center}header{padding:20px}}
|
||||
header,.tools{display:none}body{width:375px}main{padding:0;display:block}.phone{border:0;border-radius:0;box-shadow:none}
|
||||
</style>
|
||||
<header><h1>数字一笔 · 节点反馈与立体标题验收</h1><p>200关实际游戏包的 Canvas 预览。全部画面由程序绘制,字体随包提供。</p></header>
|
||||
<main><aside class="tools"><label for="size">屏幕尺寸</label><select id="size"><option>375×812</option><option>320×568</option><option>375×667</option><option>390×844</option><option>414×896</option></select><label for="feedback-mode">反馈对照</label><select id="feedback-mode"><option value="enhanced">强化版</option><option value="original">原版</option></select><h2>真实关卡样张</h2><div id="samples"></div><h2>指定关卡</h2><select id="level"></select><button id="partial">画前几步</button><button id="hint">显示指路</button><button id="clear">完成本关</button><button id="select">返回选关</button><p class="note">在右侧直接点击或滑动。音乐、音效与振动开关在游戏选关页的“设置”中。辅助演示按钮仅属于此验收页。</p><div id="status">加载字体与游戏包…</div><p class="note" id="audio-status" role="status">音效:点击游戏后播放</p><p class="note">本页不触发触觉。原版/强化版切换用于音色和表现对照;iPhone、Android触觉与性能仍需真机验收。</p><p><a href="hints.html" target="_blank">运行200关提示像素检查</a></p></aside><div class="phone" id="phone"></div></main>
|
||||
<script type="module">
|
||||
import {solve} from '../src/upstream/rules.mjs';
|
||||
import {browserAudio} from './browser-audio.mjs';
|
||||
await Promise.all(['NumberSwipeSans500','NumberSwipeSans600','NumberSwipeSans700','NumberSwipeDigits800','NumberSwipePlayful'].map(f=>document.fonts.load('16px '+f)));
|
||||
const source=await(await fetch('../game.js')).text(),pack=await(await fetch('../src/upstream/levels.json')).json();
|
||||
const levels=pack.playOrder.map(id=>pack.levels.find(l=>l.id===id));
|
||||
const size=document.querySelector('#size'),levelSelect=document.querySelector('#level'),status=document.querySelector('#status'),phone=document.querySelector('#phone');
|
||||
let runtime,feedbackMode='enhanced';
|
||||
for(const [i,l] of levels.entries()){const o=document.createElement('option');o.value=i;o.textContent=`第 ${i+1} 关 · ${l.title}`;levelSelect.append(o);}
|
||||
const samples=[['基础大块',3],['异形拼块',levels.findIndex(l=>l.cells.some(c=>c.shape)&&l.cells.length>=12)],['通道与数字',levels.findIndex(l=>l.features.includes('direction')&&l.cells.some(c=>c.direction))],['双色与奇偶',levels.findIndex(l=>l.features.includes('alternation')&&l.features.includes('parity'))],['节奏门',levels.findIndex(l=>l.doors?.length)],['六边墙',levels.findIndex(l=>l.geometry==='hex'&&l.walls?.length)],['密集36块',levels.findIndex(l=>l.id==='r2-163')]];
|
||||
for(const [name,index] of samples){const b=document.createElement('button');b.textContent=name;b.onclick=()=>show(index);document.querySelector('#samples').append(b);}
|
||||
function boot(){
|
||||
const [width,height]=size.value.split('×').map(Number),handlers={},frames=new Map(),canvases=[],store=new Map();let frame=0,now=100000;
|
||||
const timers=new Set(),sound=browserAudio(message=>{document.querySelector('#audio-status').textContent=message;});
|
||||
const later=(callback,delay)=>{const timer=setTimeout(()=>{timers.delete(timer);callback();},delay);timers.add(timer);return timer;};
|
||||
const cancel=timer=>{clearTimeout(timer);timers.delete(timer);};
|
||||
class Clock extends Date{static now(){return now;}}
|
||||
const wx={createImage(){const i=new Image();return {get width(){return i.naturalWidth;},get height(){return i.naturalHeight;},set onload(fn){i.onload=fn;},set onerror(fn){i.onerror=fn;},set src(v){i.src=new URL('../'+v,location.href).href;},_native:i};},__feedbackPreview:true,__feedbackMode:typeof feedbackMode==='undefined'?'enhanced':feedbackMode,createCanvas(){const c=document.createElement('canvas');const p=c.getContext('2d'),draw=p.drawImage.bind(p);p.drawImage=(i,...args)=>draw(i?._native||i,...args);if(!canvases.length)canvases.push(c);return c;},loadFont:p=>p.split('/').at(-1).replace('.ttf',''),getWindowInfo:()=>({windowWidth:width,windowHeight:height,pixelRatio:2,safeArea:{top:24,bottom:height-24}}),getMenuButtonBoundingClientRect:()=>({bottom:42}),getPerformance:()=>({now:()=>now}),getStorageSync:k=>store.get(k)||'',setStorageSync:(k,v)=>store.set(k,v),removeStorageSync:k=>store.delete(k),setInnerAudioOption:sound.setInnerAudioOption,createInnerAudioContext:sound.createInnerAudioContext,showModal(){},env:{USER_DATA_PATH:'/preview'}};
|
||||
for(const n of ['TouchStart','TouchMove','TouchEnd','TouchCancel','Hide','Show','WindowResize'])wx['on'+n]=cb=>handlers[n]=cb;
|
||||
const global={};new Function('wx','GameGlobal','requestAnimationFrame','cancelAnimationFrame','setTimeout','clearTimeout','Date',source)(wx,global,fn=>{frames.set(++frame,fn);return frame;},id=>frames.delete(id),later,cancel,Clock);
|
||||
const api=global.numberSwipeR2,canvas=canvases[0];canvas.style.width=width+'px';canvas.style.height=height+'px';phone.style.width=width+'px';phone.replaceChildren(canvas);
|
||||
function tick(ms=16){now+=ms;const callbacks=[...frames.values()];frames.clear();for(const fn of callbacks)fn();}
|
||||
function touch(kind,x,y){handlers[kind]({changedTouches:[{identifier:1,clientX:x,clientY:y}],touches:[{identifier:1,clientX:x,clientY:y}]});}
|
||||
function tap(x,y){touch('TouchStart',x,y);touch('TouchEnd',x,y);tick();}
|
||||
function button(name){const b=api.buttons().find(b=>b.label===name&&b.enabled);if(!b)throw Error('缺少按钮 '+name);tap(b.x+b.w/2,b.y+b.h/2);}
|
||||
function cell(id){const s=api.snapshot(),p=s.centers[id];tap(p[0],p[1]+s.boardRect.y);}
|
||||
function select(index){if(api.buttons().some(b=>b.label==='返回游戏'))button('返回游戏');if(api.snapshot().screen!=='select')button('选关');let n=0;while(!api.buttons().some(b=>b.label===String(index+1)||b.label===`${index+1} ✓`)){button(api.snapshot().selection.page>Math.floor(index/15)+1?'上一页':'下一页');if(++n>30)throw Error('分页失败');}button(api.buttons().some(b=>b.label===String(index+1))?String(index+1):`${index+1} ✓`);if(api.buttons().some(b=>b.label==='返回游戏'))button('返回游戏');tick(300);}
|
||||
let down=false;
|
||||
const coords=e=>{const r=canvas.getBoundingClientRect();return [(e.clientX-r.left)*width/r.width,(e.clientY-r.top)*height/r.height];};
|
||||
canvas.onpointerdown=e=>{down=true;canvas.setPointerCapture(e.pointerId);touch('TouchStart',...coords(e));tick();};
|
||||
canvas.onpointermove=e=>{if(down){touch('TouchMove',...coords(e));tick();}};
|
||||
canvas.onpointerup=e=>{down=false;touch('TouchEnd',...coords(e));tick();};canvas.onpointercancel=e=>{down=false;touch('TouchCancel',...coords(e));tick();};
|
||||
tick();return {tick,api,button,cell,select,dispose(){sound.dispose();for(const timer of timers)clearTimeout(timer);timers.clear();}};
|
||||
}
|
||||
function show(index){runtime?.dispose();runtime=boot();runtime.select(index);levelSelect.value=index;update();}
|
||||
function update(){const s=runtime.api.snapshot();status.textContent=`第 ${s.displayOrder} 关 · ${s.path.length} / ${levels.find(l=>l.id===s.level).cells.length} 步 · ${s.theme}`;}
|
||||
document.querySelector('#feedback-mode').onchange=e=>{feedbackMode=e.target.value;show(Number(levelSelect.value));};
|
||||
levelSelect.onchange=()=>show(Number(levelSelect.value));size.onchange=()=>show(Number(levelSelect.value));
|
||||
document.querySelector('#partial').onclick=()=>{const l=levels.find(l=>l.id===runtime.api.snapshot().level);runtime.button('重开');for(const id of solve(l)[0].slice(0,Math.min(5,l.cells.length-1)))runtime.cell(id);runtime.tick(300);update();};
|
||||
document.querySelector('#hint').onclick=()=>{runtime.button('指路');update();};
|
||||
document.querySelector('#clear').onclick=()=>{const l=levels.find(l=>l.id===runtime.api.snapshot().level);runtime.button('重开');for(const id of solve(l)[0])runtime.cell(id);runtime.tick(600);update();};
|
||||
document.querySelector('#select').onclick=()=>{runtime.button('选关');update();};
|
||||
show(3);function pump(){runtime.tick();requestAnimationFrame(pump);}requestAnimationFrame(pump);
|
||||
|
||||
const settingsCheck=document.createElement('button');settingsCheck.textContent='设置文字验收';settingsCheck.onclick=()=>{runtime.button('设置');update();};document.querySelector('.tools').append(settingsCheck);
|
||||
const timingCheck=document.createElement('button');timingCheck.textContent='通关早点击验收';document.querySelector('.tools').append(timingCheck);
|
||||
timingCheck.onclick=()=>{const results=[];for(const i of [3,90,162,191])for(const delay of [0,40,110,300,3000]){runtime.select(i);for(const id of solve(levels[i])[0])runtime.cell(id);runtime.tick(delay);const start=performance.now();runtime.button('下一关 →');const elapsed=performance.now()-start;if(runtime.api.snapshot().displayOrder!==i+2||runtime.api.snapshot().cleared)throw Error('切关失败');results.push({level:i+1,delay,ms:Number(elapsed.toFixed(1))});}status.textContent='PASS 20/20:通关后0/40/110/300/3000ms切关,上一关演出均取消。真实Canvas浏览器数据,iOS真机待复测。\n'+JSON.stringify(results);};
|
||||
|
||||
const a3Sample=document.createElement('button');a3Sample.textContent='A3软糖样张';a3Sample.onclick=()=>{show(3);runtime.cell(solve(levels[3])[0][0]);runtime.button('指路');runtime.tick(900);update();};document.querySelector('.tools').append(a3Sample);
|
||||
const a3Check=document.createElement('button');a3Check.textContent='A3尺寸与动画验收';document.querySelector('.tools').append(a3Check);a3Check.onclick=()=>{
|
||||
const rows=[];for(const dimensions of ['320×568','375×812','390×844','414×896'])for(const [,index]of samples){if(index<0)continue;size.value=dimensions;show(index);runtime.cell(solve(levels[index])[0][0]);runtime.button('指路');runtime.tick(1800);const before=runtime.api.snapshot();for(let n=0;n<12;n++)runtime.tick(16);const after=runtime.api.snapshot();if(after.softHint.builds!==before.softHint.builds)throw Error('静态带面重复构建');if(after.hint&&after.softHint.targetCacheHits<=before.softHint.targetCacheHits)throw Error('目标暖光未使用缓存');rows.push(dimensions+' / 第'+(index+1)+'关');}
|
||||
status.textContent='PASS '+rows.length+'/'+rows.length+':A3四尺寸、七类真实关卡,静态带面未逐帧重建,目标呼吸使用缓存。浏览器验收,微信真机待验。\n'+rows.join(';');
|
||||
};
|
||||
size.value='375×812';a3Sample.onclick();
|
||||
</script></html>
|
||||
@@ -20,7 +20,7 @@ const levels=pack.playOrder.map(id=>pack.levels.find(l=>l.id===id));
|
||||
const size=document.querySelector('#size'),levelSelect=document.querySelector('#level'),status=document.querySelector('#status'),phone=document.querySelector('#phone');
|
||||
let runtime,feedbackMode='enhanced';
|
||||
for(const [i,l] of levels.entries()){const o=document.createElement('option');o.value=i;o.textContent=`第 ${i+1} 关 · ${l.title}`;levelSelect.append(o);}
|
||||
const samples=[['基础大块',3],['异形拼块',levels.findIndex(l=>l.cells.some(c=>c.shape)&&l.cells.length>=12)],['通道与数字',levels.findIndex(l=>l.features.includes('direction')&&l.cells.some(c=>c.clue&&c.direction))],['双色与奇偶',levels.findIndex(l=>l.features.includes('alternation')&&l.features.includes('parity'))],['节奏门',levels.findIndex(l=>l.doors?.length)],['六边墙',levels.findIndex(l=>l.geometry==='hex'&&l.walls?.length)],['密集36块',levels.findIndex(l=>l.id==='r2-163')]];
|
||||
const samples=[['基础大块',3],['异形拼块',levels.findIndex(l=>l.cells.some(c=>c.shape)&&l.cells.length>=12)],['通道与数字',levels.findIndex(l=>l.features.includes('direction')&&l.cells.some(c=>c.direction))],['双色与奇偶',levels.findIndex(l=>l.features.includes('alternation')&&l.features.includes('parity'))],['节奏门',levels.findIndex(l=>l.doors?.length)],['六边墙',levels.findIndex(l=>l.geometry==='hex'&&l.walls?.length)],['密集36块',levels.findIndex(l=>l.id==='r2-163')]];
|
||||
for(const [name,index] of samples){const b=document.createElement('button');b.textContent=name;b.onclick=()=>show(index);document.querySelector('#samples').append(b);}
|
||||
function boot(){
|
||||
const [width,height]=size.value.split('×').map(Number),handlers={},frames=new Map(),canvases=[],store=new Map();let frame=0,now=100000;
|
||||
@@ -58,4 +58,10 @@ show(3);function pump(){runtime.tick();requestAnimationFrame(pump);}requestAnima
|
||||
const settingsCheck=document.createElement('button');settingsCheck.textContent='设置文字验收';settingsCheck.onclick=()=>{runtime.button('设置');update();};document.querySelector('.tools').append(settingsCheck);
|
||||
const timingCheck=document.createElement('button');timingCheck.textContent='通关早点击验收';document.querySelector('.tools').append(timingCheck);
|
||||
timingCheck.onclick=()=>{const results=[];for(const i of [3,90,162,191])for(const delay of [0,40,110,300,3000]){runtime.select(i);for(const id of solve(levels[i])[0])runtime.cell(id);runtime.tick(delay);const start=performance.now();runtime.button('下一关 →');const elapsed=performance.now()-start;if(runtime.api.snapshot().displayOrder!==i+2||runtime.api.snapshot().cleared)throw Error('切关失败');results.push({level:i+1,delay,ms:Number(elapsed.toFixed(1))});}status.textContent='PASS 20/20:通关后0/40/110/300/3000ms切关,上一关演出均取消。真实Canvas浏览器数据,iOS真机待复测。\n'+JSON.stringify(results);};
|
||||
|
||||
const a3Sample=document.createElement('button');a3Sample.textContent='A3软糖样张';a3Sample.onclick=()=>{show(3);runtime.cell(solve(levels[3])[0][0]);runtime.button('指路');runtime.tick(900);update();};document.querySelector('.tools').append(a3Sample);
|
||||
const a3Check=document.createElement('button');a3Check.textContent='A3尺寸与动画验收';document.querySelector('.tools').append(a3Check);a3Check.onclick=()=>{
|
||||
const rows=[];for(const dimensions of ['320×568','375×812','390×844','414×896'])for(const [,index]of samples){if(index<0)continue;size.value=dimensions;show(index);runtime.cell(solve(levels[index])[0][0]);runtime.button('指路');runtime.tick(1800);const before=runtime.api.snapshot();for(let n=0;n<12;n++)runtime.tick(16);const after=runtime.api.snapshot();if(after.softHint.builds!==before.softHint.builds)throw Error('静态带面重复构建');if(after.hint&&after.softHint.targetCacheHits<=before.softHint.targetCacheHits)throw Error('目标暖光未使用缓存');rows.push(dimensions+' / 第'+(index+1)+'关');}
|
||||
status.textContent='PASS '+rows.length+'/'+rows.length+':A3四尺寸、七类真实关卡,静态带面未逐帧重建,目标呼吸使用缓存。浏览器验收,微信真机待验。\n'+rows.join(';');
|
||||
};
|
||||
</script></html>
|
||||
|
||||
Reference in New Issue
Block a user