feat: integrate brighter A3 soft ribbon hints with cached target glow

This commit is contained in:
toshiocn
2026-10-23 17:18:09 +08:00
parent 6abbc37399
commit 8f2263b5d3
23 changed files with 683 additions and 186 deletions
+6 -6
View File
@@ -35,27 +35,27 @@ test('hint draws skip player edges and mask visited cells on their own layer; co
const ctx=new Proxy({globalCompositeOperation:'source-over',save(){stack.push(this.globalCompositeOperation);},restore(){this.globalCompositeOperation=stack.pop();},beginPath(){from=to=null;},moveTo:(...p)=>{from=p;},lineTo:(...p)=>{to=p;},stroke(){if(from&&to)strokes.push([from,to]);},arc:(...p)=>arcs.push(p),fill(){events.push({kind:'fill',mode:this.globalCompositeOperation});}},{get:(o,k)=>o[k]??(()=>{}),set:(o,k,v)=>(o[k]=v,true)});
const poly=[[0,0],[100,0],[100,100],[0,100]];
drawHint(ctx,{route:['a','b','c'],visitedPolygons:[poly]},{a:[0,0],b:[100,0],c:[200,0]},50,300,['a','b']);
assert.deepEqual(strokes,[[[100,0],[200,0]],[[100,0],[200,0]]],'only unwalked edge receives hint strokes');
assert.equal(strokes.length,4);assert.ok(strokes.every(s=>JSON.stringify(s)===JSON.stringify([[100,0],[200,0]])),'only unwalked edge receives hint strokes');
assert.ok(events.some(e=>e.kind==='fill'&&e.mode==='destination-out'));
assert.equal(ctx.globalCompositeOperation,'source-over');
assert.equal(arcs.length,1,'no moving light or rings over already walked edge');
assert.equal(arcs.length,0,'A3 has no light beads or endpoint rings');
});
test('parity is a segment anchor; colour is not; flow moves continuously across cells',()=>{
const l={cells:solution.map(id=>({id,...(id==='d'?{parity:'odd'}:{category:'circle'})}))};
assert.deepEqual(hintSegment(l,solution,[]).route,['a','b','c','d']);
const arcs=[],ctx=new Proxy({arc:(...p)=>arcs.push(p)},{get:(o,k)=>o[k]??(()=>{}),set:(o,k,v)=>(o[k]=v,true)});
drawHint(ctx,{route:['a','b','c']},{a:[0,0],b:[100,0],c:[100,100]},50,300);
assert.deepEqual(arcs.at(-3).slice(0,2),[50,0]);
assert.equal(arcs.length,0,'static fallback has no moving beads');
});
test('coral teaching ribbon preserves visited masks and offers direction in reduced motion',()=>{
test('soft teaching ribbon preserves visited masks without arrows in reduced motion',()=>{
for(const reducedMotion of [false,true]){
const strokes=[],fills=[],stack=[];
const ctx=new Proxy({globalCompositeOperation:'source-over',save(){stack.push(this.globalCompositeOperation);},restore(){this.globalCompositeOperation=stack.pop();},stroke(){strokes.push({color:this.strokeStyle,mode:this.globalCompositeOperation});},fill(){fills.push(this.globalCompositeOperation);}},{get:(o,k)=>o[k]??(()=>{}),set:(o,k,v)=>(o[k]=v,true)});
const path=['a'];
drawHint(ctx,{route:['a','b'],guides:[{a:'a',b:'b',points:[[0,0],[100,0]]}],visitedShapes:[{rings:[[[0,0],[20,0],[20,20],[0,20]]],radius:3,depth:3,maskPad:4}],keepouts:[{x:40,y:0,w:12,h:15}],reducedMotion},{},50,300,path);
assert.ok(strokes.some(s=>s.color==='#F49B75'));
assert.ok(strokes.filter(s=>s.color==='#C96E48'&&s.mode==='source-over').length>=1,'directional chevron');
assert.ok(strokes.some(s=>s.color==='rgba(255,185,126,0.72)'));
assert.equal(strokes.filter(s=>s.color==='#C96E48').length,0,'no chevrons');
assert.ok(fills.includes('destination-out'));
assert.equal(ctx.globalCompositeOperation,'source-over');
assert.deepEqual(path,['a']);