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('each of four art directions has five readable themes and independent token colors',()=>{ assert.equal(ART_DIRECTIONS.length,4);assert.equal(new Set(ART_DIRECTIONS.map(d=>d.id)).size,4); 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,4);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); for(const t of d.themes)assert.ok(contrast(t.materials.track,t.materials.board)>=3); } });