Files
HexTrails/src/production-plan.js
T

60 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { random } from './core.js';
export const PRODUCTION_PLAN_VERSION = '2026-09-12-r1';
export const PRODUCTION_CAMPAIGN = {
id: 'v0.5', revision: 1, chapterSize: 50,
chapters: ['初见弯路', '朝内的出口', '绕行花园', '轻轻让路', '环里的风景', '花瓣初开', '交织之间', '两岸来信', '回旋的发现', '层层放行', '走进花心', '曲径相逢', '另一侧的出口', '编织新路', '旋开的花园', '从容穿行', '旧路新发现', '花与回声', '千回百转', '一路盛放'],
};
export const EXPERIENCE_KINDS = {
tour: { name: '长路巡游', lesson: '点一枚棋子,跟着它沿弯路找到出口。', intent: '直接享受完整行进;多个出口提供连续点击的空间。' },
gate: { name: '清挡放行', lesson: '先让挡路的棋子离开,再放出里面的长路。', intent: '移走清楚可见的挡子,为后面的长路打开出口。' },
contrast: { name: '轻巧反差', lesson: '朝内也有出路,试着转动底盘再出发。', intent: '朝内的目标通过一次旋转接上长路;位置和箭头保持不变。' },
turn: { name: '旋转换路', lesson: '先放出能走的棋子,再转出另一条路。', intent: '棋盘初步释放后,转动轨道建立新的离场机会。' },
stages: { name: '分段释放', lesson: '一片一片清开,让不同区域的路线接连畅通。', intent: '清挡和换路分段展开,经过至少两次换路后放出主路线。' },
};
const families = ['petals', 'orbits', 'spiral', 'switchback', 'weave', 'crossing'];
// Teaching introduces a small set of actions. Later blocks vary their beat
// length/order; chapter boundaries are production units, not difficulty cliffs.
const introduction = [
['switchback', 'tour', 2, 3], ['orbits', 'tour', 2, 4],
['petals', 'gate', 2, 5], ['spiral', 'contrast', 2, 5],
['weave', 'tour', 2, 4], ['crossing', 'gate', 2, 6],
['orbits', 'turn', 2, 6], ['petals', 'tour', 3, 5],
['switchback', 'gate', 3, 7], ['spiral', 'turn', 3, 7],
];
const beats = [
['tour', 'gate', 'contrast', 'tour', 'turn', 'gate', 'stages', 'tour', 'gate'],
['gate', 'tour', 'turn', 'contrast', 'gate', 'tour', 'stages', 'turn'],
['contrast', 'gate', 'tour', 'turn', 'stages', 'tour', 'gate'],
];
export function productionSlot(index) {
if (!Number.isInteger(index) || index < 0 || index >= 1000) throw new RangeError('Production slot must be 0–999');
const chapter = Math.floor(index / 50), local = index % 50;
const rng = random(55012026 + index * 7919);
const phrase = beats[(chapter + Math.floor(local / 9)) % beats.length];
let kind = phrase[(local + chapter * 3) % phrase.length];
let family = families[(local + Math.floor(local / 6) * 2 + chapter * 5) % families.length];
let radius = chapter < 2 ? 3 : chapter < 5 ? 3 + (rng() > .65 ? 1 : 0) : 3 + Math.floor(rng() * 3);
let target = (kind === 'tour' ? 5 : 8) + Math.min(6, Math.floor(chapter / 3)) + Math.floor(rng() * 4);
if (local === 49) { family = 'petals'; kind = chapter < 2 ? 'tour' : 'stages'; radius = chapter < 2 ? 3 : 5; target = chapter < 2 ? 7 : 14; }
if (index < introduction.length) [family, kind, radius, target] = introduction[index];
if (index < 30 && kind === 'stages') kind = 'turn';
// A long open route reserves real space. Familiarity permits new relations,
// but does not create room for arbitrary extra pieces on a small board.
const capacity = ['contrast', 'turn'].includes(kind) ? { 2: 6, 3: 9, 4: 12, 5: 14 } : { 2: 6, 3: 11, 4: 14, 5: 17 };
target = Math.min(target, capacity[radius]);
// Chapter-sheet review found 849 and 850 too similar in both silhouette and
// release rhythm. A woven breather makes the final layered flower distinct.
if (index === 848) { family = 'weave'; kind = 'tour'; radius = 4; target = 8; }
return {
index, number: index + 1, chapter: chapter + 1, chapterName: PRODUCTION_CAMPAIGN.chapters[chapter],
family, kind, radius, target, variant: Math.floor(index / 6),
minHeroBends: index === 0 ? 3 : radius === 2 ? 5 : 7,
maxHeroBends: radius === 2 ? 22 : radius === 3 ? 36 : 70,
seed: 55000000 + index * 1009,
phase: chapter < 2 ? 'learn' : chapter < 6 ? 'practice' : chapter < 12 ? 'combine' : 'fluent',
};
}