dsh-math-calculator 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +52 -10
  3. package/dsh.plugin.json +15 -8
  4. package/lib/awareness.js +16 -13
  5. package/lib/body-catalog.js +488 -0
  6. package/lib/body-scaling.js +288 -0
  7. package/lib/body-taxonomy.js +719 -0
  8. package/lib/detector.js +361 -16
  9. package/lib/empirical-decomposition.js +321 -0
  10. package/lib/format.js +113 -5
  11. package/lib/formula-library-generic.js +226 -0
  12. package/lib/formulas.js +71 -13
  13. package/lib/index.js +613 -77
  14. package/lib/ops/bodies-op.js +368 -0
  15. package/lib/ops/shared.js +64 -4
  16. package/lib/ops/workspace-op.js +19 -1
  17. package/lib/ops/world-op.js +22 -0
  18. package/lib/playbook.js +6 -1
  19. package/lib/precision-report.js +80 -0
  20. package/lib/result-cache.js +144 -0
  21. package/lib/types/awareness.d.ts +10 -4
  22. package/lib/types/body-catalog.d.ts +156 -0
  23. package/lib/types/body-scaling.d.ts +123 -0
  24. package/lib/types/body-taxonomy.d.ts +95 -0
  25. package/lib/types/detector.d.ts +18 -0
  26. package/lib/types/empirical-decomposition.d.ts +107 -0
  27. package/lib/types/format.d.ts +33 -4
  28. package/lib/types/formula-library-generic.d.ts +37 -0
  29. package/lib/types/formulas.d.ts +35 -7
  30. package/lib/types/index.d.ts +16 -0
  31. package/lib/types/ops/bodies-op.d.ts +35 -0
  32. package/lib/types/ops/shared.d.ts +94 -3
  33. package/lib/types/precision-report.d.ts +59 -0
  34. package/lib/types/result-cache.d.ts +78 -0
  35. package/lib/types/workspace.d.ts +24 -0
  36. package/lib/types/world.d.ts +8 -0
  37. package/lib/workspace.js +22 -0
  38. package/lib/world.js +38 -3
  39. package/package.json +1 -1
  40. package/src/awareness.ts +16 -13
  41. package/src/body-catalog.ts +619 -0
  42. package/src/body-scaling.ts +357 -0
  43. package/src/body-taxonomy.ts +806 -0
  44. package/src/detector.ts +403 -15
  45. package/src/empirical-decomposition.ts +411 -0
  46. package/src/format.ts +123 -5
  47. package/src/formula-library-generic.ts +242 -0
  48. package/src/formulas.ts +79 -13
  49. package/src/index.ts +642 -64
  50. package/src/ops/bodies-op.ts +415 -0
  51. package/src/ops/shared.ts +141 -4
  52. package/src/ops/workspace-op.ts +21 -1
  53. package/src/ops/world-op.ts +22 -0
  54. package/src/playbook.ts +6 -1
  55. package/src/precision-report.ts +87 -0
  56. package/src/result-cache.ts +157 -0
  57. package/src/workspace.ts +34 -0
  58. package/src/world.ts +51 -4
@@ -0,0 +1,144 @@
1
+ /**
2
+ * 计算结果缓存:同样的输入不再算第二遍。
3
+ *
4
+ * ## 为什么值得做(而不只是优化)
5
+ *
6
+ * 反复算同一个式子在真实使用里很常见:
7
+ * · AI 校验自己的结论(「再算一次确认」)——输入完全相同
8
+ * · 同一批数据换着角度问(同一条 `1/7` 在几个不同问题里出现)
9
+ * · 推演时反复引用同一个常量式
10
+ *
11
+ * 每次重算的代价不只是 CPU,还有**结果文本再次进入上下文**:
12
+ * 一个 64 位小数的返回值约 65 字符,重复十次就是 650 字符的 token。
13
+ * 缓存命中时仍要返回结果(AI 需要看到值),但省掉了重算,
14
+ * 更重要的是对**批量和重复调用**能显著降低总开销。
15
+ *
16
+ * ## 缓存键必须包含什么
17
+ *
18
+ * 少放一个就会返回**错的结果**——这比不缓存危险得多:
19
+ * · 规范化后的表达式(`1/7` 与 `1 / 7` 应算同一条,但不能把 `1/7` 和 `2/7` 撞车)
20
+ * · operation(`evaluate "1+1"` 与 `solve "1+1"` 结果不同)
21
+ * · precision(计算精度不同,结果不同)
22
+ * · displayPrecision(只影响显示,但也进键——否则命中后位数不对)
23
+ * · **当前常数状态**(改了 c/G 之后同样的式子结果不同)
24
+ * · **工作区**(不同世界的公式表/变量可能不同)
25
+ *
26
+ * 最后两项最容易漏。实测过:改常数后若缓存不失效,
27
+ * 会返回「改之前」的数——那是静默错误。
28
+ *
29
+ * ## 容量与淘汰
30
+ *
31
+ * 固定容量 + LRU。不设 TTL:常数或工作区一变,键就变了,旧条目自然不再命中;
32
+ * 保留它们没有危害,只是占内存,所以用容量上限控制。
33
+ *
34
+ * @module dsh-math-calculator/result-cache
35
+ */
36
+ /** 缓存容量上限。 */
37
+ const MAX_ENTRIES = 256;
38
+ /** 主存储。Map 保留插入顺序,正好用来做 LRU。 */
39
+ const store = new Map();
40
+ /** 命中/未命中统计(供诊断)。 */
41
+ let hits = 0;
42
+ let misses = 0;
43
+ /**
44
+ * 把一个值规范成稳定的字符串,用于参与缓存键。
45
+ *
46
+ * 为什么不用 JSON.stringify:对象键的顺序不保证一致
47
+ * (`{a:1,b:2}` 与 `{b:2,a:1}` 应视为同一个键),
48
+ * 且浮点表示可能因来源不同而不一致。这里按键名排序后拼接。
49
+ *
50
+ * @param value - 任意值
51
+ * @returns 稳定字符串
52
+ */
53
+ function stableStringify(value) {
54
+ if (value === null || value === undefined)
55
+ return String(value);
56
+ const kind = typeof value;
57
+ if (kind === 'number') {
58
+ // 数字统一用 15 位有效数字,避免 -0 / 0.1+0.2 这类表示差异
59
+ return Number.isFinite(value) ? value.toPrecision(15) : String(value);
60
+ }
61
+ if (kind === 'string' || kind === 'boolean')
62
+ return String(value);
63
+ if (Array.isArray(value))
64
+ return '[' + value.map(stableStringify).join(',') + ']';
65
+ if (kind === 'object') {
66
+ const object = value;
67
+ const keys = Object.keys(object).sort();
68
+ return '{' + keys.map(function (key) {
69
+ return key + ':' + stableStringify(object[key]);
70
+ }).join(',') + '}';
71
+ }
72
+ return String(value);
73
+ }
74
+ /**
75
+ * 计算缓存键。
76
+ *
77
+ * @param parts - 参与键的各个部分(按固定顺序传入)
78
+ * @returns 键字符串
79
+ */
80
+ export function cacheKey(parts) {
81
+ return parts.map(stableStringify).join('\u0001');
82
+ }
83
+ /**
84
+ * 取缓存。
85
+ *
86
+ * @param key - 缓存键
87
+ * @returns 命中则返回缓存值;未命中返回 undefined
88
+ */
89
+ export function getCached(key) {
90
+ const entry = store.get(key);
91
+ if (entry === undefined) {
92
+ misses++;
93
+ return undefined;
94
+ }
95
+ hits++;
96
+ entry.hits++;
97
+ // LRU:命中后移到队尾(最新)
98
+ store.delete(key);
99
+ store.set(key, entry);
100
+ return entry.value;
101
+ }
102
+ /**
103
+ * 存缓存。
104
+ *
105
+ * @param key - 缓存键
106
+ * @param value - 要缓存的值
107
+ */
108
+ export function setCached(key, value) {
109
+ if (store.has(key))
110
+ store.delete(key);
111
+ store.set(key, { value, hits: 0 });
112
+ // 超容量时淘汰最久未使用的(队首)
113
+ while (store.size > MAX_ENTRIES) {
114
+ const oldest = store.keys().next();
115
+ if (oldest.done)
116
+ break;
117
+ store.delete(oldest.value);
118
+ }
119
+ }
120
+ /**
121
+ * 清空缓存(改常数、切工作区时应当调用)。
122
+ *
123
+ * 为什么需要显式清空:缓存的键里已经含常数与工作区,
124
+ * 理论上不会串;但**清空是个安全网**——
125
+ * 万一某条路径漏把常数放进键里,清空能兜住,而不是静默返回旧值。
126
+ */
127
+ export function clearCache() {
128
+ store.clear();
129
+ }
130
+ /**
131
+ * 缓存统计(供诊断与测试)。
132
+ *
133
+ * @returns 条目数、命中、未命中
134
+ */
135
+ export function cacheStats() {
136
+ return { size: store.size, hits: hits, misses: misses };
137
+ }
138
+ /**
139
+ * 重置统计(不影响已存条目)。
140
+ */
141
+ export function resetCacheStats() {
142
+ hits = 0;
143
+ misses = 0;
144
+ }
@@ -32,10 +32,16 @@ export declare function getPromptMode(): PromptMode;
32
32
  /**
33
33
  * 第一层:工具描述。
34
34
  *
35
- * 这是模型**每次**看工具清单时都会读到的内容,所以必须:
36
- * - 完整列出 operation(漏一个,模型就不知道它存在)
37
- * - 写「何时使用」而不是只写「是什么」
38
- * - 给出触发关键词,让模型在遇到这类问题时想起它
35
+ * 这是模型**每次**看工具清单时都会读到的内容 —— 属于每轮固定成本,
36
+ * 所以要跟 parameters schema 一样抠字数。
37
+ *
38
+ * 压缩原则:
39
+ * · **不重复 enum**:operation 的取值已在 schema 的 enum 里列全(300 字符),
40
+ * 这里再列一遍是纯重复。只保留「怎么选」的判断线索。
41
+ * · **能移到 help 的就移**:详细字段表、单位清单等只在用到时才需要,
42
+ * 放 help 里不占每轮成本。
43
+ * · **保留行为约束**:如「必须调本工具」「精度分两层」这类会改变模型行为的,
44
+ * 不能砍 —— 它们省下的重算远多于自身开销。
39
45
  */
40
46
  export declare const TOOL_DESCRIPTION: string;
41
47
  /**
@@ -0,0 +1,156 @@
1
+ /**
2
+ * 天体目录:恒星、行星、卫星、小天体的代表性样本。
3
+ *
4
+ * ## 为什么需要这个,而不是让作者自己查
5
+ *
6
+ * 写架空世界时最费劲的一步是「先锚一个现实参照物」:我要写一颗潮汐锁定的
7
+ * 红矮星行星,它该多大、多重、多少度?没有参照,数字全靠猜。
8
+ *
9
+ * 本文件收录真实观测值(尽量带来源量级),让作者能:
10
+ * · 直接拿某颗真实天体当基线改(「比开普勒-1649c 再热一点」)
11
+ * · 或看一组同类天体的分布范围,判断自己的设定落在什么分位
12
+ *
13
+ * ## 最关键的设计:区分「物理决定」与「只在地球标定」
14
+ *
15
+ * 这是本项目反复强调的分层。改物理常数(α、G、c)时,不同量必须区别对待:
16
+ *
17
+ * | 类别 | 例 | 改常数时会怎样 |
18
+ * |---|---|---|
19
+ * | `first-principles` | 质量、半径、轨道半径 | **不变**。它们由引力与初始条件决定,与 α 几乎无关 |
20
+ * | `scaled` | 光度、有效温度、寿命 | **变**。由核反应速率与斯特藩-玻尔兹曼决定,依赖 α |
21
+ * | `calibrated` | 行星平衡温度、反照率、温室增温 | **只在地球标定下可信**。超出适用域时本文件只给方向 |
22
+ * | `contingent` | 大气成分、云量、磁场、板块 | **不可推导**。这是历史偶然,不是物理必然 |
23
+ *
24
+ * ⚠️ 最容易犯的错:把 `calibrated` 的数值当成物理定律。
25
+ * 地球反照率 0.30 不是物理常数,是「有云、有海、有冰」的结果——
26
+ * 换一颗行星就完全不同。所以每个 calibrated 条目都写明适用域。
27
+ *
28
+ * ## 收录标准
29
+ *
30
+ * 只收**有可靠观测或可靠理论**的样本,不收「疑似存在的系外行星」。
31
+ * 数值给到有效位数为止,不假装精确——观测误差本来就大。
32
+ *
33
+ * @module dsh-math-calculator/body-catalog
34
+ */
35
+ /** 一个量对物理常数的依赖方式(决定改常数后它该不该变)。 */
36
+ export type ConstantDependence = 'first-principles' | 'scaled' | 'calibrated' | 'contingent';
37
+ /** 一条带来源的数值。 */
38
+ export interface MeasuredValue {
39
+ /** 数值。 */
40
+ value: number;
41
+ /** 单位。 */
42
+ unit: string;
43
+ /**
44
+ * 对物理常数的依赖方式。
45
+ *
46
+ * 改常数后是否重算,由它决定:
47
+ * first-principles / contingent → **不变**
48
+ * scaled → **会变**(由公式决定)
49
+ * calibrated → **只在地球适用域内可信**
50
+ */
51
+ dependence: ConstantDependence;
52
+ /** 观测/理论误差(1σ 或量级)。没有就给 undefined,别编。 */
53
+ uncertainty?: number;
54
+ /** 适用域(calibrated 必填)。 */
55
+ domain?: string;
56
+ /** 来源(尽量写具体:任务名 / 目录名 / 理论)。 */
57
+ source: string;
58
+ }
59
+ /** 天体类别。 */
60
+ export type BodyKind = 'star' | 'planet-terrestrial' | 'planet-gas-giant' | 'planet-ice-giant' | 'planet-super-earth' | 'planet-mini-neptune' | 'moon' | 'dwarf-planet' | 'asteroid' | 'kuiper-object';
61
+ /** 一个天体的目录条目。 */
62
+ export interface BodyEntry {
63
+ /** 标识。 */
64
+ id: string;
65
+ /** 中文名。 */
66
+ name: string;
67
+ /** 类别。 */
68
+ kind: BodyKind;
69
+ /** 一句话:它是什么样的天体,读者会怎么感知。 */
70
+ summary: string;
71
+ /** 宿主恒星(行星/卫星才填)。 */
72
+ host?: string;
73
+ /** 轨道半长轴(AU)。 */
74
+ semiMajorAxisAU?: MeasuredValue;
75
+ /** 质量(地球质量)。 */
76
+ massEarth?: MeasuredValue;
77
+ /** 半径(地球半径)。 */
78
+ radiusEarth?: MeasuredValue;
79
+ /** 表面重力(地球 g)。由 M/R² 算出时标 first-principles。 */
80
+ surfaceGravityG?: MeasuredValue;
81
+ /** 平衡温度 K(不含温室)。 */
82
+ equilibriumTempK?: MeasuredValue;
83
+ /** 实测表面/有效温度 K。 */
84
+ surfaceTempK?: MeasuredValue;
85
+ /** 几何反照率。 */
86
+ albedo?: MeasuredValue;
87
+ /** 自转周期(小时)。 */
88
+ rotationHours?: MeasuredValue;
89
+ /** 轴倾角(度)。 */
90
+ obliquityDeg?: MeasuredValue;
91
+ /** 轨道偏心率。 */
92
+ eccentricity?: MeasuredValue;
93
+ /** 大气压(地球=1)。 */
94
+ pressureAtm?: MeasuredValue;
95
+ /** 主要大气成分(百分比的粗描述)。 */
96
+ atmosphere?: string;
97
+ /** 是否潮汐锁定。 */
98
+ tidallyLocked?: boolean;
99
+ /** 作者需要注意的要点。 */
100
+ notes: string;
101
+ }
102
+ /** 恒星目录条目(比 STAR_CLASSES 更细,带真实样本)。 */
103
+ export interface NamedStar extends BodyEntry {
104
+ kind: 'star';
105
+ /** 光谱型。 */
106
+ spectralType: string;
107
+ /** 亮度级:V 主序 / IV 次巨星 / III 巨星 / II 亮巨星 / I 超巨星 / D 白矮星。 */
108
+ luminosityClass: 'V' | 'IV' | 'III' | 'II' | 'I' | 'D';
109
+ /** 光度(太阳)。 */
110
+ luminositySun: MeasuredValue;
111
+ /** 质量(太阳)。 */
112
+ massSun: MeasuredValue;
113
+ /** 半径(太阳)。 */
114
+ radiusSun: MeasuredValue;
115
+ /** 有效温度 K。 */
116
+ effectiveTempK: MeasuredValue;
117
+ }
118
+ /**
119
+ * 各亮度级的代表性恒星。
120
+ *
121
+ * ## 为什么必须补这一层
122
+ *
123
+ * 原 `star-catalog.ts` 只有主序星(V)。而同样光谱字母的巨星比主序星亮
124
+ * 20—100 倍 —— 作者写「F5-IIIa」时,用 F5V 顶替会让平衡温度差出几百开
125
+ * (实测:0.98 AU 处 257 K vs 576 K,从冰封翻到熔融)。
126
+ *
127
+ * 下面这些是各亮度级里最有名的样本,可直接拿来当参照。
128
+ */
129
+ export declare const NAMED_STARS: NamedStar[];
130
+ /**
131
+ * 行星样本。
132
+ *
133
+ * 太阳系 8 颗 + 代表性系外行星。数值全部带 dependence 标记:
134
+ * 质量与半径是 first-principles(改常数不会让火星变重),
135
+ * 平衡温度与反照率是 calibrated(只在与观测匹配的大气模型下可信)。
136
+ */
137
+ export declare const NAMED_PLANETS: BodyEntry[];
138
+ /** 矮行星与小天体样本。 */
139
+ export declare const NAMED_SMALL_BODIES: BodyEntry[];
140
+ /** 全部带名字的天体(恒星 + 行星 + 小天体)。 */
141
+ export declare const ALL_NAMED_BODIES: Array<NamedStar | BodyEntry>;
142
+ /** 按 id 索引。 */
143
+ export declare const BODY_INDEX: Map<string, NamedStar | BodyEntry>;
144
+ /** 按类别分组(给检索用)。 */
145
+ export declare function bodiesByKind(kind: BodyKind): Array<NamedStar | BodyEntry>;
146
+ /**
147
+ * 判断某个量在改物理常数后是否该重算。
148
+ *
149
+ * 为什么单独做成函数:作者(和 AI)最容易犯的错就是
150
+ * 「改了 α 之后把行星质量也跟着改」——质量由引力与初始条件决定,
151
+ * 与精细结构常数几乎无关,改了就是篡改设定。
152
+ *
153
+ * @param dependence - 该量的依赖类别
154
+ * @returns 改常数后是否应重算
155
+ */
156
+ export declare function shouldRescaleOnConstantChange(dependence: ConstantDependence): boolean;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * 天体量的常数传导:改了基础物理常数之后,天体数据里**哪些必须重算、哪些绝不能动**。
3
+ *
4
+ * ## 这是本模块存在的唯一理由
5
+ *
6
+ * 「把宇宙所有星体的数据都补上」听起来是填表,但一旦作者改了 α、G 或 c,
7
+ * 这张表立刻分裂成两类:
8
+ *
9
+ * | 类别 | 例 | 改 α 之后 |
10
+ * |---|---|---|
11
+ * | **不变** | 行星质量、半径、轨道半长轴 | 完全不动。它们由引力与初始条件决定,与电磁力几乎无关 |
12
+ * | **必变** | 恒星光度、寿命、有效温度、平衡温度 | 跟着 α 走,公式自己会算 |
13
+ * | **不可推导** | 大气成分、云量、反照率、磁场、板块 | 没有公式,是历史偶然。只能提示作者「这条要你自己定」 |
14
+ *
15
+ * 最容易犯的错有两个,都是**静默**的:
16
+ * 1. 改了 α 之后把行星质量也跟着改 —— 那是篡改设定(本模块明确拒绝)
17
+ * 2. 改了 α 之后忘了恒星光度要变 —— 于是气候结论还是旧的
18
+ *
19
+ * ## 传导链(α 为例)
20
+ *
21
+ * ```
22
+ * α ↑
23
+ * ├→ 库仑势垒 ↑ → 核聚变点火温度 ↑ → 同质量恒星光度 ↓、寿命 ↑
24
+ * ├→ 电子散射不透明度 ↑ → 辐射压 ↑ → 恒星光度 ↓
25
+ * ├→ 化学键能 ∝ α² → 材料强度、生物体型上限 ↑
26
+ * └→ 玻尔半径 ∝ 1/α → 原子尺度 ↓ → 物质密度 ↑
27
+ *
28
+ * 行星质量、半径、轨道:**不动**(它们的定义式里没有 α)
29
+ * 平衡温度:随恒星光度变(因为辐照变了)
30
+ * 反照率:不动,但它的数值本身只是地球标定
31
+ * ```
32
+ *
33
+ * @module dsh-math-calculator/body-scaling
34
+ */
35
+ import type { ConstantDependence, MeasuredValue } from './body-catalog.js';
36
+ /** 一条传导结论。 */
37
+ export interface ScalingRule {
38
+ /** 受影响的天体量(中文名)。 */
39
+ quantity: string;
40
+ /** 依赖哪个常数。 */
41
+ constant: string;
42
+ /** 随常数变化的幂次(相对变化 = 常数相对变化 ^ exponent)。0 表示无关。 */
43
+ exponent: number;
44
+ /** 可信度。 */
45
+ confidence: 'exact' | 'empirical' | 'order-of-magnitude' | 'direction-only';
46
+ /** 一句话说明机制。 */
47
+ mechanism: string;
48
+ /** 适用域;超出只降级不拒绝。 */
49
+ domain: string;
50
+ }
51
+ /**
52
+ * 常数 → 天体量的传导规则表。
53
+ *
54
+ * `exponent` 是这个表的核心:它回答「α 变 1%,这个量变百分之几」。
55
+ * 0 表示无关 —— 此时让它变才是错的。
56
+ */
57
+ export declare const SCALING_RULES: ScalingRule[];
58
+ /**
59
+ * 查某个天体量依赖哪个常数、怎么变。
60
+ *
61
+ * @param quantity - 量名(中文,如「恒星寿命」)
62
+ * @returns 匹配的规则(可能多条);没找到返回空数组
63
+ */
64
+ export declare function rulesFor(quantity: string): ScalingRule[];
65
+ /**
66
+ * 一次改常数后的传导结果。
67
+ */
68
+ export interface ScalingOutcome {
69
+ /** 改了哪个常数。 */
70
+ constant: string;
71
+ /** 变化率(如 1.5 表示变成原来的 1.5 倍,即 +50%)。 */
72
+ factor: number;
73
+ /** 有明确幂次的量:按指数重算。 */
74
+ effects: Array<{
75
+ quantity: string;
76
+ /** 该量的变化倍数。 */
77
+ multiplier: number;
78
+ /** 一句话结论。 */
79
+ text: string;
80
+ confidence: ScalingRule['confidence'];
81
+ }>;
82
+ /**
83
+ * **间接依赖**的量:要重算,但不能用幂次算。
84
+ *
85
+ * 例:行星平衡温度 T ∝ L^0.25,而 α 通过恒星结构改变 L。
86
+ * 这不是幂律关系,所以不能给出倍数 —— 只能提示「改完必须用 world 重跑」。
87
+ *
88
+ * ⚠️ 必须与 `unchanged` 严格分开:把这类量误列进「不该动」,
89
+ * 作者就会拿着旧温度去写新宇宙,那是这个功能最要命的失败模式。
90
+ */
91
+ indirect: Array<{
92
+ quantity: string;
93
+ reason: string;
94
+ }>;
95
+ /** 真正不该动的量(幂次严格为 0 且无间接依赖)。 */
96
+ unchanged: string[];
97
+ }
98
+ /**
99
+ * 计算某个常数变化后,各天体量的变化倍数。
100
+ *
101
+ * @param constant - 常数名(如 'alpha_fs'、'G'、'c')
102
+ * @param factor - 该常数变成原来的几倍
103
+ * @returns 传导结果
104
+ */
105
+ export declare function propagateConstantChange(constant: string, factor: number): ScalingOutcome;
106
+ /**
107
+ * 检查一条天体数据在改常数后是否应重算。
108
+ *
109
+ * @param value - 一条带依赖标记的数值
110
+ * @param constant - 改动的常数名
111
+ * @returns 是否应重算;以及原因
112
+ */
113
+ export declare function howToTreatValue(value: MeasuredValue, constant: string): {
114
+ rescale: boolean;
115
+ reason: string;
116
+ };
117
+ /**
118
+ * 依赖类别的人话说明(给输出用)。
119
+ *
120
+ * @param dependence - 依赖类别
121
+ * @returns 说明
122
+ */
123
+ export declare function describeDependence(dependence: ConstantDependence): string;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * 天体类型学:从已观测到纯理论预言,按「科幻创作会遇到什么」来分类。
3
+ *
4
+ * ## 为什么不能只收太阳系
5
+ *
6
+ * 太阳系只有 8 颗行星、1 颗恒星,而科幻创作要的是「一整个宇宙的可能性空间」:
7
+ * 流浪行星、热木星、超级地球、中子星、黑洞、白矮星、褐矮星、戴森球……
8
+ * 这些在太阳系里一个都没有,但作者天天要用。
9
+ *
10
+ * ## 三类条目,可信度天差地别
11
+ *
12
+ * | 类别 | 例 | 依据 | 本文件怎么处理 |
13
+ * |---|---|---|---|
14
+ * | **已观测** | 热木星、超级地球、潮汐锁定行星 | 凌星/径向速度/直接成像 | 给观测值或严格推算 |
15
+ * | **理论预言** | 中子星、黑洞、白矮星、褐星 | 广义相对论/恒星演化模型 | 给理论值 + 标注「未直接观测」 |
16
+ * | **纯思辨** | 戴森球、伯纳德星、质量 1e15 kg 的「行星」 | 没有观测也没有严格理论 | 给量纲约束 + 明确标「思辨」 |
17
+ *
18
+ * ⚠️ 最容易误导作者的做法:把三类混在一起给数字,不标注来源。
19
+ * 于是「中子星表面重力 1e11 g」和「戴森球厚 1 公里」看起来一样权威——
20
+ * 前者是物理必然,后者只是某人拍脑袋。所以每条都必须带 `evidence`。
21
+ *
22
+ * ## 「预言的一个一个加」怎么理解
23
+ *
24
+ * 这里的「预言」指**理论预言**(theoretical prediction),不是占星术。
25
+ * 每类都写清:哪些参数由物理锁死(改不了)、哪些由形成历史决定(可以编)。
26
+ *
27
+ * @module dsh-math-calculator/body-taxonomy
28
+ */
29
+ /** 天体类型的证据等级。 */
30
+ export type EvidenceLevel = 'observed' | 'theoretical' | 'speculative';
31
+ /** 一个天体类型。 */
32
+ export interface BodyType {
33
+ /** 标识。 */
34
+ id: string;
35
+ /** 中文名。 */
36
+ name: string;
37
+ /** 英文名(便于检索与查文献)。 */
38
+ nameEn: string;
39
+ /** 归类:恒星残骸 / 行星 / 小天体 / 人工结构 / 纯思辨。 */
40
+ family: 'stellar-remnant' | 'star' | 'planet' | 'small-body' | 'exotic' | 'artificial';
41
+ /** 证据等级。 */
42
+ evidence: EvidenceLevel;
43
+ /** 依据(观测证据或理论出处)。 */
44
+ basis: string;
45
+ /** 一句话:读者会怎么感知到它。 */
46
+ perception: string;
47
+ /** 典型质量(kg)。 */
48
+ massKg?: number;
49
+ /** 典型半径(km)。 */
50
+ radiusKm?: number;
51
+ /** 典型表面重力(以地球 g 为单位)。 */
52
+ gravityG?: number;
53
+ /** 典型表面/有效温度 K。 */
54
+ tempK?: number;
55
+ /** 典型密度 kg/m³。 */
56
+ densityKgM3?: number;
57
+ /**
58
+ * 物理锁死的性质:改不了的部分。
59
+ *
60
+ * 例:中子星的半径与质量被状态方程锁死(1.4 M☉ 对应约 11—12 km),
61
+ * 作者可以编它的磁场、转速、伴星,但编不了「半径 1000 km 的中子星」。
62
+ */
63
+ lockedBy?: string;
64
+ /** 作者可以自由发挥的部分(编这些不会与物理冲突)。 */
65
+ freeToAuthor?: string;
66
+ /** 写这类天体时最容易踩的坑。 */
67
+ pitfall?: string;
68
+ /** 科幻创作中的典型用法。 */
69
+ narrativeUse?: string;
70
+ /** 对物理常数的依赖(改 α 之类会不会变)。 */
71
+ constantDependence?: 'first-principles' | 'scaled' | 'calibrated' | 'contingent';
72
+ }
73
+ /**
74
+ * 天体类型总表。
75
+ *
76
+ * 按「创作时先想到什么」排序:恒星残骸最震撼,人工结构最极端,纯思辨放最后。
77
+ */
78
+ export declare const BODY_TYPES: BodyType[];
79
+ /** 按族分组。 */
80
+ export declare function typesByFamily(family: BodyType['family']): BodyType[];
81
+ /** 按证据等级分组。 */
82
+ export declare function typesByEvidence(evidence: EvidenceLevel): BodyType[];
83
+ /** 族的显示顺序。 */
84
+ export declare const FAMILY_ORDER: Array<BodyType['family']>;
85
+ /** 族的中文名。 */
86
+ export declare const FAMILY_LABEL: Record<BodyType['family'], string>;
87
+ /** 证据等级的中文名与说明。 */
88
+ export declare const EVIDENCE_LABEL: Record<EvidenceLevel, string>;
89
+ /**
90
+ * 按名字或名字片段查类型。
91
+ *
92
+ * @param query - 中文名/英文名/id 片段
93
+ * @returns 匹配的类型;没有返回 undefined
94
+ */
95
+ export declare function findBodyType(query: string): BodyType | undefined;
@@ -35,6 +35,14 @@ export interface DetectionResult {
35
35
  signals: string[];
36
36
  /** 提取到的候选表达式(供参考,不自动执行)。 */
37
37
  candidates: string[];
38
+ /**
39
+ * 分流档位:simple(不提醒)/ forced(必须用工具)/ fuzzy(按阈值)。
40
+ *
41
+ * 与 `score` 的区别:score 只回答「数学需求强不强」,
42
+ * 而档位回答「该心算还是该用工具」——
43
+ * `2+2` 得分很高但该心算,`20 个数字求和` 得分低但最该用工具。
44
+ */
45
+ demandClass?: DemandClass;
38
46
  }
39
47
  /** 检测配置。 */
40
48
  export interface DetectorConfig {
@@ -53,6 +61,16 @@ export interface DetectorConfig {
53
61
  export declare const DEFAULT_DETECTOR_CONFIG: DetectorConfig;
54
62
  /** 重置某个会话的检测状态(测试或用户手动清除时用)。 */
55
63
  export declare function resetDetectionState(sessionKey?: string): void;
64
+ /**
65
+ * 分流结论。
66
+ *
67
+ * | 档位 | 含义 | 处置 |
68
+ * |---|---|---|
69
+ * | `simple` | 短、少数字、纯四则、无数据信号 | **不提醒**(AI 自己算即可,想用工具仍可用) |
70
+ * | `forced` | 有数据/汇总/多步信号 | **强提醒**:这类务必用工具 |
71
+ * | `fuzzy` | 其它数学需求 | 一般提醒 |
72
+ */
73
+ export type DemandClass = 'simple' | 'forced' | 'fuzzy';
56
74
  /**
57
75
  * 检测文本里的计算需求。
58
76
  *
@@ -0,0 +1,107 @@
1
+ /**
2
+ * 经验公式的底层原理拆解:区分「物理内核」与「纯标定」,并明确标注哪些只在现实宇宙成立。
3
+ *
4
+ * ## 对上一版的自纠(2026-10-26 修订)
5
+ *
6
+ * 上一版把 `hull_speed` 归为 `scalable`(可泛化),理由是
7
+ * 「1.34 = Fr·√g 组合,换重力就能重算」。**这个判断过头了。**
8
+ *
9
+ * 真实的物理内核只有 Froude 数本身:
10
+ *
11
+ * v = Fr · √(g·L) ← 这是**量纲分析**的结论,跨世界成立
12
+ * Fr ≈ 0.4 ← 这是**经验值**,来自地球船舶的观测
13
+ *
14
+ * 而 Fr ≈ 0.4 是怎么来的?它是「兴波阻力急剧上升」的临界点,
15
+ * 本质上由**自由液面重力波的色散关系**决定:
16
+ *
17
+ * 波速 c = √(g·λ/(2π)) ← 深水重力波,**含 1/(2π)**
18
+ * 兴波极限 ≈ 船速追上自己的船首波 → 波长 ≈ 船长 L
19
+ * 代入得 v_limit ≈ √(g·L/(2π)) ≈ 0.4·√(g·L)
20
+ *
21
+ * 所以 Fr ≈ 0.4 ≈ 1/√(2π) **不是纯粹的经验拟合,而是色散关系的推论**。
22
+ * 这一点上一版说错了:它不是「地球船舶的经验常数」,而是物理推出来的。
23
+ *
24
+ * ## 但 Fr 的推导仍依赖「水」这个介质
25
+ *
26
+ * 1/(2π) 来自重力的**色散关系**(ω² = g·k),本身与介质无关;
27
+ * 但 `Fr ≈ 0.4` 的**数值**还隐含:
28
+ * · 介质是**液体**(有自由表面)
29
+ * · 表面张力可忽略(内河船长尺度下成立)
30
+ * · 兴波阻力主导(黏性阻力次要)
31
+ *
32
+ * 换一个**液体介质**(液氨、液态甲烷、岩浆),色散关系中的表面张力与黏性会变,
33
+ * 临界 Fr 会移动。所以:
34
+ * · **量纲形式** Fr·√(g·L) → 跨世界成立
35
+ * · **数值 0.4** → 只在「类水液体 + 宏观尺度」域成立
36
+ *
37
+ * ## 结论:三级标注,而不是两级
38
+ *
39
+ * | 级别 | 含义 | 例 |
40
+ * |---|---|---|
41
+ * | `dimensionless` | 量纲/标度形式跨世界成立,数值也无需改 | 克莱伯指数 0.75 |
42
+ * | `scalable-with-caveat` | **形式**可泛化,但**系数**需重新标定或验证 | Froude 0.4、潮汐系数 |
43
+ * | `earth-only` | 形式本身就是地球拟合,换世界无意义 | Mifflin 代谢、风寒、Du Bois |
44
+ *
45
+ * 上一版把 `hull_speed` 放在 `dimensionless` 那一侧,等于宣称
46
+ * 「0.4 换世界也精确成立」——那是**过度自信**。现在它被降级到 middle 档。
47
+ *
48
+ * @module dsh-math-calculator/empirical-decomposition
49
+ */
50
+ /** 拆解类型(三级)。 */
51
+ export type DecompositionKind = 'dimensionless' | 'scalable-with-caveat' | 'earth-only';
52
+ /** 一条经验公式的拆解。 */
53
+ export interface EmpiricalNote {
54
+ /** 公式 id。 */
55
+ id: string;
56
+ /** 中文名。 */
57
+ name: string;
58
+ /** 原表达式。 */
59
+ expr: string;
60
+ /** 拆解级别。 */
61
+ kind: DecompositionKind;
62
+ /**
63
+ * **底层原理**:这条式子真正的起点是什么。
64
+ *
65
+ * 与 `bakedIn` 的区别:bakedIn 说「哪个数字藏了什么」,
66
+ * principle 说「物理上为什么会有这条式子」。
67
+ */
68
+ principle: string;
69
+ /** 被烘焙的常数(原本藏起来的量)。 */
70
+ bakedIn?: string;
71
+ /**
72
+ * 泛化后**形式仍然成立**的部分(不含数值系数)。
73
+ *
74
+ * 写成「量纲正确但系数待定」的形式,让作者知道哪些能留、哪些要自己标。
75
+ */
76
+ genericForm?: string;
77
+ /** 泛化后**必须重新标定**的系数(以及为什么)。 */
78
+ retuneNeeded?: string;
79
+ /** 泛化后需要哪些物理量。 */
80
+ needsFrom?: string[];
81
+ /** 在什么条件下**形式本身**就失效(不是系数不准,而是式子不该用)。 */
82
+ breaksWhen?: string;
83
+ /** 一句话给作者。 */
84
+ advice: string;
85
+ }
86
+ /**
87
+ * 经验公式拆解表。
88
+ *
89
+ * 排序:从「最可靠」到「最只能在现实中用」。
90
+ */
91
+ export declare const EMPIRICAL_NOTES: EmpiricalNote[];
92
+ /** 拆解级别的人话说明。 */
93
+ export declare const KIND_LABEL: Record<DecompositionKind, string>;
94
+ /**
95
+ * 按级别分组。
96
+ *
97
+ * @param kind - 拆解级别
98
+ * @returns 该级别的条目
99
+ */
100
+ export declare function notesByKind(kind: DecompositionKind): EmpiricalNote[];
101
+ /**
102
+ * 查一条公式的拆解说明。
103
+ *
104
+ * @param id - 公式 id
105
+ * @returns 说明;没收录返回 undefined
106
+ */
107
+ export declare function empiricalNoteOf(id: string): EmpiricalNote | undefined;