Skip to content

Commit c4d0b71

Browse files
committed
migration: 12 integer-brain kernels from idaptik sense-heavy sweep (cluster C14)
Completes the exhaustive corpus sweep (C13 + C14) over the sense-heavy directory groups: screens (+ training/locations), vm/lib/ocaml, engine + shared/src, verisimdb + proven, and the long-tail grab-bag (narrative/tools/tea/pickups/ pixi/burble/vm/bindings/idaptik-ums). 148 .res files. New brains (all G1 compile, G2 parity all-pass vs independent JS oracle, G4 assail clean): VictoryScreen, PlayTimeSplit, VeriSimError, MissionBriefing, IntroScreen, InstructionCoprocessor, SkillTreeScreen, VeriSimModality, BalanceAnalyser, LocationData, AmbushObjective, HighwayHits. ~6,425 parity cases. The 23 individual vm/lib instruction files are confirmed covered by the migrated Vm* category brains; shared/src is a duplicate tree of src/shared (already migrated). Several new brains are residual sub-brains earlier clusters scoped out (screen score/grade kernels, opcode taxonomy, VeriSim classifications). Full per-file disposition (148 files: 12 new / 45 already / 91 no-brain) recorded in EVIDENCE-C14-sense-sweep.adoc. Brain count 79 -> 91. https://claude.ai/code/session_01WoKhFQePiRsAj7aqnxbG8s
1 parent 7f304b4 commit c4d0b71

25 files changed

Lines changed: 2182 additions & 0 deletions
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
// SPDX-FileCopyrightText: 2025-2026 hyperpolymath
3+
//
4+
// AmbushObjective -- the assassin-survival objective co-processor, the pure
5+
// integer core extracted from idaptik src/app/screens/training/AssassinTraining.res
6+
// (the onUpdate ambush-counter logic, res:144-161). Per the DESIGN-VISION
7+
// ("AffineScript is the brain, JS/Pixi the senses; only primitives cross the wasm
8+
// boundary"), the host keeps the guard array, the pulsing warning Text, the HUD
9+
// string formatting and the victory-delay float timer; AffineScript owns only the
10+
// integer objective accounting over the assassin's failed-ambush count.
11+
//
12+
//## Why this split, not a port of AssassinTraining.res
13+
// AssassinTraining.res entangles (a) the Text pulse `0.5 +. 0.5 *. sin(t*3)` and
14+
// every container/position float (res:140-141), (b) the victory-delay countdown
15+
// `victoryDelay -. dt` (res:164-169), (c) the HUD string interpolation
16+
// "Ambushes survived: N / 3" (res:150,160), and (d) the integer objective decision:
17+
// sum the per-guard `getAssassinKillAttempts` into `attempts`, declare victory once
18+
// `attempts >= 3` (res:152). Only (d) is an integer decision; (a)-(c) are float
19+
// physics, async timing and string rendering that stay host-side. This file is the
20+
// verified core of that one decision, plus the two display read-outs the HUD needs
21+
// (clamped progress, remaining-to-go) so the host never re-derives the 0..3 band.
22+
//
23+
//## Objective encoding (the header contract for the JS host)
24+
// The assassin demo is won by forcing THREE failed ambushes. `attempts` is the host's
25+
// summed `getAssassinKillAttempts` over the guard array (a non-negative i32 in normal
26+
// play). The target is the literal 3 from res:152 (`attempts >= 3`). Boundary is
27+
// GREATER-OR-EQUAL, faithful to the .res `>=`, so attempts == 3 already wins and
28+
// attempts == 2 does not. All read-outs are total over every i32 input: a negative
29+
// host count (malformed) reads as zero progress / full remaining / not-met, never an
30+
// out-of-band code, so a stray encoding cannot spuriously complete the objective.
31+
32+
// The number of failed ambushes that complete the objective (AssassinTraining
33+
// res:152 -- the literal 3 in `attempts >= 3`). The single source of truth for the
34+
// band width; every read-out below is stated against it.
35+
pub fn ambush_target() -> Int { 3 }
36+
37+
//## is_objective_met -- whether the survive-the-assassin objective is complete.
38+
// The integer core of AssassinTraining res:152 (`attempts >= 3`). Boundary is
39+
// GREATER-OR-EQUAL, faithful to the .res `>=`. Returns 1 = met (fire the victory
40+
// transition), 0 = not yet. Total over every i32: a negative count is below 3 -> 0.
41+
pub fn is_objective_met(attempts: Int) -> Int {
42+
if attempts >= 3 { 1 } else { 0 }
43+
}
44+
45+
//## ambushes_survived -- the clamped 0..3 progress for the HUD read-out.
46+
// The display value behind "Ambushes survived: N / 3" (res:150,160). The raw host
47+
// count is clamped onto the closed 0..3 band so the HUD never shows a negative or an
48+
// over-count: a malformed negative reads 0, anything at or past the target reads 3
49+
// (the objective is complete, no further progress is meaningful). The clamp is to an
50+
// IN-BAND value (0 or 3), never an out-of-band sentinel, so assail stays clean and
51+
// no in-band code is shadowed.
52+
pub fn ambushes_survived(attempts: Int) -> Int {
53+
if attempts < 0 { return 0; }
54+
if attempts > 3 { return 3; }
55+
attempts
56+
}
57+
58+
//## ambushes_remaining -- failed ambushes still needed to win.
59+
// The complement of progress: how many more failed ambushes the assassin must be
60+
// forced into. Defined as 3 minus the clamped progress, so it is total and never
61+
// negative: at or past the target it is 0 (objective met), at a malformed negative
62+
// count it is the full 3. Mirrors the HUD's "/ 3" denominator semantics exactly.
63+
pub fn ambushes_remaining(attempts: Int) -> Int {
64+
if attempts < 0 { return 3; }
65+
if attempts >= 3 { return 0; }
66+
3 - attempts
67+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// hypatia: allow cicd_rules/javascript_detected -- Deno trial component for nextgen-evangelist; production target is Rust/AffineScript (see proposals/nextgen-evangelist/README.adoc)
3+
//
4+
// affine-parity config for AmbushObjective.affine (idaptik AssassinTraining
5+
// survival-objective kernel; scalar i32 ABI). Each oracle re-derives the rule
6+
// straight from AssassinTraining.res INDEPENDENTLY of the .affine, so a codegen
7+
// regression surfaces as a differential mismatch.
8+
//
9+
// Oracle re-derivation (rule <- source site), independent of the .affine:
10+
// ambush_target() = 3 (res:152, the literal 3)
11+
// is_objective_met(a) = a >= 3 ? 1 : 0 (res:152, `attempts >= 3`)
12+
// ambushes_survived(a) = clamp a onto 0..3 (HUD "N / 3", res:150,160)
13+
// ambushes_remaining(a) = a<0 ? 3 : a>=3 ? 0 : 3-a (complement of progress)
14+
15+
// GREATER-OR-EQUAL boundary, faithful to the .res `>=` at res:152.
16+
const isObjectiveMet = (a) => (a >= 3 ? 1 : 0);
17+
// Clamp the host count onto the closed 0..3 band (negatives -> 0, over -> 3).
18+
const ambushesSurvived = (a) => (a < 0 ? 0 : a > 3 ? 3 : a);
19+
// Complement of the clamped progress against the target 3; total, never negative.
20+
const ambushesRemaining = (a) => (a < 0 ? 3 : a >= 3 ? 0 : 3 - a);
21+
22+
// Sweep the host count across the band edges and just-inside/just-outside each cut:
23+
// the malformed negatives, 0, the 1/2/3 progression (2 = not-yet, 3 = exact win),
24+
// and over-counts (4..6) plus a large overflow so every clamp branch fires.
25+
const ATTEMPTS = [-5, -1, 0, 1, 2, 3, 4, 5, 6, 100];
26+
27+
export default {
28+
affine: "AmbushObjective.affine",
29+
cases: [
30+
{ name: "ambush_target()", export: "ambush_target", args: [], oracle: () => 3 },
31+
{
32+
name: "is_objective_met over [-5..6]",
33+
export: "is_objective_met",
34+
args: [{ values: ATTEMPTS }],
35+
oracle: isObjectiveMet,
36+
},
37+
{
38+
name: "ambushes_survived over [-5..6]",
39+
export: "ambushes_survived",
40+
args: [{ values: ATTEMPTS }],
41+
oracle: ambushesSurvived,
42+
},
43+
{
44+
name: "ambushes_remaining over [-5..6]",
45+
export: "ambushes_remaining",
46+
args: [{ values: ATTEMPTS }],
47+
oracle: ambushesRemaining,
48+
},
49+
],
50+
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
// SPDX-FileCopyrightText: 2025-2026 hyperpolymath
3+
//
4+
// BalanceAnalyser -- the pure-integer impact-classification brain extracted from
5+
// src/app/screens/BalanceAnalyserModel.res (the numeric core its
6+
// BalanceAnalyserCoprocessor.res binding routes to balanceanalyser.wasm). The
7+
// model is overwhelmingly senses: it PARSES the balance-report JSON (every
8+
// string key, every Float field), formats recommendation STRINGS, and builds
9+
// the PixiJS display lines. Per the DESIGN-VISION ("AffineScript is the brain,
10+
// JS/Pixi the senses; only primitives cross the wasm boundary"), all of that
11+
// -- the JSON, the version/parameter/reason strings, the win-rate and
12+
// difficulty FLOATS, the per-impact colour ints -- stays host-side.
13+
//
14+
// RE-DECOMPOSITION. The separable integer decisions are the impact-level
15+
// comparison (getByImpact's filter) and the critical-issue test
16+
// (hasCriticalIssues). The ReScript switches the `impactLevel` variant
17+
// (Minor | Moderate | Major) to a 0..2 ordinal (impactValue) and then compares
18+
// ordinals; we re-decompose the variant as that 0..2 ordinal directly -- the
19+
// order the .res `impactValue` switch already imposes (Minor < Moderate <
20+
// Major). The variant IS the ordinal; no impact strings cross. The float
21+
// rounding helpers (round1 / pct100 / winRatePct) are senses and stay host-side.
22+
//
23+
//## Impact-level encoding (the header contract for the JS host)
24+
// ord level (mirrors BalanceAnalyserCoprocessor IMPACT.{MINOR,MODERATE,MAJOR})
25+
// 0 Minor
26+
// 1 Moderate
27+
// 2 Major
28+
// An ordinal outside 0..2 is not a defined impact: impact_valid reports 0 and
29+
// impact_value returns the out-of-band sentinel -1 (never an in-band ordinal),
30+
// so an off-domain input cannot pass as a real impact. -1 is a sentinel, not a
31+
// clamp (assail stays clean).
32+
//
33+
//## Filter / threshold contract (the decisions getByImpact + hasCriticalIssues make)
34+
// passes_impact(item_ord, min_ord) -> 1 when the item's impact meets or
35+
// exceeds the minimum (item_ord >= min_ord), else 0. This is getByImpact's
36+
// `impactValue(item.impact) >= impactValue(minImpact)` kept-row test.
37+
// has_critical_issues(major_count) -> 1 when the report has any major issue
38+
// (major_count > 0), else 0. This is hasCriticalIssues' `majorIssues > 0`.
39+
// Both treat their inputs as plain counts/ordinals; the host owns the
40+
// recommendation array, the strings, and the float fields.
41+
//
42+
// PURE: integers only, no floats, no strings, no arrays, no effects, no I/O.
43+
44+
//## The number of impact levels (Minor, Moderate, Major).
45+
pub fn impact_level_count() -> Int { 3 }
46+
47+
//## Whether a host integer names a defined impact level. 1 = valid, 0 = out of band.
48+
pub fn impact_valid(ord: Int) -> Int {
49+
if ord < 0 { 0 } else { if ord > 2 { 0 } else { 1 } }
50+
}
51+
52+
//## Canonicalise an impact ordinal: identity on a valid 0..2 ordinal, the
53+
// out-of-band sentinel -1 otherwise. Mirrors impactValue's role as the
54+
// variant's ordinal; -1 is never an in-band ordinal so out-of-band input can
55+
// never be confused with a real impact (sentinel, not a clamp).
56+
pub fn impact_value(ord: Int) -> Int {
57+
if impact_valid(ord) == 1 { ord } else { -1 }
58+
}
59+
60+
//## Whether an item's impact meets or exceeds the minimum: 1 keep, 0 drop.
61+
// getByImpact's `impactValue(item) >= impactValue(min)` row filter.
62+
pub fn passes_impact(item_ord: Int, min_ord: Int) -> Int {
63+
if item_ord >= min_ord { 1 } else { 0 }
64+
}
65+
66+
//## Whether the report has any major issue (majorIssues > 0): 1 yes, 0 no.
67+
// hasCriticalIssues' test. A non-positive count is never positive -> 0.
68+
pub fn has_critical_issues(major_count: Int) -> Int {
69+
if major_count > 0 { 1 } else { 0 }
70+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// hypatia: allow cicd_rules/javascript_detected -- Deno trial component for nextgen-evangelist; production target is Rust/AffineScript (see proposals/nextgen-evangelist/README.adoc)
3+
//
4+
// affine-parity config for BalanceAnalyser.affine (idaptik impact-classification
5+
// brain; scalar i32 ABI). The oracle re-derives each function from the ORIGINAL
6+
// BalanceAnalyserModel.res semantics (NOT from the .affine) so a codegen
7+
// regression surfaces as a differential mismatch.
8+
//
9+
// From BalanceAnalyserModel.res:
10+
// getByImpact (lines 348-356): impactValue Minor=>0 Moderate=>1 Major=>2;
11+
// keep iff impactValue(item.impact) >= impactValue(minImpact)
12+
// hasCriticalIssues (lines 435-439): r.majorIssues > 0
13+
// Impact variant Minor/Moderate/Major maps to ordinals 0/1/2.
14+
15+
// Independent re-derivation of the impact validity from the .res ordinal domain.
16+
const impactValid = (o) => (o >= 0 && o <= 2 ? 1 : 0);
17+
18+
// Independent re-derivation of impactValue (the variant's ordinal) from the .res.
19+
const impactValue = (o) => (impactValid(o) ? o : -1);
20+
21+
// Independent re-derivation of getByImpact's row filter from the .res.
22+
const passesImpact = (item, min) => (item >= min ? 1 : 0);
23+
24+
// Independent re-derivation of hasCriticalIssues from the .res.
25+
const hasCritical = (n) => (n > 0 ? 1 : 0);
26+
27+
export default {
28+
affine: "BalanceAnalyser.affine",
29+
cases: [
30+
{
31+
name: "impact_level_count()",
32+
export: "impact_level_count",
33+
args: [],
34+
oracle: () => 3,
35+
},
36+
{
37+
name: "impact_valid over ord[-3..6]",
38+
export: "impact_valid",
39+
args: [[-3, 6]],
40+
oracle: (o) => impactValid(o),
41+
},
42+
{
43+
name: "impact_value over ord[-3..6]",
44+
export: "impact_value",
45+
args: [[-3, 6]],
46+
oracle: (o) => impactValue(o),
47+
},
48+
{
49+
name: "passes_impact over (item[-1..3], min[-1..3])",
50+
export: "passes_impact",
51+
args: [[-1, 3], [-1, 3]],
52+
oracle: (item, min) => passesImpact(item, min),
53+
},
54+
{
55+
name: "passes_impact exact impact pairs 0..2 x 0..2",
56+
export: "passes_impact",
57+
args: [{ values: [0, 1, 2] }, { values: [0, 1, 2] }],
58+
oracle: (item, min) => passesImpact(item, min),
59+
},
60+
{
61+
name: "has_critical_issues over major_count[-2..10]",
62+
export: "has_critical_issues",
63+
args: [[-2, 10]],
64+
oracle: (n) => hasCritical(n),
65+
},
66+
],
67+
};
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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 C14 (sense-heavy directory sweep) — migration evidence (2026-06-14)
4+
:toc: macro
5+
6+
[IMPORTANT]
7+
====
8+
*Verdict: 12 new integer brains extracted; the remainder are host-side senses,
9+
FFI binding shims, or already-migrated.*
10+
11+
This cluster completes the exhaustive corpus sweep begun in C13, covering the
12+
six remaining sense-heavy directory groups: `src/app/screens` (top-level),
13+
`src/app/screens/{training,locations}`, `vm/lib/ocaml`, `src/engine` +
14+
`shared/src`, `src/app/{verisimdb,proven}`, and the long-tail grab-bag
15+
(`narrative`, `tools`, `tea`, `pickups`, `pixi`, `burble`, `vm`, `bindings`,
16+
`idaptik-ums/src`, loose `src`/`src/app`). 148 `.res` files.
17+
18+
All 12 new brains were *independently re-verified* by the parent session
19+
(G1 compile, G2 differential parity all-pass vs an independent JS oracle,
20+
G4 assail zero high findings). Total brain count: 79 -> 91.
21+
====
22+
23+
== Disposition summary
24+
25+
[cols="3,1,1,1,1",options="header"]
26+
|===
27+
| Directory group | Files | New | Already | No-brain
28+
| `src/app/screens` (top-level) | 25 | 5 | 1 | 19
29+
| `screens/training`+`locations` | 23 | 2 | 0 | 21
30+
| `vm/lib/ocaml` | 34 | 1 | 27 | 6
31+
| `src/engine`+`shared/src` | 27 | 0 | 17 | 10
32+
| `src/app/verisimdb`+`proven` | 16 | 3 | 0 | 13
33+
| long-tail grab-bag | 23 | 1 | 0 | 22
34+
| *Total* | *148* | *12* | *45* | *91*
35+
|===
36+
37+
== New brains (12) — all G1/G2/G4 green
38+
39+
[cols="2,1,4",options="header"]
40+
|===
41+
| Brain | G2 | Extracted logic
42+
| `VictoryScreen` | 2430/2430 | performance-grade: victory_score (100 base, alert/time/command penalties, covert/crit bonuses) + S/A/B/C/D banding (4..0)
43+
| `PlayTimeSplit` | 2040/2040 | total_minutes -> hours(/60)/minutes(%60)/has_hours (harness-Int form of the float-seconds render kernel)
44+
| `VeriSimError` | 859/859 | HTTP status -> 20-category ordinal + is_retryable(category) + is_status_retryable
45+
| `MissionBriefing` | 285/285 | difficulty/guard-count/threshold, objective-state predicates, completion + progress%, par-time & S-grade alert budget
46+
| `IntroScreen` | 254/254 | tutorial paginator state machine: next_page (bounded), is_last_page, 1-based page_label
47+
| `InstructionCoprocessor`| 175/175 | opcode arity-and-reversibility taxonomy: count/valid/arity/tier/is_self_inverse/invert/invert_round_trip
48+
| `SkillTreeScreen` | 114/114 | XP-threshold classification: rank 0..4 -> threshold, is_maxed, xp_fill_permille (0..1000)
49+
| `VeriSimModality` | 80/80 | 8-member modality band (0..7) + 5-member drift-level ordinal (0..4) + drift_is_actionable/critical
50+
| `BalanceAnalyser` | 68/68 | impact-level classification (Minor/Moderate/Major -> 0..2) + passes_impact + has_critical_issues
51+
| `LocationData` | 61/61 | world-map attributes: location_count, environment_kind (idx -> ordinal), device_count
52+
| `AmbushObjective` | 31/31 | 3-failed-ambush survival objective: is_objective_met (attempts>=3), clamped progress, remaining
53+
| `HighwayHits` | 28/28 | hit-budget accounting (max 3, game_over, clamped remaining) + per-lane traffic-direction parity (+/-1)
54+
|===
55+
56+
== NO_NEW_BRAINS (91) and ALREADY (45)
57+
58+
* *Screens / locations (~40):* PixiJS lifecycle orchestrators, navigation, static
59+
LocationData-record screens, i18n string menus, float layout/camera math.
60+
* *FFI binding shims + render-glue (~30):* `*Coprocessor.res`, Pixi/Motion/Sound
61+
bindings (`Pixi.res` 321 externals), render-decision bridges (float ABI).
62+
* *Float-domain cores (~10):* SafeFloat/SafeAngle (NaN-guarded f64), Resize,
63+
glitch-FX, training dt-physics state machines.
64+
* *String/JSON/HTTP cores (~15):* VeriSim HTTP/JSON FFI bridges, PhoenixSocket,
65+
SafeJson, lore/DataFiles, terminal/editor IPC glue.
66+
* *Orchestration / host state (~10):* Main, GameLoop, GetEngine, VM orchestrator,
67+
SubroutineRegistry, registries and circular-dep-breaker refs.
68+
* *VM instructions (23, ALREADY):* `Add.res`..`Xor.res` are each covered by the
69+
migrated `Vm{Arith,Bitwise,MulDiv,Stack,Memory,Port,Control,Ancilla,Instruction}`
70+
category brains (confirmed by reading each category `.affine`).
71+
* *`shared/src` (17, ALREADY):* a duplicate tree of `src/shared`; every brain was
72+
already extracted (Coprocessor, Kernel_*, ResourceAccounting, DeviceType, ...).
73+
74+
== Residual gap (closed in a follow-up)
75+
76+
The screens sweep covered the top level and `training`/`locations`; the small
77+
`screens/main` (3), `screens/hub` (1), `vm/wasm/src` (2), and `devices/{types,
78+
common}` (2) leaves are swept separately. (`docs/.../templates/*.res` is a
79+
template, not source.)
80+
81+
== Method note
82+
83+
Brains stay `Int(...)->Int` (the parity harness passes integer arguments only);
84+
floats are carried as x1000 milli-units / permille where integer-exact, else
85+
host-side. Two recurring AffineScript authoring landmines were re-confirmed and
86+
worked around: `total` is a *reserved word* (use `acc`/`req_total`), and the flat
87+
early-`return N;` idiom (not deeply nested deferred-brace `if/else`) is the
88+
reliable many-branch style that stays G4-clean. Several brains are residual
89+
sub-brains earlier clusters scoped out (the Coprocessor/Kernel/Resource set,
90+
ThreatClass, the screen score/grade kernels), recovered here as separable
91+
pure-integer kernels matching the existing `*Coprocessor.res` host bridges.

0 commit comments

Comments
 (0)