Files
HexTrails/src/art-directions.js
T
cabinwandClaude Opus 5 ce7f6d30cf feat(levels): gate the dead turn, and state the curve instead of hoping for it
Round 1 played all fifty levels and came back with the mechanics clean and one
complaint repeated across the reports: 短暂以为转错了方向 — a turn that changed
nothing. That one is mechanical rather than perceptual, so it can be gated.

The first attempt gated `doubleDead`, and it was wrong. That counts dead states,
which scales with the state space: three-turn levels have ~187 reachable stuck
states and five-turn ones ~739, so a raw count made deep levels look eight times
worse when the rate is 2% against 5%. It would have erased every level past
three turns — the opposite of the brief, where turning is the signature and is
not to be rationed. The gate is `maxDeadTurns`, the longest run of turns before
something opens, at 1: one beat of suspense, then a release. Eleven of the fifty
shipped at 2.

Selection could not express the curve either. Spreading picks evenly over the
ranked pool returns whatever the pool is made of, and the pool is 88%
three-turn, so no weighting moved the distribution off {3: 46}. A quota per
depth states the curve outright, deepest first so the scarce end picks before a
family cap is spent on levels the shallow end has hundreds of.

Same rule bot, both sets: blind rotations 0.24 → 0.12, worst blind streak 4 → 1,
decisions unchanged at 10.3. The same amount to do, half the wasted motion.
Rotation now runs 1.8 / 3.0 / 4.1 across the bands against 1.8 / 3.0 / 3.5.

Verdicts are keyed by the board rather than by its id, because a reselect
re-issues ids from the ordinal and `c50-01-v05` is not the same board before and
after — the review sheet was one reselect away from printing one level's verdict
under another's thumbnail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:13:02 +08:00

125 lines
14 KiB
JavaScript

import { LEVEL_THEMES, PIGMENTS, contrast, themeWithColors } from './design-tokens.js';
const makePigments=(colors,labels)=>colors.map((color,i)=>({name:labels?.[i]||PIGMENTS[i].name,color,arrow:contrast('#FFFFFF',color)>=4.5?'#FFFFFF':'#172D39'}));
const names=['轻松','进阶','烧脑','挑战','每周挑战'];
const themes=(backgrounds,titles,materials,palettes,descriptions,inks)=>backgrounds.map((background,i)=>({
...LEVEL_THEMES[i],name:titles[i],background,ink:inks?.[i]||(contrast('#12202A',background)>=4.5?'#12202A':'#FFFFFF'),
description:descriptions?.[i]||`${names[i]}主题 · ${titles[i]}。`,materials:Array.isArray(materials)?materials[i]:materials,
palette:palettes?.[i]||LEVEL_THEMES[i].palette,
}));
export const ART_DIRECTIONS=Object.freeze([
{id:'calm',code:'01',name:'柔和原色',en:'QUIET TRAILS',tag:'现有方案',style:'classic',accent:'#6652B8',paper:'#F7F6F2',
description:'低饱和环境与平面六角棋子,让注意力留在轨道和方向。',
principles:['哑光色块,克制装饰','蓝灰细轨道,保持视觉留白','金色奖励,柔和浮现'],
themes:LEVEL_THEMES,pigments:PIGMENTS},
{id:'vivid',code:'02',name:'高彩游乐场',en:'CHROMA CLUB',tag:'高饱和',style:'vivid',accent:'#4A24E8',paper:'#FFFCF0',
description:'鲜明撞色、粗线条与贴纸式棋子。像一张能玩的彩色海报,明快、有力量。',
principles:['高纯度主色,深色轮廓统一画面','轨道像印刷线,棋子像立起的贴纸','方圆按钮与错位硬阴影'],
themes:themes(
['#B8F34A','#FF771B','#3E42EE','#FF3588','#1B104D'],
['青柠派对','爆橙热浪','钴蓝电场','荧粉冲刺','黑光嘉年华'],
[
{board:'#FFFBE7',boardGrid:'#AB9C82',track:'#30264C',trackHalo:'#FFFBE7'},
{board:'#FFEF45',boardGrid:'#B78335',track:'#462654',trackHalo:'#FFEF45'},
{board:'#C9F849',boardGrid:'#789344',track:'#252269',trackHalo:'#C9F849'},
{board:'#FFF4CE',boardGrid:'#C491A1',track:'#502369',trackHalo:'#FFF4CE'},
{board:'#35225D',boardGrid:'#837B9F',track:'#D9FF55',trackHalo:'#35225D'},
],
[[0,4,2,1,3,5,6],[7,3,10,8,2,0,11],[2,9,1,10,6,5,8],[0,6,10,2,7,4,9],[10,2,6,1,9,11,5]],
['保留青柠大色块,深色轮廓与蓝、青、黄色贴纸。','爆橙背景与亮黄棋盘,钴蓝、红紫和电青正面撞色。','纯钴蓝包围荧光青柠棋盘,用暖橙与亮粉制造冷暖冲撞。','荧光粉舞台,明黄与蓝绿棋子形成强烈互补。','深紫黑底配荧光绿轨道,电青、柠黄与荧粉像黑光贴纸。']),
pigments:makePigments(['#2455E8','#E52879','#FFE13E','#7136D8','#007C70','#FF622C','#BBE929','#074DFF','#EB174E','#FF9500','#08E8E1','#A92BFF'],['电蓝','热粉','柠檬黄','电紫','深青','火橙','荧光绿','钴蓝','警示红','亮橘','电光青','激光紫'])},
{id:'toy',code:'03',name:'积木轨道屋',en:'LITTLE PLAYWORKS',tag:'玩具质感',style:'toy',accent:'#245EAF',paper:'#FFFAEE',
description:'圆润积木、原色塑胶与压下去有回弹的按钮。棋子像可以拿在手里的小零件。',
principles:['同色深浅叠层,形成柔软厚度','红黄蓝绿积木配木色底板','圆角控件、凸起边缘与轻微高光'],
themes:themes(
['#F4C548','#56AFE0','#EA743E','#439A73','#244584'],
['积木黄盒','蓝色装配台','橘红工坊','绿毯小城','太空玩具柜'],
[
{board:'#FFF7DE',boardGrid:'#B5A184',track:'#765238',trackHalo:'#FFF7DE'},
{board:'#F5DFB6',boardGrid:'#B09C7C',track:'#386A8D',trackHalo:'#F5DFB6'},
{board:'#FFEFCB',boardGrid:'#BBA27B',track:'#695037',trackHalo:'#FFEFCB'},
{board:'#F2D6A6',boardGrid:'#AC987A',track:'#476556',trackHalo:'#F2D6A6'},
{board:'#D4E4E6',boardGrid:'#8DA4B2',track:'#35516F',trackHalo:'#D4E4E6'},
],
[[0,2,3,5,4,8,10],[1,0,4,3,9,6,11],[2,3,1,7,10,5,8],[0,1,2,4,6,9,11],[1,4,0,6,10,3,8]],
['经典红黄蓝积木,放在暖黄玩具盒里;用奶油底板隔开主色。','亮蓝工作台配原木色插板,红黄橙零件像一套拼装工具。','橘红桌面与木糖底板,绿蓝零件形成互补。','绿色游戏垫与木色道路,红黄积木像散落的小房子。','太空蓝收藏柜配浅灰塑胶板,黄橙零件像小飞船。']),
pigments:makePigments(['#DF4033','#F2C52E','#2478C5','#32945C','#EF862D','#7652B2','#EFE4C4','#227B87','#B63845','#745B40','#73BFDC','#A7BA56'],['积木红','积木黄','积木蓝','积木绿','零件橙','拼装紫','奶油白','工具青','砖红','木棕','玻璃蓝','草绿'])},
{id:'animals',code:'04',name:'动物弯弯镇',en:'PAW PARADE',tag:'卡通动物',style:'animals',accent:'#396D51',paper:'#FFFCF1',
description:'猫咪、小熊、兔子与青蛙沿轨道出游。用表情与耳朵增加性格,方向箭头独立保留。',
principles:['圆脸、短耳与小表情,外形保持在六角范围内','箭头留在脸部下方,不靠动物朝向猜方向','草地、木棕与奶油毛色,搭配森林小径'],
themes:themes(
['#A9CD69','#E7B947','#72BEB2','#C67B47','#183F36'],
['青草野餐','橡果秋游','溪边捉迷藏','红土探险','萤火森林'],
[
{board:'#FFF2D2',boardGrid:'#A9AC80',track:'#527345',trackHalo:'#FFF2D2'},
{board:'#FFEDB5',boardGrid:'#B6A076',track:'#79572E',trackHalo:'#FFEDB5'},
{board:'#ECF1D8',boardGrid:'#9CA987',track:'#3B7262',trackHalo:'#ECF1D8'},
{board:'#F6DCB2',boardGrid:'#B19169',track:'#684835',trackHalo:'#F6DCB2'},
{board:'#355C43',boardGrid:'#879969',track:'#E6E9AE',trackHalo:'#355C43'},
],
[[0,1,2,3,6,7,10],[1,4,8,5,2,6,11],[7,3,6,10,0,4,9],[8,5,4,2,7,10,1],[6,10,2,4,11,0,9]],
['草地绿、奶油白与动物毛色,不使用糖果蓝紫。','秋叶黄配橡果棕、麦芽金和浅奶油,像一次林间秋游。','溪水青配苔绿与暖棕,保留自然界的冷暖关系。','红土与沙色小径,棕熊、奶油兔和绿蛙像野外探险队。','深森林绿配萤火浅黄,浅毛色角色从夜色中浮现。']),
pigments:makePigments(['#D9934F','#A4683B','#E1C58B','#6C9D40','#F1B869','#73503F','#F1DCC1','#448A67','#B78157','#BD784F','#F4E5CF','#87B76B'],['姜黄猫','栗棕熊','麦芽兔','苔绿蛙','奶橘猫','可可熊','奶油兔','溪绿蛙','榛果猫','赤陶熊','雪白兔','嫩叶蛙'])},
{id:'paper',code:'05',name:'轻线纸境',en:'PAPER ROUTES',tag:'实验页衍生',style:'paper',accent:'#294D60',paper:'#F7F9F8',
description:'提取自玩法变种实验页:暖白纸面、轻盈细轨道、白边纯色六角与细线箭头。把颜色留给棋子,让弯路轻轻浮现。',
principles:['浅蓝灰细线与微小格缝,延续实验页的轻盈感','正六角纯色棋子、细白边、圆头线条箭头','无投影与渐变;陶橙和雾蓝是核心配色'],
source:{label:'玩法变种实验页',href:'labs/variants/index.html'},
themes:themes(
['#EAF0F3','#F3EEE5','#E8EFEB','#E7EBEE','#253B4A'],
['雾蓝信纸','暖砂手札','鼠尾草页','石墨练习','夜航图纸'],
[
{board:'#FAFBF7',boardGrid:'#D3DFE3',track:'#B2C4CE',trackHalo:'#FAFBF7'},
{board:'#FFFCF5',boardGrid:'#E3DCD0',track:'#C4B8A4',trackHalo:'#FFFCF5'},
{board:'#FAFCF8',boardGrid:'#D3DFD7',track:'#ADBCB2',trackHalo:'#FAFCF8'},
{board:'#F8FAFB',boardGrid:'#D1DBE1',track:'#A5B6C1',trackHalo:'#F8FAFB'},
{board:'#304A59',boardGrid:'#435E6C',track:'#849FAE',trackHalo:'#304A59'},
],
[[0,1,2,3,4,5,7],[0,1,3,6,8,9,4],[1,2,3,5,9,10,0],[0,1,4,6,7,10,11],[0,2,3,4,5,8,10]],
['保留实验页的雾蓝背景、暖白格面与陶橙/雾蓝棋子,扩展色同样保持哑光。','像暖砂色纸上的铅笔路线,陶橙、赭金与木棕带来温度。','浅鼠尾草纸底,灰绿轨道与蓝、苔绿棋子形成安静的冷暖关系。','灰白图纸与石墨蓝细线,用陶红、雾紫和钢蓝组织复杂路线。','深蓝灰图纸上保留细线与白箭头,暖色棋子像夜航中的小标记。']),
pigments:makePigments(['#D66C4D','#477FC0','#668877','#B58943','#8B7297','#45868C','#B76260','#607B90','#B28168','#46765F','#6B8AAB','#9C7284'],['陶橙','雾蓝','鼠尾草','赭金','雾紫','湖青','陶红','钢蓝','暖木','松绿','烟蓝','藕灰']).map(p=>({...p,arrow:'#FFFFFF'}))},
{id:'signpost',code:'06',name:'路标',en:'SIGNPOST',tag:'线路图',style:'signpost',accent:'#2F5FBF',paper:'#F6F2E9',journeyWorlds:true,layout:{rotation:'under-board',rim:.06},
description:'棋盘是一张线路图:奶白卡片托在世界色上,墨蓝钢轨压在浅色路基里,端点在板边收口。棋子是路标:十二色统一白箭头,一圈细描边,方向一眼可读。',
principles:['格线几乎不可见,路基与钢轨分两层,交叉不打架','棋子内缩露出端口,细描边加统一放大的白箭头','旋转按钮像压在纸上的印记,挂在棋盘两肩之下'],
route:{exit:'#2F5FBF',exitGlow:'#C9D7F3',blocked:'#C4432F',blockedGlow:'#F3D3CB',hint:'#D9A227'},
themes:themes(
['#B7CBD3','#DBC4A6','#BBCBB1','#D7BAB5','#1F2C3B'],
['晨雾','杏仁','苔原','暮砂','夜航'],
[
{board:'#F5F2EA',boardGrid:'#E9E3D6',track:'#3A5068',trackHalo:'#E2DBCC',rim:'#7B8F9C',base:'#95ACB6'},
{board:'#F7F2E7',boardGrid:'#EAE3D3',track:'#3A5068',trackHalo:'#E4DCCA',rim:'#98836A',base:'#BFA483'},
{board:'#F5F3E9',boardGrid:'#E8E4D6',track:'#3A5068',trackHalo:'#E1DCCC',rim:'#7E9077',base:'#9EB194'},
{board:'#F7F1EA',boardGrid:'#EAE2D8',track:'#3A5068',trackHalo:'#E4DBCF',rim:'#9D817D',base:'#BB9C97'},
{board:'#F6F2E9',boardGrid:'#E9E3D6',track:'#3A5068',trackHalo:'#E2DBCC',rim:'#55697C',base:'#121C27'},
],
[[0,2,5,3,1,8,6],[1,0,2,6,3,9,7],[3,6,1,0,4,10,2],[6,0,3,5,2,11,8],[2,5,0,6,1,9,3]],
['晨雾蓝灰世界色配奶白卡片,靛蓝、松石与琥珀领色。','杏仁暖砂世界色,朱砂与靛蓝领色,像午后的纸面。','苔原灰绿世界色,紫藤与玫红提亮。','暮砂玫瑰灰世界色,玫红与靛蓝对照。','夜航深蓝灰世界色,奶白卡片像被灯照亮的图。']),
pigments:makePigments(['#2F5FBF','#C4432F','#1C8378','#7A4EB8','#4A8235','#A8690C','#BF3A6B','#4A6B8A','#B0572F','#357358','#8A5C92','#5A6D7C'],['靛蓝','朱砂','松石','紫藤','苔绿','琥珀','玫红','钢蓝','赤陶','墨绿','藕紫','铁灰']).map(p=>({...p,arrow:'#FFFFFF'}))},
{id:'macaron',code:'07',name:'马卡龙',en:'MACARON',tag:'参考 Meowdoku',style:'macaron',accent:'#E07416',paper:'#FBF6F0',journeyWorlds:true,layout:{rotation:'under-board',rim:.40,pad:1.1},motion:{intro:720,pop:true,celebrate:720},hud:{level:'关卡',moves:'待解',tutorial:'入门'},
description:'奶油色世界上托一张白色软托盘,六角格像带白缝的软糖砖,藕紫轨道压在糖砖上。棋子是糖果色圆角六角,白色贴纸边,粗圆头白箭头。反馈用果糖色说话:路线点亮、糖砖出场、离场撒星。',
principles:['白托盘加白缝,格与格靠留白分开,不画格线','十二色统一白色粗箭头,与白缝同一套白','状态用颜色和弹跳表达:路线点亮、碰撞橘红、出场从左上角一波弹开'],
route:{blocked:'#F0663A',hint:'#F7C948'},
themes:themes(
['#F5EFE7','#FBE6DA','#EEE8F6','#F9E2E8','#3E2E35'],
['奶油','蜜桃','香芋','草莓牛奶','可可夜'],
[
{board:'#FADBCB',boardGrid:'#FFFFFF',track:'#96707B',trackHalo:'#FFFFFF',rings:['#FADBCB','#FAE9BE','#D6EEE1'],tray:'#FFFFFF',shadow:'#E6DDD6',shadowSoft:'#EEE7E0',blush:'#F9B9A6',blocked:'#F0663A',hint:'#F7C948',hintGlow:'#FCE7B5'},
{board:'#FDE3D3',boardGrid:'#FFFFFF',track:'#976A66',trackHalo:'#FFFFFF',rings:['#FDE3D3','#FBEDC4','#F4D9E2'],tray:'#FFFFFF',shadow:'#EBD5C9',shadowSoft:'#F3DED4',blush:'#F9B9A6',blocked:'#F0663A',hint:'#F7C948',hintGlow:'#FCE7B5'},
{board:'#E9E0F6',boardGrid:'#FFFFFF',track:'#78678F',trackHalo:'#FFFFFF',rings:['#E9E0F6','#F8E3EA','#D9EAF1'],tray:'#FFFFFF',shadow:'#DED6EA',shadowSoft:'#E6DFF0',blush:'#F9B9A6',blocked:'#F0663A',hint:'#F7C948',hintGlow:'#FCE7B5'},
{board:'#FBDDE5',boardGrid:'#FFFFFF',track:'#966076',trackHalo:'#FFFFFF',rings:['#FBDDE5','#FDEBCF','#E3EEDA'],tray:'#FFFFFF',shadow:'#EAD0D8',shadowSoft:'#F1D9E0',blush:'#F9B9A6',blocked:'#F0663A',hint:'#F7C948',hintGlow:'#FCE7B5'},
{board:'#F9E3D6',boardGrid:'#FFFFFF',track:'#8A6370',trackHalo:'#FFFFFF',rings:['#F9E3D6','#FAEBC8','#DAEBE3'],tray:'#FFF9F3',shadow:'#2A1E23',shadowSoft:'#34272C',blush:'#F9B9A6',blocked:'#F0663A',hint:'#F7C948',hintGlow:'#FCE7B5'},
],
[[0,5,3,2,6,1,4],[1,9,0,4,2,10,3],[6,7,4,2,11,5,3],[7,3,5,2,11,8,1],[4,0,2,6,1,9,3]],
['奶油底、白托盘,草莓、蓝莓与抹茶领色,像一盒刚打开的马卡龙。','蜜桃奶油底,蜜柑与海盐蓝领色,暖一点、甜一点。','香芋奶油底,香芋紫与玫瑰领色,冷一点、静一点。','草莓牛奶底,玫瑰与抹茶对照,白托盘更亮。','可可夜底,白托盘像被灯照亮的甜品盘,薄荷与草莓最先出现。'],
['#7A5560','#7C4E52','#5F4E78','#7B4658','#FBEFE6']),
pigments:makePigments(['#EE5398','#E5680C','#AB8716','#4C9E5A','#369C94','#578BF0','#937DE4','#D06E93','#B5806A','#4A91D4','#8C68CC','#C56AC4'],['草莓','蜜柑','蜂蜜','抹茶','薄荷','蓝莓','香芋','玫瑰','可可','海盐','葡萄','洋李']).map(p=>({...p,arrow:'#FFFFFF'}))},
]);
export function artDirection(id){return ART_DIRECTIONS.find(d=>d.id===id)||ART_DIRECTIONS[0];}
export function directionTheme(direction,themeId,count=7){
const source=direction.themes.find(t=>t.id===themeId)||direction.themes[0];
return {...themeWithColors(source,count),artStyle:direction.style,pigments:direction.pigments,route:direction.route};
}
export function appearanceTile(tile,theme){
const color=theme?.pigments?.[tile.color];
return {...tile,artStyle:theme?.artStyle,fill:color?.color,arrow:color?.arrow};
}