eyeling 1.16.2 → 1.16.4
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/HANDBOOK.md +4 -0
- package/README.md +0 -1
- package/examples/ershov-mixed-computation.n3 +106 -0
- package/examples/output/ershov-mixed-computation.n3 +15 -0
- package/eyeling.js +510 -263
- package/lib/cli.js +22 -12
- package/lib/engine.js +488 -251
- package/package.json +2 -3
- package/arctifacts/README.md +0 -59
- package/arctifacts/ackermann.html +0 -678
- package/arctifacts/auroracare.html +0 -1297
- package/arctifacts/bike-trip.html +0 -752
- package/arctifacts/binomial-theorem.html +0 -631
- package/arctifacts/bmi.html +0 -511
- package/arctifacts/building-performance.html +0 -750
- package/arctifacts/clinical-care.html +0 -726
- package/arctifacts/collatz.html +0 -403
- package/arctifacts/complex.html +0 -321
- package/arctifacts/control-system.html +0 -482
- package/arctifacts/delfour.html +0 -849
- package/arctifacts/earthquake-epicenter.html +0 -982
- package/arctifacts/eco-route.html +0 -662
- package/arctifacts/euclid-infinitude.html +0 -564
- package/arctifacts/euler-identity.html +0 -667
- package/arctifacts/exoplanet-transit.html +0 -1000
- package/arctifacts/faltings-theorem.html +0 -1046
- package/arctifacts/fibonacci.html +0 -299
- package/arctifacts/fundamental-theorem-arithmetic.html +0 -398
- package/arctifacts/godel-numbering.html +0 -743
- package/arctifacts/gps-bike.html +0 -759
- package/arctifacts/gps-clinical-bench.html +0 -792
- package/arctifacts/graph-french.html +0 -449
- package/arctifacts/grass-molecular.html +0 -592
- package/arctifacts/group-theory.html +0 -740
- package/arctifacts/health-info.html +0 -833
- package/arctifacts/kaprekar-constant.html +0 -576
- package/arctifacts/lee.html +0 -805
- package/arctifacts/linked-lists.html +0 -502
- package/arctifacts/lldm.html +0 -612
- package/arctifacts/matrix-multiplication.html +0 -502
- package/arctifacts/matrix.html +0 -651
- package/arctifacts/newton-raphson.html +0 -944
- package/arctifacts/peano-factorial.html +0 -456
- package/arctifacts/pi.html +0 -363
- package/arctifacts/polynomial.html +0 -646
- package/arctifacts/prime.html +0 -366
- package/arctifacts/pythagorean-theorem.html +0 -468
- package/arctifacts/rest-path.html +0 -469
- package/arctifacts/roots-of-unity.html +0 -363
- package/arctifacts/turing.html +0 -409
- package/arctifacts/wind-turbines.html +0 -726
|
@@ -1,592 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
-
<title>Grass seed — molecular germination</title>
|
|
7
|
-
<style>
|
|
8
|
-
:root {
|
|
9
|
-
--fg: #101014;
|
|
10
|
-
--bg: #ffffff;
|
|
11
|
-
--muted: #666;
|
|
12
|
-
--accent: #2563eb;
|
|
13
|
-
--chip: #eef2ff;
|
|
14
|
-
--ok: #16a34a;
|
|
15
|
-
--bad: #dc2626;
|
|
16
|
-
--warn: #ca8a04;
|
|
17
|
-
--w: #2563eb;
|
|
18
|
-
--s: #ea580c;
|
|
19
|
-
--e: #16a34a;
|
|
20
|
-
--g: #7c3aed;
|
|
21
|
-
--aba: #b91c1c;
|
|
22
|
-
--d: #0ea5e9;
|
|
23
|
-
--x: #d97706;
|
|
24
|
-
}
|
|
25
|
-
@media (prefers-color-scheme: dark) {
|
|
26
|
-
:root {
|
|
27
|
-
--fg: #eaeaf0;
|
|
28
|
-
--bg: #0b0b10;
|
|
29
|
-
--muted: #a0a0b0;
|
|
30
|
-
--accent: #60a5fa;
|
|
31
|
-
--chip: #0e1a32;
|
|
32
|
-
--w: #60a5fa;
|
|
33
|
-
--s: #fb923c;
|
|
34
|
-
--e: #34d399;
|
|
35
|
-
--g: #c084fc;
|
|
36
|
-
--aba: #ef4444;
|
|
37
|
-
--d: #67e8f9;
|
|
38
|
-
--x: #fbbf24;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
html,
|
|
42
|
-
body {
|
|
43
|
-
margin: 0;
|
|
44
|
-
padding: 0;
|
|
45
|
-
background: var(--bg);
|
|
46
|
-
color: var(--fg);
|
|
47
|
-
font:
|
|
48
|
-
15px/1.6 ui-sans-serif,
|
|
49
|
-
system-ui,
|
|
50
|
-
-apple-system,
|
|
51
|
-
Segoe UI,
|
|
52
|
-
Roboto,
|
|
53
|
-
Helvetica,
|
|
54
|
-
Arial;
|
|
55
|
-
}
|
|
56
|
-
main {
|
|
57
|
-
max-width: 1100px;
|
|
58
|
-
margin: 0 auto;
|
|
59
|
-
padding: 28px 16px 80px;
|
|
60
|
-
}
|
|
61
|
-
h1 {
|
|
62
|
-
font-size: clamp(1.6rem, 2.6vw + 1rem, 2.2rem);
|
|
63
|
-
margin: 0 0 6px;
|
|
64
|
-
}
|
|
65
|
-
header p {
|
|
66
|
-
margin: 0;
|
|
67
|
-
color: var(--muted);
|
|
68
|
-
}
|
|
69
|
-
section {
|
|
70
|
-
margin: 18px 0 22px;
|
|
71
|
-
padding: 14px 14px 16px;
|
|
72
|
-
border: 1px solid color-mix(in srgb, var(--accent) 18%, transparent);
|
|
73
|
-
border-radius: 14px;
|
|
74
|
-
background: color-mix(in srgb, var(--accent) 4%, transparent);
|
|
75
|
-
}
|
|
76
|
-
section h2 {
|
|
77
|
-
margin: 0 0 8px;
|
|
78
|
-
font-size: 1.15rem;
|
|
79
|
-
}
|
|
80
|
-
.grid {
|
|
81
|
-
display: grid;
|
|
82
|
-
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
83
|
-
gap: 10px;
|
|
84
|
-
}
|
|
85
|
-
.chip {
|
|
86
|
-
display: inline-block;
|
|
87
|
-
padding: 2px 8px;
|
|
88
|
-
border-radius: 999px;
|
|
89
|
-
background: var(--chip);
|
|
90
|
-
font-weight: 600;
|
|
91
|
-
}
|
|
92
|
-
.muted {
|
|
93
|
-
color: var(--muted);
|
|
94
|
-
}
|
|
95
|
-
.row {
|
|
96
|
-
display: flex;
|
|
97
|
-
gap: 12px;
|
|
98
|
-
align-items: center;
|
|
99
|
-
flex-wrap: wrap;
|
|
100
|
-
}
|
|
101
|
-
.btn {
|
|
102
|
-
appearance: none;
|
|
103
|
-
border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
|
|
104
|
-
background: color-mix(in srgb, var(--accent) 8%, transparent);
|
|
105
|
-
color: var(--fg);
|
|
106
|
-
border-radius: 10px;
|
|
107
|
-
padding: 8px 12px;
|
|
108
|
-
font-weight: 700;
|
|
109
|
-
cursor: pointer;
|
|
110
|
-
}
|
|
111
|
-
.btn:disabled {
|
|
112
|
-
opacity: 0.6;
|
|
113
|
-
cursor: not-allowed;
|
|
114
|
-
}
|
|
115
|
-
.mono {
|
|
116
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;
|
|
117
|
-
}
|
|
118
|
-
.tbl {
|
|
119
|
-
width: 100%;
|
|
120
|
-
border-collapse: collapse;
|
|
121
|
-
}
|
|
122
|
-
.tbl th,
|
|
123
|
-
.tbl td {
|
|
124
|
-
padding: 6px 8px;
|
|
125
|
-
border-bottom: 1px dashed color-mix(in srgb, var(--fg) 18%, transparent);
|
|
126
|
-
vertical-align: top;
|
|
127
|
-
}
|
|
128
|
-
.tbl th {
|
|
129
|
-
text-align: left;
|
|
130
|
-
}
|
|
131
|
-
.ok {
|
|
132
|
-
color: var(--ok);
|
|
133
|
-
}
|
|
134
|
-
.bad {
|
|
135
|
-
color: var(--bad);
|
|
136
|
-
}
|
|
137
|
-
.warn {
|
|
138
|
-
color: var(--warn);
|
|
139
|
-
}
|
|
140
|
-
.small {
|
|
141
|
-
font-size: 0.92em;
|
|
142
|
-
}
|
|
143
|
-
code {
|
|
144
|
-
background: color-mix(in srgb, var(--accent) 10%, transparent);
|
|
145
|
-
padding: 0.1rem 0.35rem;
|
|
146
|
-
border-radius: 0.35rem;
|
|
147
|
-
}
|
|
148
|
-
input[type='number'] {
|
|
149
|
-
padding: 8px 10px;
|
|
150
|
-
border-radius: 10px;
|
|
151
|
-
border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
|
|
152
|
-
min-width: 120px;
|
|
153
|
-
}
|
|
154
|
-
.timeline {
|
|
155
|
-
display: flex;
|
|
156
|
-
gap: 8px;
|
|
157
|
-
align-items: center;
|
|
158
|
-
flex-wrap: wrap;
|
|
159
|
-
}
|
|
160
|
-
.badge {
|
|
161
|
-
border-radius: 999px;
|
|
162
|
-
padding: 4px 10px;
|
|
163
|
-
background: var(--chip);
|
|
164
|
-
border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent);
|
|
165
|
-
}
|
|
166
|
-
.badge .day {
|
|
167
|
-
font-weight: 700;
|
|
168
|
-
margin-left: 6px;
|
|
169
|
-
}
|
|
170
|
-
.legend {
|
|
171
|
-
display: flex;
|
|
172
|
-
gap: 12px;
|
|
173
|
-
flex-wrap: wrap;
|
|
174
|
-
align-items: center;
|
|
175
|
-
}
|
|
176
|
-
.dot {
|
|
177
|
-
width: 10px;
|
|
178
|
-
height: 10px;
|
|
179
|
-
border-radius: 50%;
|
|
180
|
-
display: inline-block;
|
|
181
|
-
vertical-align: middle;
|
|
182
|
-
margin-right: 6px;
|
|
183
|
-
}
|
|
184
|
-
.spark {
|
|
185
|
-
width: 100%;
|
|
186
|
-
height: 72px;
|
|
187
|
-
border-radius: 10px;
|
|
188
|
-
background: color-mix(in srgb, var(--accent) 6%, transparent);
|
|
189
|
-
border: 1px solid color-mix(in srgb, var(--accent) 18%, transparent);
|
|
190
|
-
}
|
|
191
|
-
details summary {
|
|
192
|
-
cursor: pointer;
|
|
193
|
-
font-weight: 700;
|
|
194
|
-
}
|
|
195
|
-
#advanced-rules {
|
|
196
|
-
white-space: pre-line;
|
|
197
|
-
}
|
|
198
|
-
</style>
|
|
199
|
-
</head>
|
|
200
|
-
<body>
|
|
201
|
-
<main>
|
|
202
|
-
<header class="row">
|
|
203
|
-
<div>
|
|
204
|
-
<h1>Grass seed — molecular germination</h1>
|
|
205
|
-
<p>A simple, visual story of what happens inside a seed right after it takes up water.</p>
|
|
206
|
-
</div>
|
|
207
|
-
<div class="row" style="margin-left: auto">
|
|
208
|
-
<label class="muted small" for="days">Days to simulate (1–60):</label>
|
|
209
|
-
<input id="days" type="number" min="1" max="60" step="1" value="20" aria-label="Days to simulate" />
|
|
210
|
-
<button id="run" class="btn">Run</button>
|
|
211
|
-
</div>
|
|
212
|
-
</header>
|
|
213
|
-
|
|
214
|
-
<section>
|
|
215
|
-
<h2>What this is?</h2>
|
|
216
|
-
<p>
|
|
217
|
-
A friendly walkthrough of early <em>germination</em>. We track a few helpful dials: water in the seed, two
|
|
218
|
-
hormones (a “brake” and a “go” signal), an enzyme that frees up food, the amount of usable sugar, and a
|
|
219
|
-
“wall‑softening” factor that lets the root tip push out. When the three key needs are met — enough water,
|
|
220
|
-
enough sugar, and soft walls — the <strong>radicle</strong> (first root) pops out. A few days later, the
|
|
221
|
-
<strong>coleoptile</strong> (shoot sheath) shows.
|
|
222
|
-
</p>
|
|
223
|
-
<div class="grid small">
|
|
224
|
-
<div>
|
|
225
|
-
<strong>How to use it</strong><br />Pick the number of days and press <em>Run</em>. Read the milestones and
|
|
226
|
-
watch the trend lines.
|
|
227
|
-
</div>
|
|
228
|
-
<div>
|
|
229
|
-
<strong>What to look for</strong><br />Do water (W), sugar (S), and wall‑softening (E) cross their marks?
|
|
230
|
-
That’s the radicle moment.
|
|
231
|
-
</div>
|
|
232
|
-
<div>
|
|
233
|
-
<strong>Keep in mind</strong><br />This is an <em>educational toy</em>: numbers are illustrative, not
|
|
234
|
-
lab‑calibrated.
|
|
235
|
-
</div>
|
|
236
|
-
</div>
|
|
237
|
-
</section>
|
|
238
|
-
|
|
239
|
-
<section>
|
|
240
|
-
<h2>Glossary (plain → scientific)</h2>
|
|
241
|
-
<div class="grid small">
|
|
242
|
-
<div>
|
|
243
|
-
<span class="dot" style="background: var(--w)"></span><strong>Water level</strong> → <em>W</em> (hydration,
|
|
244
|
-
0–1)
|
|
245
|
-
</div>
|
|
246
|
-
<div>
|
|
247
|
-
<span class="dot" style="background: var(--aba)"></span><strong>Brake hormone</strong> →
|
|
248
|
-
<em>ABA</em> (abscisic acid)
|
|
249
|
-
</div>
|
|
250
|
-
<div>
|
|
251
|
-
<span class="dot" style="background: var(--g)"></span><strong>Go hormone</strong> →
|
|
252
|
-
<em>GA</em> (gibberellin)
|
|
253
|
-
</div>
|
|
254
|
-
<div>
|
|
255
|
-
<span class="dot" style="background: var(--d)"></span><strong>Growth brakes</strong> →
|
|
256
|
-
<em>DELLA</em> proteins
|
|
257
|
-
</div>
|
|
258
|
-
<div>
|
|
259
|
-
<span class="dot" style="background: var(--x)"></span><strong>Starch‑unlocker</strong> → <em>α‑amylase</em>
|
|
260
|
-
</div>
|
|
261
|
-
<div>
|
|
262
|
-
<span class="dot" style="background: var(--s)"></span><strong>Fuel</strong> → <em>S</em> (metabolizable
|
|
263
|
-
sugar)
|
|
264
|
-
</div>
|
|
265
|
-
<div>
|
|
266
|
-
<span class="dot" style="background: var(--e)"></span><strong>Wall softeners</strong> →
|
|
267
|
-
<em>E</em> (expansins)
|
|
268
|
-
</div>
|
|
269
|
-
</div>
|
|
270
|
-
</section>
|
|
271
|
-
|
|
272
|
-
<section id="answer">
|
|
273
|
-
<h2>Answer</h2>
|
|
274
|
-
<div id="timeline" class="timeline small"></div>
|
|
275
|
-
<div id="charts"></div>
|
|
276
|
-
<div id="answer-body"><em class="muted">Not computed yet.</em></div>
|
|
277
|
-
</section>
|
|
278
|
-
|
|
279
|
-
<section id="reason">
|
|
280
|
-
<h2>Reason why</h2>
|
|
281
|
-
<div class="mono small" id="advanced-rules"></div>
|
|
282
|
-
</section>
|
|
283
|
-
|
|
284
|
-
<section id="check">
|
|
285
|
-
<h2>Check (harness)</h2>
|
|
286
|
-
<div id="check-body"><em class="muted">Not computed yet.</em></div>
|
|
287
|
-
</section>
|
|
288
|
-
</main>
|
|
289
|
-
|
|
290
|
-
<script>
|
|
291
|
-
(function () {
|
|
292
|
-
'use strict';
|
|
293
|
-
|
|
294
|
-
const $ = (id) => document.getElementById(id);
|
|
295
|
-
const setHTML = (id, html) => {
|
|
296
|
-
const el = $(id);
|
|
297
|
-
if (el) el.innerHTML = html;
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
// ------------------ Parameters & thresholds (dimensionless) ------------------
|
|
301
|
-
const P = {
|
|
302
|
-
kw: 0.45, // hydration speed
|
|
303
|
-
ka: 0.3, // ABA decay weight (scaled by W)
|
|
304
|
-
kg: 0.35, // GA rise
|
|
305
|
-
kd: 0.4, // DELLA degradation by GA
|
|
306
|
-
kx: 0.25, // amylase induction by GA
|
|
307
|
-
ks: 0.2, // sugar accumulation per amylase
|
|
308
|
-
ke: 0.22, // expansin induction by GA
|
|
309
|
-
// thresholds
|
|
310
|
-
W_hydrated: 0.85,
|
|
311
|
-
ABA_low: 0.2,
|
|
312
|
-
G_active: 0.6,
|
|
313
|
-
D_low: 0.2,
|
|
314
|
-
X_on: 0.6,
|
|
315
|
-
S_ready: 1.0,
|
|
316
|
-
E_soft: 0.8,
|
|
317
|
-
coleoptile_delay: 3,
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
// Render advanced rules text
|
|
321
|
-
(function () {
|
|
322
|
-
const txt = [
|
|
323
|
-
'State: 0 ≤ W,G,X,E ≤ 1; ABA,DELLA ≥ 0; sugar S ≥ 0.',
|
|
324
|
-
'Hydration: W_{t+1} = W_t + k_w(1 − W_t).',
|
|
325
|
-
'ABA decay: ABA_{t+1} = ABA_t · (1 − k_a W_t).',
|
|
326
|
-
'GA rise: G_{t+1} = G_t + k_g W_t (1 − G_t).',
|
|
327
|
-
'DELLA degradation: D_{t+1} = D_t · (1 − k_d G_t).',
|
|
328
|
-
'α‑amylase: X_{t+1} = X_t + k_x G_t (1 − X_t).',
|
|
329
|
-
'Sugar: S_{t+1} = S_t + k_s X_t.',
|
|
330
|
-
'Expansins: E_{t+1} = E_t + k_e G_t (1 − E_t).',
|
|
331
|
-
'Milestones at first t with thresholds; coleoptile = radicle + 3 days.',
|
|
332
|
-
];
|
|
333
|
-
const el = $('advanced-rules');
|
|
334
|
-
if (el) el.textContent = txt.join('\n');
|
|
335
|
-
})();
|
|
336
|
-
|
|
337
|
-
// ------------------ Simulator ------------------
|
|
338
|
-
function clamp01(x) {
|
|
339
|
-
return Math.max(0, Math.min(1, x));
|
|
340
|
-
}
|
|
341
|
-
function run(days = 20) {
|
|
342
|
-
// Initial conditions
|
|
343
|
-
let W = 0.1,
|
|
344
|
-
ABA = 1.0,
|
|
345
|
-
G = 0.0,
|
|
346
|
-
D = 1.0,
|
|
347
|
-
X = 0.0,
|
|
348
|
-
S = 0.0,
|
|
349
|
-
E = 0.0;
|
|
350
|
-
const hist = [{ day: 0, W, ABA, G, D, X, S, E }];
|
|
351
|
-
|
|
352
|
-
for (let t = 0; t < days; t++) {
|
|
353
|
-
W = clamp01(W + P.kw * (1 - W));
|
|
354
|
-
ABA = Math.max(0, ABA * (1 - P.ka * W));
|
|
355
|
-
G = clamp01(G + P.kg * W * (1 - G));
|
|
356
|
-
D = Math.max(0, D * (1 - P.kd * G));
|
|
357
|
-
X = clamp01(X + P.kx * G * (1 - X));
|
|
358
|
-
S = Math.max(0, S + P.ks * X);
|
|
359
|
-
E = clamp01(E + P.ke * G * (1 - E));
|
|
360
|
-
hist.push({ day: t + 1, W, ABA, G, D, X, S, E });
|
|
361
|
-
}
|
|
362
|
-
return hist;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function firstDay(hist, pred) {
|
|
366
|
-
for (const row of hist) {
|
|
367
|
-
if (pred(row)) return row.day;
|
|
368
|
-
}
|
|
369
|
-
return null;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function computeMilestones(hist) {
|
|
373
|
-
const m = {};
|
|
374
|
-
m.hydrated = firstDay(hist, (r) => r.W >= P.W_hydrated);
|
|
375
|
-
m.ABA_low = firstDay(hist, (r) => r.ABA <= P.ABA_low);
|
|
376
|
-
m.G_active = firstDay(hist, (r) => r.G >= P.G_active);
|
|
377
|
-
m.D_low = firstDay(hist, (r) => r.D <= P.D_low);
|
|
378
|
-
m.X_on = firstDay(hist, (r) => r.X >= P.X_on);
|
|
379
|
-
m.S_ready = firstDay(hist, (r) => r.S >= P.S_ready);
|
|
380
|
-
m.E_soft = firstDay(hist, (r) => r.E >= P.E_soft);
|
|
381
|
-
m.radicle = firstDay(hist, (r) => r.W >= P.W_hydrated && r.S >= P.S_ready && r.E >= P.E_soft);
|
|
382
|
-
m.coleoptile = m.radicle == null ? null : m.radicle + P.coleoptile_delay;
|
|
383
|
-
return m;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// ------------------ Rendering helpers ------------------
|
|
387
|
-
function fmt2(x) {
|
|
388
|
-
return (Math.round(x * 100) / 100).toFixed(2);
|
|
389
|
-
}
|
|
390
|
-
function badge(label, day, colorVar) {
|
|
391
|
-
const dayStr =
|
|
392
|
-
day == null ? '<span class="bad">not reached</span>' : '<span class="day">' + day + '</span> d';
|
|
393
|
-
return `<span class="badge"><span class="dot" style="background:var(${colorVar})"></span>${label}: ${dayStr}</span>`;
|
|
394
|
-
}
|
|
395
|
-
function renderTimeline(m) {
|
|
396
|
-
const html = [
|
|
397
|
-
badge('Hydrated', m.hydrated, '--w'),
|
|
398
|
-
badge('ABA low', m.ABA_low, '--aba'),
|
|
399
|
-
badge('GA active', m.G_active, '--g'),
|
|
400
|
-
badge('DELLA low', m.D_low, '--d'),
|
|
401
|
-
badge('Amylase on', m.X_on, '--x'),
|
|
402
|
-
badge('Sugar ready', m.S_ready, '--s'),
|
|
403
|
-
badge('Wall soft', m.E_soft, '--e'),
|
|
404
|
-
badge('Radicle', m.radicle, '--e'),
|
|
405
|
-
badge('Coleoptile', m.coleoptile, '--g'),
|
|
406
|
-
].join('');
|
|
407
|
-
setHTML('timeline', html);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function renderAnswer(hist, m) {
|
|
411
|
-
renderTimeline(m);
|
|
412
|
-
|
|
413
|
-
// Table
|
|
414
|
-
const milestoneRows = [
|
|
415
|
-
['Hydrated (W ≥ 0.85)', m.hydrated],
|
|
416
|
-
['Brake hormone low (ABA ≤ 0.20)', m.ABA_low],
|
|
417
|
-
['Go hormone active (GA ≥ 0.60)', m.G_active],
|
|
418
|
-
['Growth brakes low (DELLA ≤ 0.20)', m.D_low],
|
|
419
|
-
['Starch‑unlocker on (α‑amylase ≥ 0.60)', m.X_on],
|
|
420
|
-
['Fuel ready (Sugar ≥ 1.00)', m.S_ready],
|
|
421
|
-
['Walls soft (Expansins ≥ 0.80)', m.E_soft],
|
|
422
|
-
['Radicle emergence', m.radicle],
|
|
423
|
-
['Coleoptile visible', m.coleoptile],
|
|
424
|
-
];
|
|
425
|
-
let html = '<table class="tbl small"><thead><tr><th>Milestone</th><th>Day</th></tr></thead><tbody>';
|
|
426
|
-
for (const [name, day] of milestoneRows) {
|
|
427
|
-
const pretty = day == null ? '<span class="bad">not reached</span>' : day;
|
|
428
|
-
html += `<tr><td>${name}</td><td>${pretty}</td></tr>`;
|
|
429
|
-
}
|
|
430
|
-
html += '</tbody></table>';
|
|
431
|
-
|
|
432
|
-
// State trace (first 16 days)
|
|
433
|
-
const tail = hist
|
|
434
|
-
.slice(0, 16)
|
|
435
|
-
.map(
|
|
436
|
-
(r) =>
|
|
437
|
-
`${r.day.toString().padStart(2)} W=${fmt2(r.W)} ABA=${fmt2(r.ABA)} GA=${fmt2(r.G)} DELLA=${fmt2(r.D)} α-amylase=${fmt2(r.X)} S=${fmt2(r.S)} E=${fmt2(r.E)}`,
|
|
438
|
-
);
|
|
439
|
-
html += `<p class="small muted">First 16 days (state trace):</p>`;
|
|
440
|
-
html += `<pre class="mono" style="white-space:pre-wrap">${tail.join('\n')}</pre>`;
|
|
441
|
-
setHTML('answer-body', html);
|
|
442
|
-
|
|
443
|
-
// Mini‑charts
|
|
444
|
-
renderSparklines(hist);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Simple sparklines (inline SVG)
|
|
448
|
-
function sparkline(id, series, colorVar, yMax) {
|
|
449
|
-
const w = 1000,
|
|
450
|
-
h = 72,
|
|
451
|
-
pad = 6;
|
|
452
|
-
const maxX = series.length - 1;
|
|
453
|
-
const maxY = yMax !== undefined ? yMax : Math.max(...series);
|
|
454
|
-
const pts = series
|
|
455
|
-
.map((v, i) => {
|
|
456
|
-
const x = pad + (w - 2 * pad) * (i / maxX);
|
|
457
|
-
const y = h - pad - (h - 2 * pad) * (v / (maxY || 1));
|
|
458
|
-
return `${x.toFixed(2)},${y.toFixed(2)}`;
|
|
459
|
-
})
|
|
460
|
-
.join(' ');
|
|
461
|
-
return `<svg class="spark" viewBox="0 0 ${w} ${h}" preserveAspectRatio="none" role="img" aria-label="trend">
|
|
462
|
-
<polyline fill="none" stroke="var(${colorVar})" stroke-width="3" points="${pts}" />
|
|
463
|
-
</svg>`;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function renderSparklines(hist) {
|
|
467
|
-
const W = hist.map((r) => r.W),
|
|
468
|
-
S = hist.map((r) => r.S),
|
|
469
|
-
E = hist.map((r) => r.E),
|
|
470
|
-
G = hist.map((r) => r.G);
|
|
471
|
-
const box = document.getElementById('charts');
|
|
472
|
-
let html = '<div class="legend small">';
|
|
473
|
-
html += '<span><span class="dot" style="background:var(--w)"></span>Water (W)</span>';
|
|
474
|
-
html += '<span><span class="dot" style="background:var(--s)"></span>Sugar (S)</span>';
|
|
475
|
-
html += '<span><span class="dot" style="background:var(--e)"></span>Wall softening (E)</span>';
|
|
476
|
-
html += '<span><span class="dot" style="background:var(--g)"></span>Go hormone (GA)</span>';
|
|
477
|
-
html += '</div>';
|
|
478
|
-
html += sparkline('w', W, '--w', 1);
|
|
479
|
-
html += sparkline('s', S, '--s', Math.max(1, Math.max(...S)));
|
|
480
|
-
html += sparkline('e', E, '--e', 1);
|
|
481
|
-
html += sparkline('g', G, '--g', 1);
|
|
482
|
-
box.innerHTML = html;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// ------------------ Checks ------------------
|
|
486
|
-
function runChecks(hist, m) {
|
|
487
|
-
const lines = [];
|
|
488
|
-
let okAll = true;
|
|
489
|
-
|
|
490
|
-
// 1) In-range & trend direction
|
|
491
|
-
function nonDecreasing(seq) {
|
|
492
|
-
for (let i = 1; i < seq.length; i++) {
|
|
493
|
-
if (seq[i] + 1e-12 < seq[i - 1]) return false;
|
|
494
|
-
}
|
|
495
|
-
return true;
|
|
496
|
-
}
|
|
497
|
-
function nonIncreasing(seq) {
|
|
498
|
-
for (let i = 1; i < seq.length; i++) {
|
|
499
|
-
if (seq[i] - 1e-12 > seq[i - 1]) return false;
|
|
500
|
-
}
|
|
501
|
-
return true;
|
|
502
|
-
}
|
|
503
|
-
const Wseries = hist.map((r) => r.W),
|
|
504
|
-
ABAseries = hist.map((r) => r.ABA),
|
|
505
|
-
Dseries = hist.map((r) => r.D);
|
|
506
|
-
const Gseries = hist.map((r) => r.G),
|
|
507
|
-
Xseries = hist.map((r) => r.X),
|
|
508
|
-
Eseries = hist.map((r) => r.E),
|
|
509
|
-
Sseries = hist.map((r) => r.S);
|
|
510
|
-
const okBounds =
|
|
511
|
-
Math.min(...Wseries) >= 0 &&
|
|
512
|
-
Math.max(...Wseries) <= 1 &&
|
|
513
|
-
Math.min(...Gseries) >= 0 &&
|
|
514
|
-
Math.max(...Gseries) <= 1 &&
|
|
515
|
-
Math.min(...Xseries) >= 0 &&
|
|
516
|
-
Math.max(...Xseries) <= 1 &&
|
|
517
|
-
Math.min(...Eseries) >= 0 &&
|
|
518
|
-
Math.max(...Eseries) <= 1 &&
|
|
519
|
-
Math.min(...ABAseries) >= 0 &&
|
|
520
|
-
Math.min(...Dseries) >= 0 &&
|
|
521
|
-
Math.min(...Sseries) >= 0;
|
|
522
|
-
const okMono =
|
|
523
|
-
nonDecreasing(Wseries) &&
|
|
524
|
-
nonDecreasing(Gseries) &&
|
|
525
|
-
nonDecreasing(Xseries) &&
|
|
526
|
-
nonDecreasing(Eseries) &&
|
|
527
|
-
nonDecreasing(Sseries) &&
|
|
528
|
-
nonIncreasing(ABAseries) &&
|
|
529
|
-
nonIncreasing(Dseries);
|
|
530
|
-
lines.push(
|
|
531
|
-
`Signals stay within sensible ranges and move in the expected direction: ${okBounds && okMono ? 'OK' : 'Issue'}`,
|
|
532
|
-
);
|
|
533
|
-
okAll = okAll && okBounds && okMono;
|
|
534
|
-
|
|
535
|
-
// 2) Logical ordering
|
|
536
|
-
const orderOK =
|
|
537
|
-
(m.ABA_low ?? 1e9) >= (m.hydrated ?? -1) &&
|
|
538
|
-
(m.G_active ?? 1e9) >= (m.hydrated ?? -1) &&
|
|
539
|
-
(m.D_low ?? 1e9) >= (m.G_active ?? -1) &&
|
|
540
|
-
(m.X_on ?? 1e9) >= (m.G_active ?? -1) &&
|
|
541
|
-
(m.S_ready ?? 1e9) >= (m.X_on ?? -1) &&
|
|
542
|
-
(m.E_soft ?? 1e9) >= (m.G_active ?? -1) &&
|
|
543
|
-
(m.radicle ?? 1e9) >= Math.max(m.S_ready ?? -1, m.E_soft ?? -1, m.hydrated ?? -1);
|
|
544
|
-
lines.push(`Milestones happen in a sensible order: ${orderOK ? 'OK' : 'Check thresholds'}`);
|
|
545
|
-
okAll = okAll && orderOK;
|
|
546
|
-
|
|
547
|
-
// 3) Radicle condition equivalence
|
|
548
|
-
let okRad = true;
|
|
549
|
-
if (m.radicle != null) {
|
|
550
|
-
const r = hist[m.radicle];
|
|
551
|
-
okRad = r && r.W >= P.W_hydrated && r.S >= P.S_ready && r.E >= P.E_soft;
|
|
552
|
-
}
|
|
553
|
-
lines.push(
|
|
554
|
-
`On the radicle day, water + sugar + soft walls are all high enough: ${okRad ? 'OK' : 'Not satisfied'}`,
|
|
555
|
-
);
|
|
556
|
-
okAll = okAll && okRad;
|
|
557
|
-
|
|
558
|
-
// 4) Coleoptile offset
|
|
559
|
-
const okCol = (m.coleoptile == null && m.radicle == null) || m.coleoptile === m.radicle + P.coleoptile_delay;
|
|
560
|
-
lines.push(`Coleoptile appears ${P.coleoptile_delay} days after radicle: ${okCol ? 'OK' : 'Mismatch'}`);
|
|
561
|
-
|
|
562
|
-
lines.push('');
|
|
563
|
-
lines.push(`All checks passed? ${okAll ? 'Yes ✓' : 'Some items need attention'}`);
|
|
564
|
-
return lines;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
function renderChecks(hist, m) {
|
|
568
|
-
const lines = runChecks(hist, m);
|
|
569
|
-
const wrap = $('check-body');
|
|
570
|
-
const pre = document.createElement('pre');
|
|
571
|
-
pre.className = 'mono';
|
|
572
|
-
pre.style.whiteSpace = 'pre-wrap';
|
|
573
|
-
pre.textContent = lines.join('\n');
|
|
574
|
-
wrap.replaceChildren(pre);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// ------------------ Wire up ------------------
|
|
578
|
-
function runAll() {
|
|
579
|
-
const days = Math.max(1, Math.min(60, parseInt($('days').value || '20', 10)));
|
|
580
|
-
const hist = run(days);
|
|
581
|
-
const m = computeMilestones(hist);
|
|
582
|
-
renderAnswer(hist, m);
|
|
583
|
-
renderChecks(hist, m);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
$('run').addEventListener('click', runAll);
|
|
587
|
-
// initial
|
|
588
|
-
runAll();
|
|
589
|
-
})();
|
|
590
|
-
</script>
|
|
591
|
-
</body>
|
|
592
|
-
</html>
|