@jjlmoya/utils-science 1.31.0 → 1.33.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.
- package/package.json +1 -1
- package/src/category/index.ts +3 -1
- package/src/entries.ts +5 -1
- package/src/index.ts +2 -0
- package/src/tests/locale_completeness.test.ts +2 -2
- package/src/tests/tool_validation.test.ts +2 -2
- package/src/tool/mandelbrot-fractal/bibliography.astro +14 -0
- package/src/tool/mandelbrot-fractal/bibliography.ts +16 -0
- package/src/tool/mandelbrot-fractal/component.astro +242 -0
- package/src/tool/mandelbrot-fractal/entry.ts +26 -0
- package/src/tool/mandelbrot-fractal/i18n/de.ts +159 -0
- package/src/tool/mandelbrot-fractal/i18n/en.ts +160 -0
- package/src/tool/mandelbrot-fractal/i18n/es.ts +159 -0
- package/src/tool/mandelbrot-fractal/i18n/fr.ts +159 -0
- package/src/tool/mandelbrot-fractal/i18n/id.ts +159 -0
- package/src/tool/mandelbrot-fractal/i18n/it.ts +159 -0
- package/src/tool/mandelbrot-fractal/i18n/ja.ts +159 -0
- package/src/tool/mandelbrot-fractal/i18n/ko.ts +159 -0
- package/src/tool/mandelbrot-fractal/i18n/nl.ts +159 -0
- package/src/tool/mandelbrot-fractal/i18n/pl.ts +159 -0
- package/src/tool/mandelbrot-fractal/i18n/pt.ts +159 -0
- package/src/tool/mandelbrot-fractal/i18n/ru.ts +159 -0
- package/src/tool/mandelbrot-fractal/i18n/sv.ts +159 -0
- package/src/tool/mandelbrot-fractal/i18n/tr.ts +159 -0
- package/src/tool/mandelbrot-fractal/i18n/zh.ts +159 -0
- package/src/tool/mandelbrot-fractal/index.ts +11 -0
- package/src/tool/mandelbrot-fractal/logic/MandelbrotEngine.ts +67 -0
- package/src/tool/mandelbrot-fractal/mandelbrot-fractal-calculator.css +357 -0
- package/src/tool/mandelbrot-fractal/seo.astro +15 -0
- package/src/tool/planet-atmosphere-survival/bibliography.astro +14 -0
- package/src/tool/planet-atmosphere-survival/bibliography.ts +16 -0
- package/src/tool/planet-atmosphere-survival/component.astro +404 -0
- package/src/tool/planet-atmosphere-survival/entry.ts +26 -0
- package/src/tool/planet-atmosphere-survival/i18n/de.ts +255 -0
- package/src/tool/planet-atmosphere-survival/i18n/en.ts +255 -0
- package/src/tool/planet-atmosphere-survival/i18n/es.ts +255 -0
- package/src/tool/planet-atmosphere-survival/i18n/fr.ts +255 -0
- package/src/tool/planet-atmosphere-survival/i18n/id.ts +255 -0
- package/src/tool/planet-atmosphere-survival/i18n/it.ts +255 -0
- package/src/tool/planet-atmosphere-survival/i18n/ja.ts +255 -0
- package/src/tool/planet-atmosphere-survival/i18n/ko.ts +255 -0
- package/src/tool/planet-atmosphere-survival/i18n/nl.ts +255 -0
- package/src/tool/planet-atmosphere-survival/i18n/pl.ts +255 -0
- package/src/tool/planet-atmosphere-survival/i18n/pt.ts +255 -0
- package/src/tool/planet-atmosphere-survival/i18n/ru.ts +255 -0
- package/src/tool/planet-atmosphere-survival/i18n/sv.ts +255 -0
- package/src/tool/planet-atmosphere-survival/i18n/tr.ts +255 -0
- package/src/tool/planet-atmosphere-survival/i18n/zh.ts +255 -0
- package/src/tool/planet-atmosphere-survival/index.ts +11 -0
- package/src/tool/planet-atmosphere-survival/logic.ts +201 -0
- package/src/tool/planet-atmosphere-survival/planet-atmosphere-survival-calculator.css +494 -0
- package/src/tool/planet-atmosphere-survival/seo.astro +15 -0
- package/src/tools.ts +4 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { bibliography } from '../bibliography';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
|
|
4
|
+
const slug = 'planet-atmosphere-survival-calculator';
|
|
5
|
+
const title = '行星大气生存计算器';
|
|
6
|
+
const description = '在火星、金星、土卫六、木星或珠穆朗玛峰上没有宇航服你能生存多久?这个交互式计算器根据压力、温度、氧气、二氧化碳、毒性和风危险来估算未受保护的人类生存时间。';
|
|
7
|
+
|
|
8
|
+
const howTo = [
|
|
9
|
+
{
|
|
10
|
+
name: '选择目的地以加载真实大气数据',
|
|
11
|
+
text: '选择火星、金星、土卫六、木星或珠穆朗玛峰山顶预设,即可立即设置该环境的逼真压力、温度、气体混合和风值。',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: '调整条件以探索临界点',
|
|
15
|
+
text: '移动压力、温度、氧气和二氧化碳滑块,观察哪种危险首先变得致命。微小的变化可能会完全改变限制因素。',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: '读取生存计时器和最薄弱的环节',
|
|
19
|
+
text: '计时器显示发生严重生物应激前的估计时间。限制因素标签准确告诉您目前哪种危险是最紧迫的威胁。',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: '在风险图上直观比较危险',
|
|
23
|
+
text: '径向辐条和时间线图表显示压力、热、冷、缺氧、毒性和风如何随时间各自促成了总体风险。',
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const faq = [
|
|
28
|
+
{
|
|
29
|
+
question: '人类在没有宇航服的情况下能在火星上生存吗?',
|
|
30
|
+
answer: '不能。火星气压极低(不到地球的1%),几乎没有可呼吸的氧气,大气主要由二氧化碳组成。如果没有压力和氧气支持,几秒钟内就会失去意识,几分钟内就会受到严重伤害。',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
question: '为什么大气压力对人类生存如此关键?',
|
|
34
|
+
answer: '在Armstrong极限(约6.3 kPa)以下,水可以在体温下沸腾。即使空气是100%氧气,低压也会阻止氧气进入血液。这就是压力是最快致命危险之一的原因。',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
question: '哪颗行星拥有最可生存的大气?',
|
|
38
|
+
answer: '在太阳系目的地中,高海拔地球(珠穆朗玛峰)是最可生存的,但未经适应仍然危险。土卫六是其他选项中最不敌意的,因为它的压力是可管理的,但缺乏氧气且极冷。除地球外,没有行星或卫星拥有可呼吸的大气。',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
question: '金星更糟糕是因为热还是压力?',
|
|
42
|
+
answer: '两者在表面都很极端。金星具有毁灭性的压力(地球的92倍)和比厨房烤箱更热的表面温度。这些危险共同作用,因此工具将两者标记为即时的主导威胁。',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
question: '为什么土卫六的生存估计时间比火星长?',
|
|
46
|
+
answer: '土卫六有浓厚的大气,因此压力本身并不是即时问题。生存时间受到极端寒冷(约-180°C)和完全缺乏氧气的限制。火星在压力上失败,土卫六在温度上失败。',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
question: '在太空中没有宇航服的主要死因是什么?',
|
|
50
|
+
answer: '最快的杀手是真空暴露(压力损失在几秒钟内导致ebullism和缺氧),其次是极端温度、有毒气体成分和风驱动的热量损失。计算器跟踪所有六种危险类别。',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
question: '这个工具适合太空任务规划吗?',
|
|
54
|
+
answer: '不适合。这是一个使用简化生物学阈值的教育模型。真正的减压、缺氧、有毒气体、热损伤和任务风险分析需要专家医学和工程评估以确保安全。',
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
export const content: ToolLocaleContent = {
|
|
59
|
+
slug,
|
|
60
|
+
title,
|
|
61
|
+
description,
|
|
62
|
+
ui: {
|
|
63
|
+
riskMap: '大气风险图',
|
|
64
|
+
timeline: '生物风险时间线',
|
|
65
|
+
controls: '大气控制',
|
|
66
|
+
destination: '目的地',
|
|
67
|
+
pressure: '压力',
|
|
68
|
+
temperature: '温度',
|
|
69
|
+
oxygen: '氧气',
|
|
70
|
+
co2: '二氧化碳',
|
|
71
|
+
limitingFactor: '限制因素',
|
|
72
|
+
verdict: '判定',
|
|
73
|
+
exposureSummary: '暴露摘要',
|
|
74
|
+
atmosphericModel: '大气暴露模型',
|
|
75
|
+
survivalEnvelope: '生存包络',
|
|
76
|
+
survival: '生存',
|
|
77
|
+
mode: '模式',
|
|
78
|
+
metric: '公制',
|
|
79
|
+
imperial: '英制',
|
|
80
|
+
unitSystem: '单位制',
|
|
81
|
+
vitalStress: '生命压力',
|
|
82
|
+
timeLabel: '时间',
|
|
83
|
+
estimatedSurvival: '估计生存时间',
|
|
84
|
+
hazardPressure: '气压',
|
|
85
|
+
hazardTemperature: '温度',
|
|
86
|
+
hazardOxygen: '氧气',
|
|
87
|
+
hazardToxicity: '毒性',
|
|
88
|
+
hazardWind: '风',
|
|
89
|
+
presetMars: '火星',
|
|
90
|
+
presetVenus: '金星表面',
|
|
91
|
+
presetTitan: '土卫六',
|
|
92
|
+
presetJupiter: '木星云层',
|
|
93
|
+
presetEverest: '地球·珠峰顶',
|
|
94
|
+
noteMars: '近乎真空、极端寒冷,几乎没有可呼吸的氧气。',
|
|
95
|
+
noteVenus: '毁灭性的压力和熔炉般的高温立即占主导地位。',
|
|
96
|
+
noteTitan: '稠密的氮气大气,但致命寒冷且无氧气。',
|
|
97
|
+
noteJupiter: '富含氢的大气、严寒和暴风。',
|
|
98
|
+
noteEverest: '训练有素的登山者可生存,但缺氧和寒冷是严重的。',
|
|
99
|
+
verdictSeconds: '秒',
|
|
100
|
+
verdictMinutes: '分钟',
|
|
101
|
+
verdictHours: '小时',
|
|
102
|
+
verdictExtended: '长期暴露风险',
|
|
103
|
+
},
|
|
104
|
+
seo: [
|
|
105
|
+
{
|
|
106
|
+
type: 'title',
|
|
107
|
+
text: '行星大气生存计算器: 在火星、金星、土卫六或木星上没有宇航服你能生存多久?',
|
|
108
|
+
level: 2,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: 'paragraph',
|
|
112
|
+
html: '如果你突然暴露在另一个行星的大气中没有宇航服,你能生存多久?这个计算器通过模拟六种危险:总压力、氧气可用性、温度、二氧化碳浓度、有毒化学物质和风应力,估算在火星、金星、土卫六、木星和珠穆朗玛峰上未受保护的人类生存时间。它回答了太空爱好者和学生最常问的问题:哪颗行星杀死你最快,哪种危险是真正的威胁,以及你需要什么才能生存。',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
type: 'paragraph',
|
|
116
|
+
html: '结果是教育性估计,而非任务规划数字。它旨在帮助比较为什么不同的世界以截然不同的方式危险。火星在几秒钟内因压力和缺氧而失败。金星将毁灭性压力与烤箱高温结合在一起。土卫六极冷且没有氧气。气态巨行星的云层增加了有毒成分和超音速风。每个环境都教会我们关于地球独特宜居性的不同内容。',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
type: 'title',
|
|
120
|
+
text: '哪颗行星拥有最可生存的大气?',
|
|
121
|
+
level: 3,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
type: 'paragraph',
|
|
125
|
+
html: '在这个计算器的目的地中,地球的高海拔环境(如珠穆朗玛峰顶部)是最可生存的,但未经准备仍然危险。在其他行星中,土卫六拥有最宽容的压力,但在温度和氧气上失败。除地球外,没有其他目的地目前提供可呼吸的大气。这个计算器帮助您准确看到每个世界失败的原因以及哪种危险首先越过临界阈值。',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
type: 'title',
|
|
129
|
+
text: '每种危险如何影响身体',
|
|
130
|
+
level: 3,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
type: 'list',
|
|
134
|
+
items: [
|
|
135
|
+
'<strong>压力(低):</strong> 低于6.3 kPa时体液可能沸腾(ebullism)。即使高于该值,低压也会阻止氧气吸收。这是近真空环境中最快的杀手。',
|
|
136
|
+
'<strong>压力(高):</strong> 极端压力压缩呼吸气体,增加氮麻醉风险,并可能机械损伤肺部和鼻窦。',
|
|
137
|
+
'<strong>氧分压:</strong> 可呼吸的氧气取决于气体百分比和总压力。稀薄的大气可能有21%的氧气,但仍然会导致缺氧。',
|
|
138
|
+
'<strong>温度(热):</strong> 大约60°C以上时,蛋白质变性和器官衰竭迅速开始。金星表面温度超过460°C。',
|
|
139
|
+
'<strong>温度(冷):</strong> 冰点以下时,冻伤和体温过低开始发生。在土卫六的-180°C等极低温度下,组织冻结几乎是瞬间的。',
|
|
140
|
+
'<strong>二氧化碳毒性:</strong> CO2超过约5%会导致头晕、头痛和意识丧失。许多行星大气主要由CO2组成。',
|
|
141
|
+
'<strong>有毒化学物质:</strong> 硫化合物、氨、甲烷和氢气可能具有腐蚀性、窒息性或化学危险性。',
|
|
142
|
+
'<strong>风:</strong> 强风通过对流加速热量损失,造成风寒,吹走碎片,并可能使人身体不稳。',
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
type: 'title',
|
|
147
|
+
text: '火星: 为什么低压先于其他因素致死',
|
|
148
|
+
level: 3,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
type: 'paragraph',
|
|
152
|
+
html: '火星的表面压力约为0.6 kPa,远低于水可在体温下沸腾的Armstrong极限6.3 kPa。未受保护的暴露会在15秒内导致ebullism、快速缺氧和意识丧失。即使考虑寒冷(平均-60°C)和富含二氧化碳的大气,压力和缺氧主导了时间线。功能性的压力服和氧气供应是在火星上生存的绝对最低要求。',
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
type: 'title',
|
|
156
|
+
text: '金星: 极端压力和热量共同作用',
|
|
157
|
+
level: 3,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
type: 'paragraph',
|
|
161
|
+
html: '金星表面有92个地球大气压的压力(约9.3 MPa,相当于水下900米)和462°C的表面温度。大气是96%的二氧化碳,含有硫酸云。这些危险同时而非顺序地作用:压力压碎,热烘烤,CO2毒害。在这个计算器中,金星是多个危险几乎同时越过致命阈值的唯一目的地。',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
type: 'title',
|
|
165
|
+
text: '土卫六: 地球之外太阳系中最友好的压力',
|
|
166
|
+
level: 3,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
type: 'paragraph',
|
|
170
|
+
html: '土星的卫星土卫六因其表面压力(约147 kPa,地球的1.45倍)实际上处于人类可以忍受的范围内而与众不同。仅就该变量而言,不需要压力服。然而,土卫六基本没有氧气,表面温度为-179°C,大气为甲烷-氮气。计算器显示压力是可管理的,但温度和缺氧立即占据主导。土卫六提醒我们,生存取决于整个危险概况,而不仅仅是单一测量值。',
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
type: 'title',
|
|
174
|
+
text: '如何解读生存计时器和风险图',
|
|
175
|
+
level: 3,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
type: 'paragraph',
|
|
179
|
+
html: '生存计时器估计未受保护的人发生严重生物应激之前的间隔。限制因素标签识别哪种危险首先越过临界阈值。径向危险辐条显示六种跟踪危险中每一种的相对严重程度,时间线图表显示在暴露窗口内组合风险如何累积。这些视觉工具帮助您一眼看出为什么特定环境是危险的,以及哪种保护系统最重要。',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
type: 'table',
|
|
183
|
+
headers: ['目的地', '致命危险', '最快威胁', '宇航服必须解决的问题'],
|
|
184
|
+
rows: [
|
|
185
|
+
['火星', '近真空、缺氧、寒冷、CO2', '压力 < Armstrong极限', '压力服、氧气、热绝缘'],
|
|
186
|
+
['金星表面', '毁灭性压力、462°C高温、CO2、硫酸', '压力和热同时', '强力冷却、耐压壳体、呼吸装置'],
|
|
187
|
+
['土卫六', '无氧气、-179°C寒冷、甲烷', '温度和缺氧', '氧气供应、极端热保护'],
|
|
188
|
+
['木星云层', '无氧气、富氢、寒冷、强风', '缺氧和缺乏可呼吸气体', '密封呼吸系统、温度控制'],
|
|
189
|
+
['珠穆朗玛峰山顶', '缺氧、寒冷、风', '氧分压过低', '氧气面罩、防寒装备、适应'],
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
type: 'title',
|
|
194
|
+
text: '没有宇航服生存需要什么?',
|
|
195
|
+
level: 3,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
type: 'paragraph',
|
|
199
|
+
html: '实际上,除地球外没有已知的太阳系天体允许未受保护的人类生存超过几分钟,而且大多数在几秒钟内致命。这个计算器的价值不在于寻找安全的行星,而在于理解每个环境敌对的特定原因。这些知识指导着行星科学教育、太空栖息地设计、宇航员训练优先事项以及寻找大气可能真正支持生命的潜在宜居系外行星。',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
type: 'list',
|
|
203
|
+
items: [
|
|
204
|
+
'<strong>用于学习:</strong> 观察改变一个变量,比如将火星压力加倍,如何改变生存窗口。',
|
|
205
|
+
'<strong>用于比较:</strong> 对比土卫六和金星虽然都不可生存,但为什么土卫六提供更多时间。',
|
|
206
|
+
'<strong>用于讨论:</strong> 探索经过地球化改造的大气需要什么才能达到可呼吸的条件。',
|
|
207
|
+
'<strong>不要用于真实决策:</strong> 计算器使用简化阈值。应急计划需要专业航空航天医学。',
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
type: 'title',
|
|
212
|
+
text: '重要限制和教育目的',
|
|
213
|
+
level: 3,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
type: 'paragraph',
|
|
217
|
+
html: '真正的生存取决于个人健康、衣物、劳累程度、湿度、太阳辐射、减压历史、呼吸气体混合物、救援时间以及许多其他变量。行星大气数据也因海拔、季节和测量来源而异。此工具使用简化的生物学阈值和代表性环境数据进行科学教育。它旨在帮助学生、教师、太空爱好者和科学作家理解行星宜居性,而非指导实际的太空操作。',
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
faq,
|
|
221
|
+
bibliography,
|
|
222
|
+
howTo,
|
|
223
|
+
schemas: [
|
|
224
|
+
{
|
|
225
|
+
'@context': 'https://schema.org',
|
|
226
|
+
'@type': 'SoftwareApplication',
|
|
227
|
+
name: title,
|
|
228
|
+
description,
|
|
229
|
+
applicationCategory: 'ScientificApplication',
|
|
230
|
+
operatingSystem: 'Any',
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
'@context': 'https://schema.org',
|
|
234
|
+
'@type': 'FAQPage',
|
|
235
|
+
mainEntity: faq.map((item) => ({
|
|
236
|
+
'@type': 'Question',
|
|
237
|
+
name: item.question,
|
|
238
|
+
acceptedAnswer: {
|
|
239
|
+
'@type': 'Answer',
|
|
240
|
+
text: item.answer,
|
|
241
|
+
},
|
|
242
|
+
})),
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
'@context': 'https://schema.org',
|
|
246
|
+
'@type': 'HowTo',
|
|
247
|
+
name: title,
|
|
248
|
+
step: howTo.map((step) => ({
|
|
249
|
+
'@type': 'HowToStep',
|
|
250
|
+
name: step.name,
|
|
251
|
+
text: step.text,
|
|
252
|
+
})),
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { planetAtmosphereSurvival } from './entry';
|
|
2
|
+
import type { ToolDefinition } from '../../types';
|
|
3
|
+
|
|
4
|
+
export * from './entry';
|
|
5
|
+
|
|
6
|
+
export const PLANET_ATMOSPHERE_SURVIVAL_TOOL: ToolDefinition = {
|
|
7
|
+
entry: planetAtmosphereSurvival,
|
|
8
|
+
Component: () => import('./component.astro'),
|
|
9
|
+
SEOComponent: () => import('./seo.astro'),
|
|
10
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
11
|
+
};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
export type AtmosphereHazard = 'pressure' | 'temperature' | 'oxygen' | 'toxicity' | 'wind';
|
|
2
|
+
|
|
3
|
+
export interface PlanetAtmospherePreset {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
pressureKpa: number;
|
|
7
|
+
temperatureC: number;
|
|
8
|
+
oxygenFraction: number;
|
|
9
|
+
co2Fraction: number;
|
|
10
|
+
toxicIndex: number;
|
|
11
|
+
windMps: number;
|
|
12
|
+
note: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SurvivalInput {
|
|
16
|
+
pressureKpa: number;
|
|
17
|
+
temperatureC: number;
|
|
18
|
+
oxygenFraction: number;
|
|
19
|
+
co2Fraction: number;
|
|
20
|
+
toxicIndex: number;
|
|
21
|
+
windMps: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface HazardScore {
|
|
25
|
+
hazard: AtmosphereHazard;
|
|
26
|
+
score: number;
|
|
27
|
+
timeToSevereSeconds: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SurvivalTimelinePoint {
|
|
31
|
+
second: number;
|
|
32
|
+
pressure: number;
|
|
33
|
+
temperature: number;
|
|
34
|
+
oxygen: number;
|
|
35
|
+
toxicity: number;
|
|
36
|
+
wind: number;
|
|
37
|
+
total: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SurvivalResult {
|
|
41
|
+
survivalSeconds: number;
|
|
42
|
+
limitingHazard: AtmosphereHazard;
|
|
43
|
+
hazards: HazardScore[];
|
|
44
|
+
timeline: SurvivalTimelinePoint[];
|
|
45
|
+
verdict: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const ATMOSPHERE_PRESETS: PlanetAtmospherePreset[] = [
|
|
49
|
+
{
|
|
50
|
+
id: 'mars',
|
|
51
|
+
name: 'Mars',
|
|
52
|
+
pressureKpa: 0.64,
|
|
53
|
+
temperatureC: -63,
|
|
54
|
+
oxygenFraction: 0.0013,
|
|
55
|
+
co2Fraction: 0.953,
|
|
56
|
+
toxicIndex: 0.28,
|
|
57
|
+
windMps: 18,
|
|
58
|
+
note: 'Near vacuum, extreme cold, and almost no breathable oxygen.',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'venus',
|
|
62
|
+
name: 'Venus surface',
|
|
63
|
+
pressureKpa: 9200,
|
|
64
|
+
temperatureC: 464,
|
|
65
|
+
oxygenFraction: 0,
|
|
66
|
+
co2Fraction: 0.965,
|
|
67
|
+
toxicIndex: 0.95,
|
|
68
|
+
windMps: 1,
|
|
69
|
+
note: 'Crushing pressure and oven-like heat dominate immediately.',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'titan',
|
|
73
|
+
name: 'Titan',
|
|
74
|
+
pressureKpa: 146.7,
|
|
75
|
+
temperatureC: -179,
|
|
76
|
+
oxygenFraction: 0,
|
|
77
|
+
co2Fraction: 0,
|
|
78
|
+
toxicIndex: 0.18,
|
|
79
|
+
windMps: 2,
|
|
80
|
+
note: 'Dense nitrogen air but lethal cold and no oxygen.',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 'jupiter',
|
|
84
|
+
name: 'Jupiter cloud deck',
|
|
85
|
+
pressureKpa: 250,
|
|
86
|
+
temperatureC: -110,
|
|
87
|
+
oxygenFraction: 0,
|
|
88
|
+
co2Fraction: 0,
|
|
89
|
+
toxicIndex: 0.72,
|
|
90
|
+
windMps: 120,
|
|
91
|
+
note: 'Hydrogen-rich atmosphere, severe cold, and violent winds.',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'earth-everest',
|
|
95
|
+
name: 'Earth, Everest summit',
|
|
96
|
+
pressureKpa: 33.7,
|
|
97
|
+
temperatureC: -25,
|
|
98
|
+
oxygenFraction: 0.209,
|
|
99
|
+
co2Fraction: 0.0004,
|
|
100
|
+
toxicIndex: 0,
|
|
101
|
+
windMps: 12,
|
|
102
|
+
note: 'Survivable for trained climbers, but hypoxia and cold are serious.',
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
function clamp01(value: number): number {
|
|
107
|
+
return Math.min(1, Math.max(0, value));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function interpolateRisk(time: number, severeTime: number): number {
|
|
111
|
+
if (severeTime <= 0) return 1;
|
|
112
|
+
return clamp01(Math.pow(time / severeTime, 1.25));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function calcPressureScore(kpa: number): number {
|
|
116
|
+
if (kpa < 6.3) return 1;
|
|
117
|
+
if (kpa > 250) return clamp01((kpa - 250) / 700);
|
|
118
|
+
if (kpa < 55) return clamp01((55 - kpa) / 48.7);
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function calcPressureTime(kpa: number, score: number): number {
|
|
123
|
+
if (kpa < 6.3) return 12;
|
|
124
|
+
if (kpa > 250) return 45 / Math.max(0.15, score);
|
|
125
|
+
return 1200 / Math.max(0.15, score);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function calcOxygenScore(pp: number): number {
|
|
129
|
+
if (pp < 16) return clamp01((16 - pp) / 16);
|
|
130
|
+
if (pp > 50) return clamp01((pp - 50) / 110);
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function calcOxygenTime(pp: number, score: number): number {
|
|
135
|
+
if (pp < 6) return 18;
|
|
136
|
+
if (pp < 16) return 240 / Math.max(0.15, score);
|
|
137
|
+
return 1200 / Math.max(0.15, score);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function calcVerdict(seconds: number): string {
|
|
141
|
+
if (seconds < 60) return 'seconds';
|
|
142
|
+
if (seconds < 1800) return 'minutes';
|
|
143
|
+
if (seconds < 21600) return 'hours';
|
|
144
|
+
return 'extended';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function scoreAtmosphere(input: SurvivalInput): HazardScore[] {
|
|
148
|
+
const oxygenPartialKpa = input.pressureKpa * input.oxygenFraction;
|
|
149
|
+
const pressureScore = calcPressureScore(input.pressureKpa);
|
|
150
|
+
const pressureTime = calcPressureTime(input.pressureKpa, pressureScore);
|
|
151
|
+
const coldScore = input.temperatureC < 0 ? clamp01(Math.abs(input.temperatureC) / 95) : 0;
|
|
152
|
+
const heatScore = input.temperatureC > 40 ? clamp01((input.temperatureC - 40) / 140) : 0;
|
|
153
|
+
const temperatureScore = Math.max(coldScore, heatScore);
|
|
154
|
+
const temperatureTime = heatScore > coldScore ? 35 / Math.max(0.12, heatScore) : 900 / Math.max(0.12, coldScore);
|
|
155
|
+
const oxygenScore = calcOxygenScore(oxygenPartialKpa);
|
|
156
|
+
const oxygenTime = calcOxygenTime(oxygenPartialKpa, oxygenScore);
|
|
157
|
+
const co2Score = input.co2Fraction > 0.08 ? clamp01((input.co2Fraction - 0.08) / 0.18) : 0;
|
|
158
|
+
const toxicityScore = clamp01(Math.max(co2Score, input.toxicIndex));
|
|
159
|
+
const toxicityTime = 180 / Math.max(0.12, toxicityScore);
|
|
160
|
+
const windScore = clamp01((input.windMps - 20) / 80);
|
|
161
|
+
const windTime = 300 / Math.max(0.12, windScore);
|
|
162
|
+
|
|
163
|
+
return [
|
|
164
|
+
{ hazard: 'pressure', score: pressureScore, timeToSevereSeconds: pressureTime },
|
|
165
|
+
{ hazard: 'temperature', score: temperatureScore, timeToSevereSeconds: temperatureTime },
|
|
166
|
+
{ hazard: 'oxygen', score: oxygenScore, timeToSevereSeconds: oxygenTime },
|
|
167
|
+
{ hazard: 'toxicity', score: toxicityScore, timeToSevereSeconds: toxicityTime },
|
|
168
|
+
{ hazard: 'wind', score: windScore, timeToSevereSeconds: windTime },
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function estimateSurvival(input: SurvivalInput): SurvivalResult {
|
|
173
|
+
const hazards = scoreAtmosphere(input);
|
|
174
|
+
const activeHazards = hazards.filter((hazard) => hazard.score > 0.01);
|
|
175
|
+
const limiting = activeHazards.reduce((fastest, hazard) => hazard.timeToSevereSeconds < fastest.timeToSevereSeconds ? hazard : fastest, activeHazards[0] ?? hazards[0]);
|
|
176
|
+
const totalStress = hazards.reduce((sum, hazard) => sum + hazard.score, 0);
|
|
177
|
+
const survivalSeconds = activeHazards.length === 0 ? 86400 : Math.max(8, Math.min(86400, limiting.timeToSevereSeconds / Math.max(0.85, totalStress * 0.55)));
|
|
178
|
+
const timelineEnd = Math.min(Math.max(survivalSeconds * 1.2, 60), 3600);
|
|
179
|
+
const timeline = Array.from({ length: 25 }, (_, index) => {
|
|
180
|
+
const second = (timelineEnd / 24) * index;
|
|
181
|
+
const point = {
|
|
182
|
+
second,
|
|
183
|
+
pressure: interpolateRisk(second, hazards[0].timeToSevereSeconds),
|
|
184
|
+
temperature: interpolateRisk(second, hazards[1].timeToSevereSeconds),
|
|
185
|
+
oxygen: interpolateRisk(second, hazards[2].timeToSevereSeconds),
|
|
186
|
+
toxicity: interpolateRisk(second, hazards[3].timeToSevereSeconds),
|
|
187
|
+
wind: interpolateRisk(second, hazards[4].timeToSevereSeconds),
|
|
188
|
+
total: 0,
|
|
189
|
+
};
|
|
190
|
+
point.total = clamp01(Math.max(point.pressure, point.temperature, point.oxygen, point.toxicity, point.wind) * 0.72 + totalStress * 0.08);
|
|
191
|
+
return point;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
survivalSeconds,
|
|
196
|
+
limitingHazard: limiting.hazard,
|
|
197
|
+
hazards,
|
|
198
|
+
timeline,
|
|
199
|
+
verdict: calcVerdict(survivalSeconds),
|
|
200
|
+
};
|
|
201
|
+
}
|