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.
Files changed (51) hide show
  1. package/HANDBOOK.md +4 -0
  2. package/README.md +0 -1
  3. package/examples/ershov-mixed-computation.n3 +106 -0
  4. package/examples/output/ershov-mixed-computation.n3 +15 -0
  5. package/eyeling.js +510 -263
  6. package/lib/cli.js +22 -12
  7. package/lib/engine.js +488 -251
  8. package/package.json +2 -3
  9. package/arctifacts/README.md +0 -59
  10. package/arctifacts/ackermann.html +0 -678
  11. package/arctifacts/auroracare.html +0 -1297
  12. package/arctifacts/bike-trip.html +0 -752
  13. package/arctifacts/binomial-theorem.html +0 -631
  14. package/arctifacts/bmi.html +0 -511
  15. package/arctifacts/building-performance.html +0 -750
  16. package/arctifacts/clinical-care.html +0 -726
  17. package/arctifacts/collatz.html +0 -403
  18. package/arctifacts/complex.html +0 -321
  19. package/arctifacts/control-system.html +0 -482
  20. package/arctifacts/delfour.html +0 -849
  21. package/arctifacts/earthquake-epicenter.html +0 -982
  22. package/arctifacts/eco-route.html +0 -662
  23. package/arctifacts/euclid-infinitude.html +0 -564
  24. package/arctifacts/euler-identity.html +0 -667
  25. package/arctifacts/exoplanet-transit.html +0 -1000
  26. package/arctifacts/faltings-theorem.html +0 -1046
  27. package/arctifacts/fibonacci.html +0 -299
  28. package/arctifacts/fundamental-theorem-arithmetic.html +0 -398
  29. package/arctifacts/godel-numbering.html +0 -743
  30. package/arctifacts/gps-bike.html +0 -759
  31. package/arctifacts/gps-clinical-bench.html +0 -792
  32. package/arctifacts/graph-french.html +0 -449
  33. package/arctifacts/grass-molecular.html +0 -592
  34. package/arctifacts/group-theory.html +0 -740
  35. package/arctifacts/health-info.html +0 -833
  36. package/arctifacts/kaprekar-constant.html +0 -576
  37. package/arctifacts/lee.html +0 -805
  38. package/arctifacts/linked-lists.html +0 -502
  39. package/arctifacts/lldm.html +0 -612
  40. package/arctifacts/matrix-multiplication.html +0 -502
  41. package/arctifacts/matrix.html +0 -651
  42. package/arctifacts/newton-raphson.html +0 -944
  43. package/arctifacts/peano-factorial.html +0 -456
  44. package/arctifacts/pi.html +0 -363
  45. package/arctifacts/polynomial.html +0 -646
  46. package/arctifacts/prime.html +0 -366
  47. package/arctifacts/pythagorean-theorem.html +0 -468
  48. package/arctifacts/rest-path.html +0 -469
  49. package/arctifacts/roots-of-unity.html +0 -363
  50. package/arctifacts/turing.html +0 -409
  51. 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>