fix: descend undo notes and smooth next-level transitions

This commit is contained in:
toshiocn
2026-10-23 16:02:03 +08:00
parent 90d83b3903
commit 0cefce44ef
26 changed files with 301 additions and 26 deletions
@@ -32,7 +32,7 @@ function boot(){
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 entry=frames.entries().next().value;if(entry){frames.delete(entry[0]);entry[1]();}}
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);}
+1 -1
View File
@@ -28,7 +28,7 @@ for(const n of ['TouchStart','TouchMove','TouchEnd','TouchCancel','Hide','Show',
const global={};
new Function('wx','GameGlobal','requestAnimationFrame','cancelAnimationFrame','setTimeout','clearTimeout','Date',source)(wx,global,cb=>{frames.set(++frameId,cb);return frameId;},id=>frames.delete(id),()=>1,()=>{},Clock);
const api=global.numberSwipeR2;
const tick=(ms=16)=>{now+=ms;const entry=frames.entries().next().value;if(!entry)return;const [id,fn]=entry;frames.delete(id);fn();};tick();
const tick=(ms=16)=>{now+=ms;const callbacks=[...frames.values()];frames.clear();for(const fn of callbacks)fn();};tick();
const tap=(x,y)=>{const e={changedTouches:[{identifier:1,clientX:x,clientY:y}],touches:[{identifier:1,clientX:x,clientY:y}]};handlers.TouchStart(e);handlers.TouchEnd(e);tick();};
const button=label=>{const b=api.buttons().find(b=>b.label===label);if(!b||!b.enabled)throw Error('缺少按钮 '+label);tap(b.x+b.w/2,b.y+b.h/2);};
const cell=id=>{const s=api.snapshot(),[x,y]=s.centers[id];tap(x+s.boardRect.x,y+s.boardRect.y);};
@@ -0,0 +1,26 @@
[
{
"nextLevel": 91,
"ms": 44.39
},
{
"nextLevel": 123,
"ms": 67.92
},
{
"nextLevel": 145,
"ms": 43.31
},
{
"nextLevel": 163,
"ms": 56.98
},
{
"nextLevel": 182,
"ms": 70.77
},
{
"nextLevel": 192,
"ms": 105.95
}
]
@@ -0,0 +1,32 @@
[
{
"version": "修改前",
"nextLevel": 91,
"milliseconds": 62.3
},
{
"version": "修改前",
"nextLevel": 163,
"milliseconds": 130.5
},
{
"version": "修改前",
"nextLevel": 192,
"milliseconds": 152.6
},
{
"version": "优化后",
"nextLevel": 91,
"milliseconds": 13.1
},
{
"version": "优化后",
"nextLevel": 163,
"milliseconds": 8.4
},
{
"version": "优化后",
"nextLevel": 192,
"milliseconds": 7.8
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

@@ -0,0 +1 @@
<!doctype html><meta charset="utf-8"><style>body{margin:0;padding:20px;width:560px;background:#fff6df;color:#43382f;font:16px 'Microsoft YaHei',sans-serif}h2{font-size:21px;margin:0 0 10px}table{border-collapse:collapse;width:100%;background:#fffbf2;border-radius:12px}th,td{padding:10px;text-align:center;border-bottom:1px solid #eddfc7}td:last-child{color:#326443;font-weight:bold}p{font-size:12px;color:#71604f}</style><h2>下一关点击响应 · 真实Canvas抽测</h2><table><tr><th>目标关卡</th><th>修改前</th><th>优化后</th></tr><tr><td>第91关</td><td>62.3 ms</td><td>13.1 ms</td></tr><tr><td>第163关</td><td>130.5 ms</td><td>8.4 ms</td></tr><tr><td>第192关</td><td>152.6 ms</td><td>7.8 ms</td></tr></table><p>同机浏览器、375×812;包含切关到首帧绘制。真机仍需复测。<br>预计算路线+去重绘制+下一关逐帧材质预热;不影响规则。</p>
@@ -12,7 +12,7 @@ export function boot(storage = new Map(), width = 375, height = 812) {
wx['on' + name] = cb => handlers[name] = cb;
const global = {};
vm.runInNewContext(fs.readFileSync(path.join(root, 'game.js'), 'utf8'), {Date:Clock, wx, GameGlobal: global, requestAnimationFrame: cb => { frames.set(++frameId, cb); return frameId; }, cancelAnimationFrame: id => frames.delete(id), setTimeout: () => 1, clearTimeout: () => { } }, { timeout: 10000 });
const tick = () => { const entry=frames.entries().next().value;if(!entry)return;const [id,fn]=entry; frames.delete(id);text.length=0;images.length=0; fn(); };
const tick = () => { const callbacks=[...frames.values()];frames.clear();text.length=0;images.length=0;for(const fn of callbacks)fn(); };
tick();
const touch = (kind, x, y) => handlers[kind]({ changedTouches: [{ identifier: 1, clientX: x, clientY: y }], touches: [{ identifier: 1, clientX: x, clientY: y }] });
const tap = (x, y) => { touch('TouchStart', x, y); touch('TouchEnd', x, y); tick(); };
@@ -0,0 +1,127 @@
> 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 步和六类反馈音 (5.9719ms)
✔ 关闭音效停止播放;再次开启会试听并保存偏好;后台中断停止所有播放器 (0.7505ms)
✔ 延迟通关音在静音或进入后台时取消,不在返回后突然播放 (49.8525ms)
✔ 音频错误可诊断,不再静默吞掉 (0.5119ms)
✔ 所有音频均为非静音 PCM;原版第13步比第1步高一个八度 (27.1585ms)
✔ 教学和组合规则用具体动作及奇偶例子解释,没有未解释的术语 (13.4334ms)
✔ 过关提示同步使用实际播放起点,非请求时间 (7.7487ms)
✔ 过关短句使用用户提供的3秒WAV,不与旧和弦叠加 (1.9568ms)
✔ jingle end, cancellation and playback error restore BGM without restarting (4.24ms)
✔ muted effects never duck BGM and unrelated effect errors cannot end jingle ducking (0.7256ms)
✔ hide and music preference do not revive playback through a late jingle callback (2.6609ms)
✔ BGMB is absent from the runtime manifest and music package (1.4668ms)
✔ merged irregular outlines retain holes and never draw internal unit edges (89.6057ms)
✔ all 200 boards fit four screen budgets with stable safe anchors and real shared entrances (910.9608ms)
✔ guides through every irregular block stay inside its occupied shape (38.2585ms)
✔ visited, conflicting and hinted category cells retain their material identity (13.1484ms)
✔ five font subsets match their manifest and fit the package budget (3.7437ms)
✔ upstream adapter fails closed if the inspected render seams change (0.8671ms)
✔ procedural cushion pixels preserve holes and light the upper bevel without a border (20.4011ms)
✔ visible glyph bounds are centered independently of font ascent and side bearings (1.3968ms)
✔ stage with zero depth casts alpha shadows without painting a solid backing (9.1895ms)
✔ native width-only TextMetrics still centers the rasterized glyph ink (2.4161ms)
✔ fixed channels remain anchored at their block centers after number enlargement (1096.8783ms)
✔ checkpoint ribbons trigger on correct arrival only and never duplicate per frame (3.4084ms)
✔ checkpoint pulse is small, centered in time and settles without looping (0.336ms)
✔ event batches preserve every landing and sequence but commit only highest audio and haptic (4.69ms)
✔ completion waits at most 120ms and ignores late audio callbacks (1.528ms)
✔ light haptics are limited, checkpoint replaces light, and completion adds only one delayed short pulse (0.7758ms)
✔ unsupported graded haptic falls back to one short pulse, never long vibration (0.9678ms)
✔ haptic disable, preview and lifecycle cancellation suppress delayed or expired work (0.5194ms)
✔ late failed haptic requests cannot revive old pulses and ungraded completion falls back once (0.3432ms)
✔ rectangles, concave shapes and hexagons require geometric inward distance without treating internal seams as walls (4.27ms)
✔ one high-speed native move records all intermediate cells and completion without duplicate final node (197.6697ms)
✔ legal but off-solution moves use public rules, while invalid clues only produce error (93.488ms)
✔ drag near a previous-block boundary cannot undo; deeper entry can, and undo button remains immediate (85.5951ms)
✔ tutorial, redraw, hint and restored saved routes do not generate player feedback (117.3508ms)
✔ pressure and rebound have bounded amplitude and duration (0.6393ms)
✔ interpolation cannot duplicate the same failed-cell event within one native movement (0.3588ms)
✔ nodes are driven by explicit events even when multiple arrive before rendering; at most two ribbons survive (47.8573ms)
✔ audio fast moves merge to latest pitch and completion stops the whole four-channel pool (2.7155ms)
✔ settings persist haptic preference independently and chapter cards always use three columns (104.3303ms)
✔ original audio is preserved and high pitches lose energy rather than growing sharper (11.5992ms)
✔ empty, partial and wrong routes reveal the first incomplete full segment (2.6982ms)
✔ completion hides the hint; repeat clicks cannot skip; undo and restart return to earlier segments (0.6567ms)
✔ all 200 levels reveal consecutive segments without changing the player path (1831.3425ms)
✔ hint draws skip player edges and mask visited cells on their own layer; compositing is restored (1.4713ms)
✔ parity is a segment anchor; colour is not; flow moves continuously across cells (0.5245ms)
✔ coral teaching ribbon preserves visited masks and offers direction in reduced motion (2.4201ms)
✔ hint rounds shared guide junctions and keeps separate runs disconnected (0.6225ms)
✔ 实际教学关动画:basic (5.4459ms)
✔ 实际教学关动画:walls (1.3921ms)
✔ 实际教学关动画:straight (2.2782ms)
✔ 实际教学关动画:vertical (7.57ms)
✔ 实际教学关动画:corner (2.7113ms)
✔ 实际教学关动画:colour (1.7488ms)
✔ 实际教学关动画:parity (0.644ms)
✔ 实际教学关动画:rhythm-door (0.75ms)
✔ 实际教学关动画:hex (0.6395ms)
✔ 实际教学关动画:unknown-start (1.3742ms)
✔ 200关的组合规则均能找到对应真实教学关 (166.655ms)
✔ 教学减速,结束停留1秒后自动循环 (3.3536ms)
✔ music loops independently, persists mute and cancels in background (4.7913ms)
✔ old arrows retain distance, rush to endpoint and fresh arrows move at base speed (0.8271ms)
✔ settings have exactly three independent preferences, and hint has its own cue (223.1428ms)
✔ actual fixed numeric node uses checkpoint pitch, final cell uses complete chord (248.4456ms)
✔ selected menu and BGMA loop are stereo PCM WAV with complete musical durations (15.0776ms)
✔ selected runtime WAVs retain the original durations after sample-rate adaptation (25.2474ms)
✔ gameplay ambient uses lower scene volume and keeps independent mute and resume behavior (0.4916ms)
✔ all 200 levels: valid numeric, parity and direction arrivals play checkpoint pitch; final arrival only completes (29009.0033ms)
✔ drag back and undo button both vibrate lightly; disabling vibration suppresses both (65.8824ms)
✔ haptic cadence is approximately 15 percent denser without changing light or medium grades (2.9116ms)
✔ regular and special scale settle at 90ms with a larger special peak (0.3386ms)
✔ rendered undo target and nonnumeric checkpoint animate immediately without adding ribbons (10.7241ms)
✔ quick undo cancels removed node rebound, including last-step removal (2.5595ms)
✔ first launch goes directly to gameplay (133.5197ms)
✔ resume unfinished route after exit from gameplay (247.5837ms)
✔ resume unfinished route after exit from selection (153.6188ms)
✔ completed level resumes next unfinished, including leaving from cleared screen (99.8722ms)
✔ replaying a cleared level does not replace the unfinished save (95.6296ms)
✔ legacy saves, malformed data and full completion have valid startup targets (68.3508ms)
✔ selection pages expose fifteen cards and fit phone safe areas (254.6395ms)
✔ hint-only frames reuse static UI, and player input invalidates it (63.1021ms)
✔ hint curves are cached and distance lookup is stable at boundaries (1.2907ms)
✔ new-mechanic lesson reuses the board while the continuous cursor advances (47.8203ms)
✔ BGMA stays continuous across level changes and completion redraws (2.0839ms)
✔ subpackage completion never plays a stale scene after hiding, muting, or clearing (0.5922ms)
✔ music load failure is silent and one real resume can retry; muted pending loads stay silent (0.4568ms)
✔ runtime uses supplied WAV hashes and ships music as a subpackage with a small main bundle (151.6511ms)
✔ title is centered and selection settings share the progress and level-range row (354.3931ms)
✔ completion ducks BGMA until jingle ends and next level never restarts it (120.0796ms)
✔ title artwork is a transparent, high resolution PNG packaged locally (7.1401ms)
✔ asynchronous native image load requests a repaint and draws the artwork without a font substitute (0.6299ms)
✔ artwork stays above progress pills and clear of settings; fifteen cards still fit all phone sizes (432.4844ms)
✔ undo button reverses seven played steps as 7,6,5,4,3,2,1 (266.8512ms)
✔ drag reverses seven played steps as 7,6,5,4,3,2,1 (181.5857ms)
✔ completion amplitude is reduced 15 percent while node, undo and restart volumes stay unchanged (1.6029ms)
✔ fast reverse coalesces to the latest descending pitch and cannot replay an old forward note (2.7327ms)
✔ all prepared routes match the immutable level hash and public rules (18.0142ms)
✔ entering next level draws the live board once and preserves the running BGMA player (1386.9742ms)
✔ next-board warming advances one item per frame and cancels on deadline, replacement or hide (0.7355ms)
✔ idle material warming never changes the player path or emits sound and vibration (36.9474ms)
ℹ tests 101
ℹ suites 0
ℹ pass 101
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 29450.7015
> numberswipe-main@1.0.0 smoke
> node smoke.mjs
PASS 200/200: r2源文件哈希一致、唯一解、微信发布包触摸通关、撤销/重开/指路、存档/音效设置恢复、前后台切换。
@@ -0,0 +1 @@
PASS:200/200 关,点击指路前后和撤销重画后,玩家已走格子的实际 Canvas 填色像素一致。浏览器真实Canvas,375x812。