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,226 @@
1
+ /**
2
+ * 公式库第八批:**普适版经验式**(把烘焙的常数拆出来,让它们随世界变)。
3
+ *
4
+ * ## 这一批解决什么问题
5
+ *
6
+ * 前七批里有一批经验公式把常数**烘焙进了系数**:
7
+ *
8
+ * 船舶极限航速 = 1.34 × sqrt(L) ← 1.34 里藏着 g = 9.81
9
+ * 宜居带内边界 = sqrt(L / 1.1) ← 1.1 是「地球温室条件下的辐照阈值」
10
+ * 硬度估强度 = 3 × H × 9.80665e6 ← 9.8 是地球重力换算
11
+ *
12
+ * 它们在**地球**上对,但作者写 1.5 倍重力的星球时照抄就会错,而且错得隐蔽
13
+ * (数字看着正常,只是用了地球的常数)。
14
+ *
15
+ * 本批把这些式子重写成**显式暴露物理量**的形式:
16
+ *
17
+ * hull_speed_generic = Fr × sqrt(g × L) × 1.944 ← g 由 world 给出
18
+ * habitable_inner_generic = sqrt(L / (flux × S0)) ← S0 由 L☉/AU 推出
19
+ * hardness_yield_generic = 3 × H × g / 1e-6 × 1e6 ← g 可替换
20
+ *
21
+ * ## 与旧版并存,不是替换
22
+ *
23
+ * 旧的 `hull_speed`(1.34)**保留**:作者要写地球上的船时它更快更省事,
24
+ * 而且那是船舶工程界的标准式。新版只是给「非地球重力」的场景一个正确入口。
25
+ * 两版的 desc 都写明适用域,让作者知道该用哪个。
26
+ *
27
+ * ## 单位约定
28
+ *
29
+ * 同前几批:**SI 基本单位**,但保留各村行业惯用单位(节、kgf/mm²)并在参数说明里写明。
30
+ *
31
+ * @module dsh-math-calculator/formula-library-generic
32
+ */
33
+ const p = (value, desc) => ({ value, desc });
34
+ const req = (desc) => ({ value: null, desc });
35
+ /**
36
+ * 第八批公式:把烘焙常数拆出来的普适形式。
37
+ */
38
+ export const GENERIC_FORMULAS = [
39
+ // ══════════════════════════════════════════════════════════════════
40
+ // 船舶与流体:把 g 从系数里拆出来
41
+ // ══════════════════════════════════════════════════════════════════
42
+ {
43
+ id: 'hull_speed_generic',
44
+ cat: 'perception/travel',
45
+ name: '船舶极限航速(普适版,可换重力)',
46
+ expr: 'froude * sqrt(g * length) * 1.944',
47
+ params: {
48
+ froude: p(0.4, '兴波极限 Froude 数(无量纲,约 0.4)'),
49
+ g: p(9.80665, '地表重力 m/s²(由 world 给出;地球 9.81)'),
50
+ length: req('水线长 m'),
51
+ },
52
+ unit: 'kn(节)',
53
+ desc: '排水型船的兴波极限速度。**这是 hull_speed 的普适版**:那个版本的 1.34 ' +
54
+ '把 g=9.81 烘焙进了系数,只在 1 g 世界成立。1.5 g 世界里同一艘 100 m 的船' +
55
+ '极限航速是 29.8 节而非 24.4 节(快 22%)。Froude ≈ 0.4 是经验值,' +
56
+ '再快就得上滑行艇或水翼。',
57
+ keys: ['hull speed', '航速', 'Froude', '兴波', '船速'],
58
+ frequency: 'high',
59
+ layer: 'derived',
60
+ domains: ['physics', 'engineering'],
61
+ },
62
+ {
63
+ id: 'froude_number',
64
+ cat: 'physics/fluid',
65
+ name: 'Froude 数',
66
+ expr: 'v / sqrt(g * length)',
67
+ params: {
68
+ v: req('特征速度 m/s'),
69
+ g: p(9.80665, '重力 m/s²'),
70
+ length: req('特征长度 m'),
71
+ },
72
+ unit: '无量纲',
73
+ desc: '惯性力与重力之比。船舶 Fr < 0.4 为排水航行,> 0.4 进入过渡态,' +
74
+ '> 1 才可能滑行(需要极大功率)。这是判断「这艘船能不能更快」的核心无量纲数。',
75
+ keys: ['froude', '弗劳德数', '兴波阻力'],
76
+ frequency: 'medium',
77
+ layer: 'fundamental',
78
+ domains: ['physics', 'engineering'],
79
+ },
80
+ // ══════════════════════════════════════════════════════════════════
81
+ // 宜居带:把太阳常数显式化
82
+ // ══════════════════════════════════════════════════════════════════
83
+ {
84
+ id: 'habitable_zone_generic',
85
+ cat: 'physics/astronomy',
86
+ name: '宜居带边界(普适版,显式用辐照阈值)',
87
+ expr: 'sqrt(luminosity / flux_ratio)',
88
+ params: {
89
+ luminosity: p(1, '恒星光度(太阳 = 1)'),
90
+ flux_ratio: p(1.1, '内边界处的辐照阈值(地球温室条件下约 1.1 倍地球辐照)'),
91
+ },
92
+ unit: 'AU',
93
+ desc: 'innerAU = sqrt(L / f),其中 f 是「该边界处的辐照是地球的几倍」。' +
94
+ '原式 `sqrt(L/1.1)` 的 1.1 就是 f —— 现在它是可替换参数。' +
95
+ '强温室行星(如金星式)的 f 可以远小于 1.1;真空岩石行星则要大得多。' +
96
+ '**solar_flux_at_1au** 用来把 W/m² 换算成 f。',
97
+ keys: ['habitable zone', '宜居带', '辐照阈值', 'HZ'],
98
+ frequency: 'high',
99
+ layer: 'derived',
100
+ domains: ['astronomy'],
101
+ },
102
+ {
103
+ id: 'solar_flux_at_1au',
104
+ cat: 'physics/astronomy',
105
+ name: '太阳常数(由光度与距离推出)',
106
+ expr: 'luminosity * 1361',
107
+ params: {
108
+ luminosity: p(1, '恒星光度(太阳 = 1)'),
109
+ },
110
+ unit: 'W/m²',
111
+ desc: '太阳常数 1361 W/m² 是「1 L☉ 在 1 AU 处的辐照」——可由 ' +
112
+ 'L☉/(4π·AU²) 严格算出(得 1362 W/m²,观测值 1361,差 0.07%)。' +
113
+ '换成任意距离用 inverse_square_flux。',
114
+ keys: ['solar constant', '太阳常数', '辐照'],
115
+ frequency: 'medium',
116
+ layer: 'derived',
117
+ domains: ['astronomy'],
118
+ },
119
+ {
120
+ id: 'inverse_square_flux',
121
+ cat: 'physics/astronomy',
122
+ name: '辐照随距离的平方反比',
123
+ expr: 'flux0 * (distance0 / distance)^2',
124
+ params: {
125
+ flux0: req('参考点辐照 W/m²'),
126
+ distance0: req('参考距离(可与 distance 同单位)'),
127
+ distance: req('目标距离'),
128
+ },
129
+ unit: 'W/m²',
130
+ desc: '同一颗恒星在不同距离的辐照。这是「为什么热木星会被烧」的第一性依据,' +
131
+ '也是宜居带宽度 ∝ √L 的来源(辐照 ∝ 1/r²,所以要维持同样辐照,r ∝ √L)。',
132
+ keys: ['inverse square', '平方反比', '辐照衰减'],
133
+ frequency: 'high',
134
+ layer: 'fundamental',
135
+ domains: ['physics', 'astronomy'],
136
+ },
137
+ // ══════════════════════════════════════════════════════════════════
138
+ // 材料:把重力换算拆出来
139
+ // ══════════════════════════════════════════════════════════════════
140
+ {
141
+ id: 'hardness_yield_generic',
142
+ cat: 'materials/structures',
143
+ name: '硬度估屈服强度(普适版,可换重力)',
144
+ expr: 'k * hardness * g',
145
+ params: {
146
+ k: p(3, '硬度-强度比例系数(经验,约 3)'),
147
+ hardness: req('布氏/维氏硬度(kgf/mm²)'),
148
+ g: p(9.80665, '地表重力 m/s²(地球 9.81)'),
149
+ },
150
+ unit: 'MPa',
151
+ desc: 'σy ≈ k·HB(比例 π 是无量纲经验),单位换算里含 g。' +
152
+ '原式的 9.80665e6 就是「kgf/mm² → Pa」的换算,含地球重力。' +
153
+ '换重力时这 9.8 要跟着变(1.5 g 世界同样硬度值对应 1.5 倍 Pa)。' +
154
+ 'σy≈3HB 本身误差可达 30%,只作量级估算。',
155
+ keys: ['hardness', '屈服强度', '硬度换算'],
156
+ frequency: 'medium',
157
+ layer: 'phenomenological',
158
+ domains: ['materials'],
159
+ },
160
+ // ══════════════════════════════════════════════════════════════════
161
+ // 代谢与散热:幂律是普适的,系数不是
162
+ // ══════════════════════════════════════════════════════════════════
163
+ {
164
+ id: 'metabolic_generic',
165
+ cat: 'biology/physiology',
166
+ name: '代谢率(普适幂律,系数可换)',
167
+ expr: 'k * mass^exponent',
168
+ params: {
169
+ k: p(70, '代谢系数,单位 kcal/day·kg^0.75(地球哺乳类约 70)'),
170
+ mass: req('体重 kg'),
171
+ exponent: p(0.75, '标度指数(分形输运网络 → 0.75)'),
172
+ },
173
+ unit: 'kcal/day',
174
+ desc: '克莱伯定律的显式形式 BMR = k·M^0.75。**注意 k 的量纲含 kg^0.75**:' +
175
+ 'k=70 时,70 kg 的人得 1694 kcal/day(约 70×70^0.75),不是 70 kcal —— ' +
176
+ '70 这个数常被误读成「人的代谢率」,其实是「1 kg 基准下的系数」。' +
177
+ '**指数 0.75 是无量纲的、跨世界应成立**(分形输运网络的最优标度);' +
178
+ '系数 k 是地球生物标定,会因生物化学与体温不同而变。' +
179
+ '异星生物改 k 即可,不必改指数。这比 Mifflin 那种线性拟合式通用得多。',
180
+ keys: ['metabolism', '代谢', 'Kleiber', '异速生长'],
181
+ frequency: 'high',
182
+ layer: 'derived',
183
+ domains: ['biology'],
184
+ },
185
+ {
186
+ id: 'convective_cooling_generic',
187
+ cat: 'biology/physiology',
188
+ name: '对流散热系数(普适版,含大气密度)',
189
+ expr: '10 * sqrt(v) * (density / 1.225)^0.8',
190
+ params: {
191
+ v: p(1, '风速 m/s'),
192
+ density: p(1.225, '大气密度 kg/m³(地球 1.225;0.1 atm 约 0.12)'),
193
+ },
194
+ unit: 'W/(m²·K)',
195
+ desc: '对流换热系数的量级估计。**这是「风寒/酷热感」的物理内核**:' +
196
+ '经验公式(风寒指数)把人体与地球大气烘焙进了系数,换世界就失效;' +
197
+ '而这个幂律形式 h ∝ v^0.5 · ρ^0.8 是流体力学标度,跨世界成立。' +
198
+ '稀薄大气(0.1 atm)里散热系数只有地球的约 15% —— 这就是「真空里不觉得冷」的原因。',
199
+ keys: ['convection', '对流', '散热', '大气密度'],
200
+ frequency: 'medium',
201
+ layer: 'derived',
202
+ domains: ['physics', 'biology'],
203
+ },
204
+ {
205
+ id: 'evaporation_saturation_generic',
206
+ cat: 'physics/thermo',
207
+ name: '饱和蒸气压(Clausius-Clapeyron,可换溶剂)',
208
+ expr: 'p0 * exp(-latent / R * (1 / T - 1 / T0))',
209
+ params: {
210
+ p0: p(101325, '参考温度下的饱和蒸气压 Pa'),
211
+ latent: p(40650, '汽化潜热 J/mol(水 40650;氨约 23400)'),
212
+ R: p(8.31446261815324, '气体常数 J/(mol·K)'),
213
+ T0: p(373.15, '参考温度 K(水的常压沸点)'),
214
+ T: req('温度 K'),
215
+ },
216
+ unit: 'Pa',
217
+ desc: '饱和蒸气压的普适形式。**这是露点、沸点、蒸发的底层物理**:' +
218
+ 'Magnus 经验式(243.04 那些数)只是把这个式子在水附近拟合的近似;' +
219
+ '而本式换溶剂只需改 latent(氨、甲烷、硫酸都有各自的值)。' +
220
+ '这也是「为什么高海拔沸点低」的第一性解释。',
221
+ keys: ['saturation', '饱和蒸气压', 'Clausius', '沸点', '露点'],
222
+ frequency: 'high',
223
+ layer: 'fundamental',
224
+ domains: ['physics', 'chemistry'],
225
+ },
226
+ ];
package/lib/formulas.js CHANGED
@@ -17,10 +17,11 @@ import { STORY_FORMULAS } from './formula-library-story.js';
17
17
  import { STORY2_FORMULAS } from './formula-library-story2.js';
18
18
  import { POWER_FORMULAS } from './formula-library-power.js';
19
19
  import { BASIC_FORMULAS } from './formula-library-basic.js';
20
+ import { GENERIC_FORMULAS } from './formula-library-generic.js';
20
21
  import { getActiveConstants } from './constants-graph.js';
21
22
  import { loadGlobalFormulas } from './formula-pack.js';
22
23
  /**
23
- * 全部公式 = 主批次 + 扩展 + 感知层 + 故事 + 势力动力学 + 通用计算基础件。
24
+ * 全部公式 = 主批次 + 扩展 + 感知层 + 故事 + 势力动力学 + 通用计算 + 普适经验式。
24
25
  * 分批组织而不是单文件拼接,避免 formula-library.ts 无限膨胀。
25
26
  */
26
27
  export const FORMULAS = [
@@ -31,6 +32,7 @@ export const FORMULAS = [
31
32
  ...STORY2_FORMULAS,
32
33
  ...POWER_FORMULAS,
33
34
  ...BASIC_FORMULAS,
35
+ ...GENERIC_FORMULAS,
34
36
  ];
35
37
  /** id 索引(后批次覆盖同名 id 时以后者为准,便于后续修订)。 */
36
38
  export const FORMULA_INDEX = new Map();
@@ -48,6 +50,8 @@ for (const formula of POWER_FORMULAS)
48
50
  FORMULA_INDEX.set(formula.id, formula);
49
51
  for (const formula of BASIC_FORMULAS)
50
52
  FORMULA_INDEX.set(formula.id, formula);
53
+ for (const formula of GENERIC_FORMULAS)
54
+ FORMULA_INDEX.set(formula.id, formula);
51
55
  /**
52
56
  * 把全局公式包并入内置公式(幂等:已存在的 id 跳过)。
53
57
  *
@@ -291,27 +295,26 @@ function tokenizeQuery(query) {
291
295
  return [...tokens].sort((left, right) => right.length - left.length);
292
296
  }
293
297
  /**
294
- * 查找公式。支持 id 精确匹配、名称子串、关键词、分类、同义词与中文子词。
295
- * 返回按相关度排序的候选。
296
- *
297
- * 排序优先级:整串精确 > id 精确 > 关键词精确 > id 子串 > 名称精确 > 名称子串 > 描述 > 分类。
298
- * 注意:分类命中给的分要低,否则搜「重力」会把整个 physics/gravity
299
- * 分类的公式都挤到最前面,把真正相关的 gravitation 埋掉。
298
+ * 在一组公式里按相关度检索(**检索逻辑的唯一实现**)。
300
299
  *
301
- * ⚠️ 词元匹配的分数按**词长加权**:命中 4 个字比命中 2 个字更相关。
302
- * 否则「粮食产量」会把所有含「量」的公式都召回到同一档。
300
+ * 为什么要单独抽出来:这段逻辑曾经被**复制成两份** ——
301
+ * `formulas.ts` 里的 `findFormulas` 和 `index.ts` 里的 `findInCurrentWorkspace`。
302
+ * 于是修好了一份、另一份还是旧的:`formulas` 操作走 `findInCurrentWorkspace`,
303
+ * 真实工具里查「贷款月供」仍然是 0 条,而单元测试调 `findFormulas` 全部通过。
304
+ * **测试绿了,工具没修**。现在两边都调这个函数,物理上不可能再不一致。
303
305
  *
304
- * @param query - 查询串(关键词 / 同义词 / 中文复合词都行)
305
- * @param limit - 最多返回条数。宽泛查询给大一点,否则会被截断成 10 条
306
+ * @param formulas - 待检索的公式集(内置全量,或「工作区叠加后的当前集」)
307
+ * @param query - 查询串
308
+ * @param limit - 最多返回条数
306
309
  * @returns 按相关度排序的公式
307
310
  */
308
- export function findFormulas(query, limit = 20) {
311
+ export function scoreFormulas(formulas, query, limit = 20) {
309
312
  const text = query.trim().toLowerCase();
310
313
  if (text === '')
311
314
  return [];
312
315
  const tokens = tokenizeQuery(query);
313
316
  const scored = [];
314
- for (const formula of FORMULAS) {
317
+ for (const formula of formulas) {
315
318
  let score = 0;
316
319
  const id = formula.id.toLowerCase();
317
320
  const name = formula.name;
@@ -367,6 +370,61 @@ export function findFormulas(query, limit = 20) {
367
370
  .slice(0, limit)
368
371
  .map((entry) => entry.formula);
369
372
  }
373
+ /**
374
+ * 查找公式(内置全量集)。支持 id 精确、名称子串、关键词、分类、同义词与中文子词。
375
+ *
376
+ * 排序优先级:整串精确 > id 精确 > 关键词精确 > id 子串 > 名称精确 > 名称子串 > 描述 > 分类。
377
+ * 注意:分类命中给的分要低,否则搜「重力」会把整个 physics/gravity
378
+ * 分类的公式都挤到最前面,把真正相关的 gravitation 埋掉。
379
+ *
380
+ * @param query - 查询串(关键词 / 同义词 / 中文复合词都行)
381
+ * @param limit - 最多返回条数
382
+ * @returns 按相关度排序的公式
383
+ */
384
+ export function findFormulas(query, limit = 20) {
385
+ return scoreFormulas(FORMULAS, query, limit);
386
+ }
387
+ /**
388
+ * 严格解析单个公式(不做子词/同义词模糊匹配)。
389
+ *
390
+ * 为什么必须与 `scoreFormulas` 分开:
391
+ * 检索(`formulas` 操作)追求「想得到」,模糊匹配是对的;
392
+ * 求值(`formula` 操作)追求「指哪儿打哪儿」,模糊匹配是灾难 ——
393
+ * 传 `'完全不存在的公式xyz'` 时,子词「公式」会命中别的公式,
394
+ * 于是本该报 `unknown_formula` 的地方变成 `missing_params`,
395
+ * 模型会以为「公式找到了,只是缺参数」,然后去补一堆无关参数。
396
+ *
397
+ * 这里只认四种关系:id 精确、名称精确、id 包含、名称包含。
398
+ *
399
+ * @param formulas - 待解析的公式集
400
+ * @param query - 公式 id 或名称
401
+ * @returns 唯一命中的公式;没有命中返回 `undefined`
402
+ */
403
+ export function resolveFormulaStrict(formulas, query) {
404
+ const raw = query.trim();
405
+ if (raw === '')
406
+ return undefined;
407
+ const lower = raw.toLowerCase();
408
+ // 1. id 精确
409
+ const byId = formulas.find(function (f) { return f.id.toLowerCase() === lower; });
410
+ if (byId)
411
+ return byId;
412
+ // 2. 名称精确
413
+ const byName = formulas.find(function (f) { return f.name === raw; });
414
+ if (byName)
415
+ return byName;
416
+ // 3. id / 名称整串包含('zzz不存在' 不会命中,因为要求整串被包含)
417
+ const partial = formulas.filter(function (f) {
418
+ return f.id.toLowerCase().includes(lower) || f.name.includes(raw);
419
+ });
420
+ if (partial.length === 1)
421
+ return partial[0];
422
+ // 多个候选时优先名字最短的(更贴近用户输入)
423
+ if (partial.length > 1) {
424
+ return [...partial].sort(function (a, b) { return a.name.length - b.name.length; })[0];
425
+ }
426
+ return undefined;
427
+ }
370
428
  // ── 读者视角频次 ──
371
429
  //
372
430
  // 270 条是「检索式广度」,但故事里真正高频的是另一批: