|
| 1 | +/** |
| 2 | + * 世界状态派生层(宪法 P1 · 数据接线) |
| 3 | + * World state derivation layer (constitution P1 · data wiring) |
| 4 | + * |
| 5 | + * @ai-context: 从留存引擎(珊瑚/发现)数据派生熵可视化单范式所需的世界信号集。 |
| 6 | + * 纯函数、无副作用、可单测。所有输出遵循《熵可视化设计宪法》第一条映射表 |
| 7 | + * 与第二条焦虑防线:vitality→发光体亮度、mist→混沌雾(上限 0.4)、 |
| 8 | + * depthNorm→地质层、firefly→萤火数量、warmth→洋流温度。 |
| 9 | + * 留存关闭时返回中性信号(第二条 §5 可关闭性:纯净亮度映射,无雾)。 |
| 10 | + * |
| 11 | + * @ai-context: Derives world signals for the entropy-visualization paradigm |
| 12 | + * from retention engine data (corals/discoveries). Pure, testable, bounded |
| 13 | + * by the design constitution's mapping table and anxiety-defense limits. |
| 14 | + */ |
| 15 | + |
| 16 | +import type { CoralRecord } from '../types'; |
| 17 | + |
| 18 | +/** 归一化锚点(世界饱和度常量) / Normalization anchors */ |
| 19 | +export const WORLD_ANCHORS = { |
| 20 | + /** 地质层满饱和的累计深度(米) / Depth where strata saturate */ |
| 21 | + DEPTH_FULL: 2000, |
| 22 | + /** 萤火密度满饱和的发现数 / Discoveries where firefly density saturates */ |
| 23 | + FIREFLY_FULL: 20, |
| 24 | + /** 洋流温度满饱和的连击天数 / Streak days where warmth saturates */ |
| 25 | + STREAK_FULL: 7, |
| 26 | + /** 混沌雾最大强度(宪法第二条 §1:≤40%) / Max mist per constitution */ |
| 27 | + MIST_MAX: 0.4, |
| 28 | + /** 冷启动基底雾(宪法第七条:未点亮的混沌,非空白) / Cold-start base mist */ |
| 29 | + MIST_COLD_START: 0.12, |
| 30 | + /** 冷启动世界活力中性值 / Cold-start neutral vitality */ |
| 31 | + VITALITY_COLD_START: 0.55, |
| 32 | +} as const; |
| 33 | + |
| 34 | +/** 派生输入:全部来自 retention 引擎存量 / Inputs from existing retention stores */ |
| 35 | +export interface WorldSignalInput { |
| 36 | + corals: CoralRecord[]; |
| 37 | + totalDepth: number; |
| 38 | + discoveriesCount: number; |
| 39 | + currentStreak: number; |
| 40 | + /** 留存总开关:关闭→中性信号(纯净亮度映射) / Master toggle off → neutral */ |
| 41 | + enabled: boolean; |
| 42 | +} |
| 43 | + |
| 44 | +/** 世界信号集:每个字段只映射一个学习变量(宪法第一条) / One signal = one variable */ |
| 45 | +export interface WorldSignals { |
| 46 | + /** 世界活力 0–1:健康珊瑚占比 → 发光体亮度 / Healthy coral ratio → glow */ |
| 47 | + vitality: number; |
| 48 | + /** 混沌雾强度 0–0.4:白化占比 → 遗忘(熵增) / Bleached ratio → mist */ |
| 49 | + mist: number; |
| 50 | + /** 地质层规模 0–1:累计深度归一 / Cumulative depth normalized */ |
| 51 | + depthNorm: number; |
| 52 | + /** 萤火密度 0–1:发现数归一 / Discovery count normalized */ |
| 53 | + firefly: number; |
| 54 | + /** 洋流温度 0–1:连击归一 / Streak normalized */ |
| 55 | + warmth: number; |
| 56 | + /** 留存是否开启 / Retention enabled flag */ |
| 57 | + enabled: boolean; |
| 58 | +} |
| 59 | + |
| 60 | +const clamp01 = (v: number): number => Math.min(1, Math.max(0, v)); |
| 61 | + |
| 62 | +/** |
| 63 | + * 派生世界信号集 / Derive the world signal set |
| 64 | + * |
| 65 | + * @ai-context 奖赏回来原则:白化珊瑚映射为"朦胧雾"而非消亡—— |
| 66 | + * 雾永远可拨开,复习(新的种植)即恢复。无任何负向输出语义。 |
| 67 | + */ |
| 68 | +export function deriveWorldSignals(input: WorldSignalInput): WorldSignals { |
| 69 | + const { corals, totalDepth, discoveriesCount, currentStreak, enabled } = input; |
| 70 | + const depthNorm = clamp01(totalDepth / WORLD_ANCHORS.DEPTH_FULL); |
| 71 | + const firefly = clamp01(discoveriesCount / WORLD_ANCHORS.FIREFLY_FULL); |
| 72 | + const warmth = clamp01(currentStreak / WORLD_ANCHORS.STREAK_FULL); |
| 73 | + |
| 74 | + // 焦虑防线 §5:用户关闭留存/混沌雾渲染 → 纯净亮度映射,无雾 |
| 75 | + if (!enabled) { |
| 76 | + return { vitality: 1, mist: 0, depthNorm, firefly: 0, warmth: 0, enabled: false }; |
| 77 | + } |
| 78 | + |
| 79 | + // 冷启动:未点亮的混沌世界(宪法第七条)——中性活力 + 薄雾,暗示可能性 |
| 80 | + if (corals.length === 0) { |
| 81 | + return { |
| 82 | + vitality: WORLD_ANCHORS.VITALITY_COLD_START, |
| 83 | + mist: WORLD_ANCHORS.MIST_COLD_START, |
| 84 | + depthNorm: 0, |
| 85 | + firefly, |
| 86 | + warmth, |
| 87 | + enabled: true, |
| 88 | + }; |
| 89 | + } |
| 90 | + |
| 91 | + const healthy = corals.filter((c) => c.health === 'healthy').length; |
| 92 | + // 白化是"暂停生长"而非消亡:占比映射为雾,上限被宪法锁死 |
| 93 | + const bleachedRatio = 1 - healthy / corals.length; |
| 94 | + |
| 95 | + return { |
| 96 | + vitality: healthy / corals.length, |
| 97 | + mist: Math.min(WORLD_ANCHORS.MIST_MAX, bleachedRatio * WORLD_ANCHORS.MIST_MAX), |
| 98 | + depthNorm, |
| 99 | + firefly, |
| 100 | + warmth, |
| 101 | + enabled: true, |
| 102 | + }; |
| 103 | +} |
| 104 | + |
| 105 | +/** |
| 106 | + * 世界活力 → 发光体辉光乘数 / Vitality → entity glow multiplier |
| 107 | + * |
| 108 | + * @ai-context 输出域 0.6–1.15:只调节明暗程度,永不低于可读下限, |
| 109 | + * 避免"暗淡=惩罚"的负向语义(奖赏回来原则)。 |
| 110 | + */ |
| 111 | +export function vitalityToGlowScale(vitality: number): number { |
| 112 | + return 0.6 + clamp01(vitality) * 0.55; |
| 113 | +} |
0 commit comments