import test from 'node:test'; import assert from 'node:assert/strict'; import { ART_DIRECTIONS, directionTheme, artDirection, appearanceTile } from '../src/art-directions.js'; import { contrast } from '../src/design-tokens.js'; import { drawScene, drawTile, drawExit } from '../src/renderer.js'; import { createStudyLevel } from '../src/study-levels.js'; import { available, trace } from '../src/core.js'; import { layout } from '../src/geometry.js'; test('paper study preserves the variants reference: pale thin rails and outlined hexagons with line arrows',()=>{ const theme=directionTheme(artDirection('paper'),'lagoon'),commands=[]; const p={polygon:(points,color)=>commands.push({kind:'polygon',points,color}),line:(points,color,width)=>commands.push({kind:'line',points,color,width}),circle:()=>{}}; assert.equal(theme.background,'#EAF0F3');assert.equal(theme.materials.board,'#FAFBF7');assert.equal(theme.materials.track,'#B2C4CE'); assert.deepEqual(theme.pigments.slice(0,2).map(p=>p.color),['#D66C4D','#477FC0']); drawTile(p,50,50,40,appearanceTile({color:0,dir:0},theme)); assert.equal(commands[0].points.length,6);assert.equal(commands[0].color,'#D66C4D'); assert.ok(Math.abs(commands[0].points[0].x-50-40*.88)<1e-9); assert.equal(commands[1].color,'#FFFFFFCC');assert.ok(Math.abs(commands[1].width-40*.035)<1e-9); assert.deepEqual(commands.slice(2).map(c=>[c.kind,c.points.length,c.color]),[['line',2,'#FFFFFF'],['line',3,'#FFFFFF']]); assert.ok(commands[2].points[1].yc.kind==='line'&&c.color===theme.materials.track); assert.equal(rails.length,level.cells.length*3); assert.ok(rails.every(c=>Math.abs(c.width-40*.055)<1e-9)); assert.ok(!commands.some(c=>c.kind==='line'&&c.color===theme.materials.trackHalo),'no thick rail halo'); }); test('each of five art directions has five readable themes and independent token colors',()=>{ assert.equal(ART_DIRECTIONS.length,5);assert.equal(new Set(ART_DIRECTIONS.map(d=>d.id)).size,5); for(const d of ART_DIRECTIONS){ assert.deepEqual(d.themes.map(t=>t.label),['轻松','进阶','烧脑','挑战','每周挑战']); for(const t of d.themes){assert.ok(contrast(t.background,t.ink)>=4.5);assert.equal(directionTheme(d,t.id).artStyle,d.style);} for(const p of d.pigments)assert.ok(contrast(p.color,p.arrow)>=3); } assert.equal(artDirection('invalid').id,'calm'); }); test('art styles change actual geometry without changing level state, including moving tokens',()=>{ const level=createStudyLevel(3),copy=JSON.stringify(level),view=layout(level.cells,520,600),tile=available(level,level.tiles)[0],route=trace(level,tile,level.tiles); const signatures=[]; for(const d of ART_DIRECTIONS){ const commands=[],p={polygon:(points,color)=>{assert.ok(color);assert.ok(points.every(p=>Number.isFinite(p.x)&&Number.isFinite(p.y)));commands.push(['polygon',points.length,color]);},circle:(x,y,r,color)=>{assert.ok(Number.isFinite(r)&&r>=0);commands.push(['circle',r,color]);},line:(points,color,width)=>{assert.ok(width>0);commands.push(['line',points.length,color]);}}; const theme=directionTheme(d,'lagoon'),t=appearanceTile({color:0,dir:1},theme); drawTile(p,50,50,40,t);signatures.push(JSON.stringify(commands)); drawScene(p,level,level.tiles,view,{theme});drawExit(p,route,view,.4,t);drawExit(p,route,view,.99,t); } assert.equal(new Set(signatures).size,5);assert.equal(JSON.stringify(level),copy); }); test('vivid worlds retain strong chroma and each new theme has its own seven-color mix',()=>{ const vivid=artDirection('vivid'); assert.equal(vivid.themes[0].background,'#B8F34A'); for(const t of vivid.themes){ const rgb=t.background.slice(1).match(/../g).map(v=>parseInt(v,16)/255),hi=Math.max(...rgb),lo=Math.min(...rgb),l=(hi+lo)/2,s=(hi-lo)/(1-Math.abs(2*l-1)); assert.ok(s>=.65&&l<=.65,`${t.name} should keep the saturated poster treatment`); } for(const d of ART_DIRECTIONS.slice(1)){ assert.equal(new Set(d.themes.map(t=>directionTheme(d,t.id).palette.slice().sort((a,b)=>a-b).join(','))).size,5); assert.equal(new Set(d.themes.map(t=>t.materials.board)).size,5); // The paper study deliberately retains the reference's low-contrast rails. if(d.style!=='paper')for(const t of d.themes)assert.ok(contrast(t.materials.track,t.materials.board)>=3); } });