|
| 1 | +// SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | +// SPDX-FileCopyrightText: 2025-2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> |
| 3 | += Cluster C10 (utils/tools/companions/narrative/proven) — migration evidence (2026-06-05) |
| 4 | +:toc: macro |
| 5 | + |
| 6 | +[IMPORTANT] |
| 7 | +==== |
| 8 | +*Verdict: NO_NEW_BRAINS — coprocessor bridges, float-wall physics, string-wall |
| 9 | +i18n/persistence/errors, and effect-gated accessibility/settings orchestration.* |
| 10 | +All 21 files are host-side senses. No new AffineScript kernels are required |
| 11 | +because: |
| 12 | +
|
| 13 | +1. Nine `*Coprocessor.res` bridge files are pure `external` FFI declarations |
| 14 | + wrapping already-compiled wasm brains: |
| 15 | + `moletairecoprocessors.wasm` (Moletaire behaviour FSM), |
| 16 | + `moletairehunger.wasm` (hunger ordinals — already extracted), |
| 17 | + `missionbriefing.wasm` (mission briefing state machine), |
| 18 | + `safefloat.wasm` (safe-float arithmetic coprocessor), |
| 19 | + `passwordcracker.wasm` (password-crack score engine), |
| 20 | + `portscanner.wasm` (port-scan result classifier), |
| 21 | + `colorpalette.wasm` (color-palette lookup), |
| 22 | + `fontscale.wasm` (font-scale arithmetic), |
| 23 | + `gamei18n.wasm` (plural-form selection + language-cycle arithmetic). |
| 24 | +2. Two float-wall files (`MoletaireHunger.res`, `SafeAngle.res`): |
| 25 | + `MoletaireHunger.res` contains gravity/hunger physics |
| 26 | + (`calculatePull`, `calculateTotalPull`, `calculateHungerRate` — all f64); |
| 27 | + the integer-flavoured `getBehaviour`/`hungerColor`/`willEatObjective` enum |
| 28 | + functions are already extracted to `moletairehunger.wasm`. |
| 29 | + `SafeAngle.res` is pure trigonometry (`Math.atan2`, `normalize`, |
| 30 | + `Math.cos`, `Math.sin`, `mod_float`) — float-wall throughout. |
| 31 | +3. String-wall files: `ProvenError.res` (all constructors carry `operation`, |
| 32 | + `message`, `errorType` string fields), `MoletairePersistence.res` |
| 33 | + (`equipmentToString`/`equipmentFromString` string serialization via |
| 34 | + `localStorage`), `Locales.res` (five-language translation dicts; all data |
| 35 | + is string key→value, `getTranslations(string)` dispatches on string param), |
| 36 | + `PolyglotI18n.res` (`String.replaceAll`, JSON string parsing, async |
| 37 | + `fetch` of locale JSON files, string template literals in `tw`/`tn`). |
| 38 | +4. Effect-gated files: `Announcer.res` (gated on |
| 39 | + `AccessibilitySettings.isScreenReaderEnabled()`; passes string messages to |
| 40 | + `DomA11y.announce`), `DomA11y.res` (all `%raw` DOM manipulation — |
| 41 | + `window.matchMedia`, `document.createElement`, `region.textContent`), |
| 42 | + `ColorPalette.res` (branches on `AccessibilitySettings.isHighContrastEnabled()` |
| 43 | + and `getColorBlindMode()` mutable JS refs; integer color tables are |
| 44 | + already extracted to `colorpalette.wasm`), `KeyboardNav.res` (all `%raw` |
| 45 | + `window.addEventListener/removeEventListener`, `e.key`, `e.preventDefault`), |
| 46 | + `PanicHandler.res` (`Console.error`, string template literals), |
| 47 | + `UserSettings.res` (`Storage` API, `Audio` API, `DesktopIntegration`; |
| 48 | + the `stance` enum serializes via string literals and has no standalone |
| 49 | + integer computation). |
| 50 | +
|
| 51 | +No 4-gate recipe is applied; no new wasm is produced. |
| 52 | +==== |
| 53 | + |
| 54 | +toc::[] |
| 55 | + |
| 56 | +== File-by-file disposition |
| 57 | + |
| 58 | +[cols="3,1,4",options="header"] |
| 59 | +|=== |
| 60 | +| File | Disposition | Reason |
| 61 | +| `src/app/companions/MoletaireCoprocessorsCoprocessor.res` | SENSES (bridge) | ReScript FFI wrapping `MoletaireCoprocessorsBridge.js` → `moletairecoprocessors.wasm`. Pure `external` declarations. Moletaire behaviour FSM brain already live. |
| 62 | +| `src/app/companions/MoletaireHunger.res` | SENSES (float-wall) | Gravity/hunger physics: `calculatePull`, `calculateTotalPull`, `calculateHungerRate` — all f64. Integer-flavoured functions (`getBehaviour`, `hungerColor`, `willEatObjective`) already extracted to `moletairehunger.wasm`. |
| 63 | +| `src/app/companions/MoletaireHungerCoprocessor.res` | SENSES (bridge) | ReScript FFI wrapping `MoletaireHungerCoprocessorBridge.js` → `moletairehunger.wasm`. Pure `external` declarations. Hunger-ordinal brain already live. |
| 64 | +| `src/app/companions/MoletairePersistence.res` | SENSES (string-wall gated) | localStorage persistence: `equipmentToString`/`equipmentFromString` string serialization, `Storage.getString`/`setBool`. String-wall prevents extraction. |
| 65 | +| `src/app/narrative/MissionBriefingCoprocessor.res` | SENSES (bridge) | ReScript FFI wrapping `MissionBriefingCoprocessorBridge.js` → `missionbriefing.wasm`. Pure `external` declarations. Mission briefing state machine already live. |
| 66 | +| `src/app/proven/ProvenError.res` | SENSES (string-wall gated) | Error type `provenError` with `operation: string`, `message: string`, `errorType: string` fields. All constructors string-bearing. String-wall prevents extraction. |
| 67 | +| `src/app/proven/SafeAngle.res` | SENSES (float-wall) | Pure trigonometry: `Math.atan2`, `normalize`, `Math.cos`, `Math.sin`, `mod_float`. All f64 operations. No integer brain. |
| 68 | +| `src/app/proven/SafeFloatCoprocessor.res` | SENSES (bridge) | ReScript FFI wrapping `SafeFloatCoprocessorBridge.js` → `safefloat.wasm`. Pure `external` declarations. Safe-float arithmetic brain already live. |
| 69 | +| `src/app/tools/PasswordCrackerCoprocessor.res` | SENSES (bridge) | ReScript FFI wrapping `PasswordCrackerCoprocessorBridge.js` → `passwordcracker.wasm`. Pure `external` declarations. Password-crack score brain already live. |
| 70 | +| `src/app/tools/PortScannerCoprocessor.res` | SENSES (bridge) | ReScript FFI wrapping `PortScannerCoprocessorBridge.js` → `portscanner.wasm`. Pure `external` declarations. Port-scan classifier brain already live. Inline `deviceTypeId`/`securityLevelId` enum dispatch needs no wasm. |
| 71 | +| `src/app/utils/Announcer.res` | SENSES (effect-gated) | Screen-reader announcer: gated on `AccessibilitySettings.isScreenReaderEnabled()`, passes string messages to `DomA11y.announce`. No integer computation. |
| 72 | +| `src/app/utils/ColorPalette.res` | SENSES (effect-gated) | Color lookup tables (`alertColor(int)`, `deviceColor`, `securityColor`) read `AccessibilitySettings.isHighContrastEnabled()` and `getColorBlindMode()` mutable JS refs. Integer color tables already extracted to `colorpalette.wasm`. Effect-gated selector stays host-side. |
| 73 | +| `src/app/utils/ColorPaletteCoprocessor.res` | SENSES (bridge) | ReScript FFI wrapping `ColorPaletteCoprocessorBridge.js` → `colorpalette.wasm`. Pure `external` declarations. Color-palette lookup brain already live. |
| 74 | +| `src/app/utils/DomA11y.res` | SENSES (effect-gated) | DOM accessibility utilities: all `%raw` — `window.matchMedia`, `document.createElement`, `region.textContent`, event listeners. No integer computation. |
| 75 | +| `src/app/utils/FontScaleCoprocessor.res` | SENSES (bridge) | ReScript FFI wrapping `FontScaleCoprocessorBridge.js` → `fontscale.wasm`. Pure `external` declarations (`scaleFloat`, `scaleInt`, `clampScale`, `scaleIntClamped`). Font-scale arithmetic brain already live. |
| 76 | +| `src/app/utils/GameI18nCoprocessor.res` | SENSES (bridge) | ReScript FFI wrapping `GameI18nCoprocessorBridge.js` → `gamei18n.wasm`. Pure `external` declarations (`languageClamp`, `nextLanguage`, `selectPluralForm`). Plural-form selection + language-cycle brain already live. |
| 77 | +| `src/app/utils/KeyboardNav.res` | SENSES (effect-gated) | All `%raw` window event wiring: `window.addEventListener/removeEventListener('keydown')`, `e.key`, `e.preventDefault`. No integer computation. |
| 78 | +| `src/app/utils/Locales.res` | SENSES (string-wall gated) | Five-language translation dicts (~130 keys × 5 languages). `getTranslations(string)` dispatches on string lang-code parameter. All data is string key→string value. String-wall throughout. |
| 79 | +| `src/app/utils/PanicHandler.res` | SENSES (effect-gated) | Error reporting: `Console.error`, string template literals, `Promise.resolve()`. No integer computation. |
| 80 | +| `src/app/utils/PolyglotI18n.res` | SENSES (string-wall + effect-gated) | Polyglot i18n wrapper: async `fetch` of locale JSON files, `String.replaceAll` interpolation, `tw`/`tn` string ops, mutable `ref<string>` language state. String-wall + effect-wall prevents extraction. |
| 81 | +| `src/app/utils/UserSettings.res` | SENSES (effect-gated) | Settings persistence: `Storage` API (getString/setBool/setNumber), `Audio` API (setMasterVolume/setBgmVolume/setSfxVolume), `DesktopIntegration`. The `stance` enum (Jessica/Q) serializes via string literals — string-wall gated; no standalone integer computation. |
| 82 | +|=== |
| 83 | + |
| 84 | +== Already-live wasm brains referenced by C10 bridges |
| 85 | + |
| 86 | +The nine bridge files reference wasm kernels that are already compiled and |
| 87 | +live in `public/assets/wasm/`: |
| 88 | + |
| 89 | +[cols="2,2,3",options="header"] |
| 90 | +|=== |
| 91 | +| Bridge file | Wasm target | Brain description |
| 92 | +| `MoletaireCoprocessorsCoprocessor.res` | `moletairecoprocessors.wasm` | Moletaire companion behaviour FSM. Position/action decisions for the robotic mole companion. |
| 93 | +| `MoletaireHungerCoprocessor.res` | `moletairehunger.wasm` | Hunger ordinal decisions: `getBehaviour`, `hungerColor`, `willEatObjective`, `objectiveAtRisk`. Already extracted; float-wall physics stays host-side. |
| 94 | +| `MissionBriefingCoprocessor.res` | `missionbriefing.wasm` | Mission briefing state machine. Screen-progression logic. |
| 95 | +| `SafeFloatCoprocessor.res` | `safefloat.wasm` | Safe-float arithmetic coprocessor. Guards float operations with formal safety bounds. |
| 96 | +| `PasswordCrackerCoprocessor.res` | `passwordcracker.wasm` | Password-crack score engine. Integer scoring for crack success/failure states. |
| 97 | +| `PortScannerCoprocessor.res` | `portscanner.wasm` | Port-scan result classifier. Maps scan outcomes to integer result codes. |
| 98 | +| `ColorPaletteCoprocessor.res` | `colorpalette.wasm` | Color-palette lookup. Integer RGB hex values for alert/device/security levels. |
| 99 | +| `FontScaleCoprocessor.res` | `fontscale.wasm` | Font-scale arithmetic. `scaleFloat`, `scaleInt`, `clampScale`, `scaleIntClamped` pure integer/float pixel-size operations. |
| 100 | +| `GameI18nCoprocessor.res` | `gamei18n.wasm` | Plural-form selection and language-cycle arithmetic. `languageClamp` (0..4 band), `nextLanguage` (EN→ES→FR→DE→JA→EN cycle), `selectPluralForm` (count == 1 → singular). |
| 101 | +|=== |
| 102 | + |
| 103 | +C10 being NO_NEW_BRAINS does not add to the wasm surface — all nine brains |
| 104 | +were already live before C10 was triaged. |
| 105 | + |
| 106 | +== MoletaireHunger.res — float-wall analysis |
| 107 | + |
| 108 | +`MoletaireHunger.res` contains f64 physics that cannot cross the float-wall: |
| 109 | + |
| 110 | +* `calculatePull(dist: float, strength: float): float` — gravitational |
| 111 | + attraction formula (`strength / (dist * dist)`). Pure f64. |
| 112 | +* `calculateTotalPull(positions: array<...>, strength: float): (float, float)` |
| 113 | + — sum of pull vectors over all attractor positions. f64 throughout. |
| 114 | +* `calculateHungerRate(hungerMultiplier: float, ...): float` — speed-based |
| 115 | + hunger drain. f64 physics. |
| 116 | + |
| 117 | +The integer-flavoured functions are NOT extractable because they are already |
| 118 | +in `moletairehunger.wasm` from a prior migration pass: |
| 119 | + |
| 120 | +* `getBehaviour(hungerScore: float): behaviour` — float threshold → enum. |
| 121 | +* `hungerColor(hungerScore: float): int` — float → RGB hex. Already in wasm. |
| 122 | +* `willEatObjective(...)`, `objectiveAtRisk(...)` — float comparisons. |
| 123 | + |
| 124 | +Verdict: no new brain needed; the integer surface is already live. |
| 125 | + |
| 126 | +== SafeAngle.res — float-wall analysis |
| 127 | + |
| 128 | +All six functions in `SafeAngle.res` are pure f64 trigonometry: |
| 129 | + |
| 130 | +* `fromAtan2(dy: float, dx: float): safeAngle` — `Math.atan2`. |
| 131 | +* `normalize(a: safeAngle): safeAngle` — fmod-based angle normalization. |
| 132 | +* `diff(a: safeAngle, b: safeAngle): safeAngle` — angle subtraction with |
| 133 | + normalization. |
| 134 | +* `cos(a: safeAngle): float` — `Math.cos`. |
| 135 | +* `sin(a: safeAngle): float` — `Math.sin`. |
| 136 | +* `toFloat(a: safeAngle): float` — identity cast. |
| 137 | + |
| 138 | +The float-wall convention (AffineScript wasm backend has no f64 `sqrt` |
| 139 | +or general float codegen) requires these to remain host-side. The `safefloat.wasm` |
| 140 | +brain handles safe arithmetic on simple float ranges; trigonometry is a |
| 141 | +separate concern with no integer-only reformulation possible. |
| 142 | + |
| 143 | +== String-wall analysis: ProvenError, MoletairePersistence, Locales, PolyglotI18n |
| 144 | + |
| 145 | +[cols="1,1,4",options="header"] |
| 146 | +|=== |
| 147 | +| File | Wall type | Why NOT extracted |
| 148 | +| `ProvenError.res` | String-wall | Every constructor (`ProvenError` record) carries `operation: string`, `message: string`, `errorType: string` fields. No integer computation separable from the string payloads. |
| 149 | +| `MoletairePersistence.res` | String-wall + effect | `equipmentToString`/`equipmentFromString` use string serialization throughout; `Storage.getString`/`setBool` are DOM-side effects. No integer brain separable. |
| 150 | +| `Locales.res` | String-wall | Five-language dicts (~650 string pairs total). `getTranslations(langCode: string)` dispatches on a string parameter and returns `dict<string>`. No integer computation exists — the entire module is translation data. |
| 151 | +| `PolyglotI18n.res` | String-wall + effect | `tw` uses `String.replaceAll`; `tn` compares `count == 1.0` (float); async `fetch` for locale JSON; mutable `ref<string>` language state. String ops throughout; effect-wall (async + mutable ref). |
| 152 | +|=== |
| 153 | + |
| 154 | +In contrast, `GameI18nCoprocessor.res` already wraps `gamei18n.wasm` which |
| 155 | +handles the *integer* core (`languageClamp`, `nextLanguage`, |
| 156 | +`selectPluralForm`) — the AffineScript brain for this cluster's numeric |
| 157 | +i18n logic is already live. |
| 158 | + |
| 159 | +== UserSettings.res — stance enum analysis |
| 160 | + |
| 161 | +`UserSettings.res` contains a 2-value `stance` enum (Jessica | Q). |
| 162 | +At first glance it appears potentially extractable, but: |
| 163 | + |
| 164 | +1. `getCharacterStance()` reads `Storage.getString(keyStance)` and matches on |
| 165 | + string values `"Q"` / `_` — string-wall gated. |
| 166 | +2. `setCharacterStance(s: stance)` serializes to string literals `"Jessica"` / |
| 167 | + `"Q"` before calling `Storage.setString` — string-wall gated. |
| 168 | +3. The remaining functions (`getMasterVolume`, `getBgmVolume`, `getSfxVolume`, |
| 169 | + `init`, `setMasterVolume`, `setBgmVolume`, `setSfxVolume`, |
| 170 | + `setSystemTrayEnabled`) are pure effect-gated orchestration over `Audio` |
| 171 | + and `Storage` APIs with float volume values. |
| 172 | + |
| 173 | +No standalone integer brain is separable from the string serialization and |
| 174 | +effect-heavy orchestration. |
| 175 | + |
| 176 | +== Impact on remaining clusters |
| 177 | + |
| 178 | +C10 being NO_NEW_BRAINS does not affect the C12 roadmap. All nine wasm |
| 179 | +brains referenced by C10 bridges are already live. The utilities/tools/ |
| 180 | +companions/narrative/proven modules remain host-side ReScript throughout |
| 181 | +Phase Ω. The completion-drive directive proceeds to *C12*. |
0 commit comments