anima-python 0.13.1__py3-none-any.whl

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.
@@ -0,0 +1,1350 @@
1
+ #!/usr/bin/env python3
2
+ # evaluate.py — anima MEASUREMENT single entry (cli/evaluate.hexa's py twin).
3
+ #
4
+ # WHY THIS FILE (single-entry measurement · a_engine_native_learning): anima's two
5
+ # installed verbs are SYMMETRIC across two files — cli/train.{hexa,py} = LEARNING,
6
+ # cli/evaluate.{hexa,py} = MEASUREMENT. `anima evaluate <ckpt>` scores the full ρ-AXON
7
+ # reach battery (Ψ-SOMA reach layer · owner redesign of the old G-ladder · cli/rho_axon.py;
8
+ # the frozen bars this driver runs today = the former G0-G6) with the engine's OWN ops —
9
+ # the reach-scoring system is folded DIRECTLY into
10
+ # this measurement single-entry (the former separate core/g_gates.py module was absorbed
11
+ # here — measurement = evaluate.{hexa,py} ONE FILE). Decode enters via the generator L3
12
+ # mouth (gen_auto_ideate → clm/bytegpt decode), so the py evaluate is byte-identical to
13
+ # the hexa `anima evaluate`. No new metric is invented here (logic byte-identical to the
14
+ # absorbed g_gates module — only the file home changed).
15
+ #
16
+ # This py evaluate is torch-free and gauge-free — the scoring is the numpy `math.log`
17
+ # mirror, so `anima evaluate` stays a clean engine-native measurement surface (the gate
18
+ # enforcer's torch/gauge grep over this file must come back empty).
19
+ #
20
+ # USAGE (installed `anima` PATH command — NOT `hexa run`):
21
+ # python3 cli/evaluate.py — usage (no args)
22
+ # python3 cli/evaluate.py <ckpt> [--corpus <p>...] [--gen N]
23
+ # — ρ-AXON reach battery (former G0-G6)
24
+ #
25
+ # 2-PRODUCTION (a_engine_native_learning): byte-parity twin = cli/evaluate.hexa. Both
26
+ # define the SAME eval_reach_all driver in-file.
27
+
28
+ import os
29
+ import sys
30
+ import math
31
+ import json
32
+
33
+ _HERE = os.path.dirname(os.path.abspath(__file__))
34
+ _REPO = os.path.dirname(_HERE)
35
+ # core/ is the engine package; the decode mouths (clm_decode, bytegpt_decode) + the G6
36
+ # ideation ops live there. Add core/ to the path so they resolve.
37
+ sys.path.insert(0, os.path.join(_REPO, "core"))
38
+
39
+ import decode as clm # unified core decoder (conv+byte mouths), KV-cache fast path
40
+ import decode as bg # same module; both aliases resolve the union public API
41
+ from rho_fan import (
42
+ _rho_fan_concepts, _rho_fan_words, _rho_fan_dict_load, _rho_fan_known_word_ratio,
43
+ _rho_fan_is_falsifiable, _rho_fan_jaccard, rho_fan_build_frames, rho_fan_frame_guard,
44
+ rho_fan_detector_calibration,
45
+ # H_9212 ③ per-cell dispatch: 4 register cells + ko codepoint-aware tokenizer + kwr_ko gate
46
+ _rho_fan_cells, _rho_fan_cell_lang, _rho_fan_words_uni, _rho_fan_ko_known_word_ratio,
47
+ KWR_KO_GATE,
48
+ )
49
+
50
+ # H_9200 — process-global: render the ρ-AXON reach panel (cli/rho_axon.py) instead of the
51
+ # G0-G6 battery when `anima-python evaluate --rho-axon` is passed (set in main()).
52
+ _RHO_AXON = False
53
+
54
+
55
+ # ════════════════════════════════════════════════════════════════════════
56
+ # ρ-AXON REACH SCORING (absorbed from the former core/g_gates.py module —
57
+ # measurement = this single file). Ported 1:1 from the hexa SSOT; decode enters
58
+ # via the py CLMConvMoE / ByteGPT mouth (clm_decode / bytegpt_decode), hoisting the
59
+ # weight load ONCE (== gen_clm_ideate_W in the hexa engine). torch-free numpy mirror.
60
+ #
61
+ # ρ-AXON is the current-standard reach layer (cli/rho_axon.py · design SSOT
62
+ # state/rho_axon_measurement/) — an owner redesign OVER these frozen bars. The
63
+ # detector identifiers below (eval_rho_form…g6) are the load-bearing frozen substrate
64
+ # ρ-AXON reuses; the G→ρ mapping (axis names shown first-time here) is:
65
+ # ρ·form ← G0 COHERENCE kwr>=0.50 on >=4/5 single-concept gens
66
+ # ρ·weave ← G1 RECOMBINATION some k: composed_distinct>=2 AND >max_single AND coherent (the central WALL)
67
+ # ρ·leap ← G2 NOVELTY >=3 distinct coherent corpus-absent n-grams AND control=0
68
+ # ρ·self ← G3 PHILOSOPHY Psi-fixed-point self-identity continuity (architecture read)
69
+ # ρ·tether ← G5 NON-FAB L1 fab-rate<=0.30 AND L2 abstain (L2 = engine §ImmuneMemory)
70
+ # ρ·fan ← G6 IDEATION ★ >=5 distinct (pairwise Jaccard<0.5) AND >=1 falsifiable
71
+ # ρ·store ← (NEW · held-out association retrieval · PENDING, no old-G)
72
+ # REACH-CLOSED a7b_pass = ρ·form ∧ ρ·weave ∧ ρ·leap (frozen bars = G0 ∧ G1 ∧ G2)
73
+ # FROZEN-FIRST bars (7B_PASS_CONDITIONS.md VERBATIM, p7 — NO tune-to-green).
74
+ #
75
+ # SCOPE NOTE: G3 (self-identity, ckpt-independent) is ported here. G5-L2 (live
76
+ # §ImmuneMemory / VAdaptField abstain) is a large engine subsystem NOT yet ported
77
+ # to py — G5 reports L1 + marks L2 pending. Neither affects the a7b_pass closure.
78
+ # ════════════════════════════════════════════════════════════════════════
79
+
80
+
81
+ def _default_gen():
82
+ return 40
83
+
84
+
85
+ # ════════════════════════════════════════════════════════════════════════
86
+ # decode entry — gen_auto_ideate(ckpt) MOUTH-SNIFF dispatch (generator L3).
87
+ #
88
+ # Mirrors the hexa gen_auto_ideate -> generator gen_auto_backend mouth dispatch
89
+ # (a_core_engine_map): sniff the ckpt header — CLM\x01 magic => ConvMoE .clm mouth
90
+ # (clm_decode), else a sane 5xu32 ByteGPT header => transformer .bin mouth
91
+ # (bytegpt_decode). Both hoist the weight load ONCE and ideate via the byte-parity-
92
+ # proven seeded top-k sampler. The clm path is unchanged.
93
+ # ════════════════════════════════════════════════════════════════════════
94
+
95
+ class _Mouth:
96
+ _n_decode = 0
97
+
98
+ def __init__(self, ckpt):
99
+ if bg.bg_is_bytegpt(ckpt):
100
+ self.kind = "bytegpt"
101
+ self.W = bg.bg_load(ckpt)
102
+ if not self.W.get("ok"):
103
+ raise RuntimeError("ckpt not decodable (bytegpt): " + ckpt)
104
+ elif clm.clm_decodable(ckpt):
105
+ self.kind = "clm"
106
+ self.W = clm.clm_load_weights(ckpt)
107
+ if not self.W.get("ok"):
108
+ raise RuntimeError("ckpt not decodable (clm): " + ckpt)
109
+ else:
110
+ raise RuntimeError("ckpt not decodable (unknown mouth): " + ckpt)
111
+
112
+ def ideate(self, seed, gen, top_k, temp, seed_rng):
113
+ # keep-alive heartbeat: the hexa launcher captures this decode's stdout via
114
+ # exec(); a long idle (a 303M numpy decode emits nothing until it returns)
115
+ # drops that captured pipe → BrokenPipe mid-battery (303M gen80 died at ~8min;
116
+ # the gen8 smoke was fast enough to finish first). One line per decode keeps
117
+ # the pipe live across the full G0-G6 battery. Heartbeat only — no scoring effect.
118
+ _Mouth._n_decode += 1
119
+ print(" [decode #" + str(_Mouth._n_decode) + "] " + self.kind
120
+ + " gen=" + str(gen) + " seed_rng=" + str(seed_rng), flush=True)
121
+ if self.kind == "bytegpt":
122
+ # seed string -> byte ids inside bytegpt_decode (_seed_to_ids); the
123
+ # ByteGPT window grows up to block natively (no fixed-T right-align).
124
+ return bg.bytegpt_decode_topk_sampled_W(
125
+ self.W, seed, gen, top_k, temp, seed_rng)["text"]
126
+ return clm.clm_decode_topk_sampled_W(
127
+ self.W, seed, gen, top_k, temp, seed_rng)["text"]
128
+
129
+
130
+ # ════════════════════════════════════════════════════════════════════════
131
+ # G0 — COHERENCE
132
+ # ════════════════════════════════════════════════════════════════════════
133
+
134
+ def eval_rho_form(mouth, gen, known):
135
+ cz = _rho_fan_concepts()
136
+ ratios = []; texts = []; n_coherent = 0
137
+ for i in range(len(cz)):
138
+ seed = cz[i] + ": "
139
+ o = mouth.ideate(seed, gen, 40, 0.7, 7 + i)
140
+ kwr = _rho_fan_known_word_ratio(o, known)
141
+ ratios.append(kwr); texts.append(o)
142
+ if kwr >= 0.5:
143
+ n_coherent += 1
144
+ return {"pass": n_coherent >= 4, "n_coherent": n_coherent,
145
+ "ratios": ratios, "texts": texts}
146
+
147
+
148
+ # ════════════════════════════════════════════════════════════════════════
149
+ # G1 — RECOMBINATION (H_1129)
150
+ # ════════════════════════════════════════════════════════════════════════
151
+
152
+ def _g_concept_keywords():
153
+ return [["consciousness", "cells", "mind", "aware"],
154
+ ["tension", "ripple", "distant", "between"],
155
+ ["memory", "meaning", "compose", "new"],
156
+ ["silence", "information", "quiet", "carries"],
157
+ ["dream", "engine", "alone", "sleep"]]
158
+
159
+
160
+ def _g_coverage(text):
161
+ kwsets = _g_concept_keywords()
162
+ wm = set(_rho_fan_words(text))
163
+ covered = 0
164
+ for kw in kwsets:
165
+ if any(k in wm for k in kw):
166
+ covered += 1
167
+ return covered
168
+
169
+
170
+ def eval_rho_weave(mouth, gen, known):
171
+ cz = _rho_fan_concepts()
172
+ n = len(cz)
173
+ g_single = gen if (gen > 0 and gen < 80) else 80
174
+ g_comp = gen if (gen > 0 and gen < 120) else 120
175
+ max_single = 0
176
+ for s in range(n):
177
+ seed = cz[s] + ". "
178
+ o = mouth.ideate(seed, g_single, 40, 0.7, 7 + s)
179
+ cov = _g_coverage(o)
180
+ if cov > max_single:
181
+ max_single = cov
182
+ ks = []; passed = False; best_k = 0; best_distinct = 0
183
+ for k in range(2, n + 1):
184
+ seed = ""
185
+ for c in range(k):
186
+ if c > 0:
187
+ seed += ". "
188
+ seed += cz[c]
189
+ seed += ". "
190
+ o = mouth.ideate(seed, g_comp, 40, 0.7, 7)
191
+ cov = _g_coverage(o)
192
+ kwr = _rho_fan_known_word_ratio(o, known)
193
+ coherent = kwr >= 0.5
194
+ clears = cov >= 2 and cov > max_single and coherent
195
+ ks.append({"k": k, "distinct": cov, "kwr": kwr, "coherent": coherent, "clears": clears})
196
+ if clears:
197
+ passed = True
198
+ if cov > best_distinct:
199
+ best_distinct = cov; best_k = k
200
+ return {"pass": passed, "max_single": max_single, "best_k": best_k,
201
+ "best_distinct": best_distinct, "ks": ks}
202
+
203
+
204
+ # ════════════════════════════════════════════════════════════════════════
205
+ # system-G1 — RECOMBINATION RELOCATION (card H_9035, Direction A)
206
+ # ════════════════════════════════════════════════════════════════════════
207
+ #
208
+ # Relocates recombination OUT of the mouth-only eval_rho_weave path INTO the pipe:
209
+ # held-out DISTANT pair (A,B)
210
+ # → Stage M frozen mouth ideate(A), ideate(B) (G0 fluency)
211
+ # → Stage K kosmos_merge: recursive labeled-parent bind (A,B as children)
212
+ # → Stage B brain realizes/releases the joint utterance C
213
+ # → score bind-RECOVERABILITY on the SURFACED C only (store-id HARD-BLOCKED)
214
+ # + SCRAMBLE ablation.
215
+ #
216
+ # This is the py-2-production (session-eval-py-only) engine-native version of the
217
+ # round-1 numpy harness (state/system_g1_relocate_kosmos_merge/system_g1_harness.py)
218
+ # — same FROZEN bar (FREEZE.txt), but Stage M/B run through the REAL _Mouth (303M
219
+ # .clm/.bin) instead of a toy. The kosmos_merge step mirrors core/kosmos_io.hexa
220
+ # kosmos_merge (children preserved, mean tension, lane="recomb" DISJOINT from
221
+ # emit-drive {0,4}). RECOVERY reads ONLY C — the store's parent-ids are withheld,
222
+ # so a lookup earns ZERO (H_1874). Honest expectation for the real mouth = the
223
+ # MOUTHFLOOR floor (cannot surface both concepts in one coherent joint utterance).
224
+
225
+ # ── FROZEN bar (FREEZE.txt — do NOT move post-hoc) ──────────────────────────
226
+ _SG1_SEEDS = [7, 42, 4302]
227
+ _SG1_M = 24
228
+ _SG1_N_DISTRACT = 8
229
+ _SG1_COV_BAR = _SG1_M // 2 # 12
230
+ _SG1_REC_BAR = _SG1_M // 2 # 12
231
+ _SG1_LEAK_BAR = (_SG1_N_DISTRACT - 2) / _SG1_N_DISTRACT # 0.75
232
+ _SG1_SCR_DROP = _SG1_M // 2 # 12
233
+
234
+ # distant concepts → keyword sets (disjoint vocab ⇒ H_1599 data-presence control).
235
+ _SG1_CONCEPTS = {
236
+ "ocean": ["tide", "salt", "wave", "deep", "current"],
237
+ "forest": ["moss", "branch", "fern", "canopy", "root"],
238
+ "engine": ["piston", "fuel", "gear", "torque", "cylinder"],
239
+ "music": ["chord", "tempo", "melody", "rhythm", "harmony"],
240
+ "market": ["price", "trade", "stock", "ledger", "buyer"],
241
+ "medicine": ["dose", "fever", "immune", "remedy", "cure"],
242
+ "desert": ["dune", "cactus", "mirage", "arid", "sand"],
243
+ "galaxy": ["orbit", "nebula", "comet", "stellar", "star"],
244
+ "kitchen": ["knife", "simmer", "flour", "roast", "spice"],
245
+ "law": ["statute", "verdict", "counsel", "appeal", "court"],
246
+ "glacier": ["crevasse", "moraine", "frost", "calve", "ice"],
247
+ "circuit": ["resistor", "voltage", "solder", "diode", "wire"],
248
+ }
249
+
250
+
251
+ def _sg1_bigrams(tokens):
252
+ from collections import Counter
253
+ return Counter(zip(tokens, tokens[1:]))
254
+
255
+
256
+ def _sg1_cos(u, v):
257
+ dot = sum(u[k] * v.get(k, 0) for k in u)
258
+ nu = sum(x * x for x in u.values()) ** 0.5
259
+ nv = sum(x * x for x in v.values()) ** 0.5
260
+ return dot / (nu * nv + 1e-9)
261
+
262
+
263
+ def _sg1_coverage(text, a, b):
264
+ wm = set(_rho_fan_words(text))
265
+ cov = 0
266
+ for cpt in (a, b):
267
+ if any(k in wm for k in _SG1_CONCEPTS[cpt]):
268
+ cov += 1
269
+ return cov
270
+
271
+
272
+ # kosmos_merge mirror (core/kosmos_io.hexa kosmos_merge): recursive labeled-parent
273
+ # bind — anchor_c keeps A,B as children; the ids are HARD-BLOCKED from RECOVERY.
274
+ def _sg1_kosmos_merge(frag_a, frag_b, ta, tb):
275
+ a = {"text": frag_a, "lane": "recomb", "tension_5ch": ta}
276
+ b = {"text": frag_b, "lane": "recomb", "tension_5ch": tb}
277
+ mean5 = [(ta[i] + tb[i]) / 2.0 for i in range(5)]
278
+ c = {"text": None, "lane": "recomb", "tension_5ch": mean5, "children": (a, b)}
279
+ return c
280
+
281
+
282
+ # INDEPENDENT recoverer R — reads ONLY C's surfaced tokens; parent-ids withheld.
283
+ # top-2 by bigram cosine over the N=8 pool; a candidate counts only with cos>0.
284
+ def _sg1_recover(C_text, pool, scramble, seed):
285
+ import random
286
+ toks = _rho_fan_words(C_text)
287
+ if scramble:
288
+ random.Random(seed).shuffle(toks)
289
+ cvec = _sg1_bigrams(toks)
290
+ scored = sorted(
291
+ ((name, _sg1_cos(cvec, _sg1_bigrams(_SG1_CONCEPTS[name]))) for name in pool),
292
+ key=lambda kv: -kv[1],
293
+ )
294
+ top2 = set(n for (n, s) in scored[:2] if s > 1e-9)
295
+ return top2
296
+
297
+
298
+ def _sg1_pairs():
299
+ names = list(_SG1_CONCEPTS)
300
+ idx = [(0, 1), (2, 3), (4, 5), (6, 7), (8, 9), (10, 11), (0, 6), (3, 9)]
301
+ base = [(names[i], names[j]) for (i, j) in idx]
302
+ pairs = []
303
+ for s in _SG1_SEEDS:
304
+ for (a, b) in base:
305
+ pairs.append((a, b, s))
306
+ return pairs[:_SG1_M]
307
+
308
+
309
+ def _sg1_pool(a, b, seed):
310
+ import random
311
+ others = [n for n in _SG1_CONCEPTS if n not in (a, b)]
312
+ random.Random(seed).shuffle(others)
313
+ pool = [a, b] + others[: _SG1_N_DISTRACT - 2]
314
+ random.Random(seed + 999).shuffle(pool)
315
+ return pool
316
+
317
+
318
+ def eval_system_rho_weave(mouth, gen):
319
+ g_single = gen if (gen > 0 and gen < 80) else 80
320
+ g_comp = gen if (gen > 0 and gen < 120) else 120
321
+ cov_pass = rec_pass = scr_pass = 0
322
+ leak_sum = 0.0
323
+ rows = []
324
+ for (a, b, seed) in _sg1_pairs():
325
+ # Stage M — frozen single-concept fragments (the mouth stays FROZEN).
326
+ frag_a = mouth.ideate(a + ". ", g_single, 40, 0.7, 7 + seed)
327
+ frag_b = mouth.ideate(b + ". ", g_single, 40, 0.7, 11 + seed)
328
+ max_single = max(_sg1_coverage(frag_a, a, b), _sg1_coverage(frag_b, a, b))
329
+ # Stage K — kosmos_merge (store keeps A,B as children; ids withheld from R).
330
+ _comp = _sg1_kosmos_merge(frag_a, frag_b, [0.0] * 5, [0.0] * 5)
331
+ # Stage B — brain realizes/releases the joint C from the composite seed.
332
+ C_text = mouth.ideate(a + ". " + b + ". ", g_comp, 40, 0.7, seed)
333
+ cov = _sg1_coverage(C_text, a, b)
334
+ # Recovery R — reads ONLY C; parent-ids HARD-BLOCKED.
335
+ pool = _sg1_pool(a, b, seed)
336
+ rec = _sg1_recover(C_text, pool, False, seed)
337
+ scr = _sg1_recover(C_text, pool, True, seed)
338
+ both = {a, b}
339
+ nrec = len(both & rec)
340
+ nscr = len(both & scr)
341
+ if cov >= 2 and cov > max_single:
342
+ cov_pass += 1
343
+ if nrec >= 2:
344
+ rec_pass += 1
345
+ if nscr >= 2:
346
+ scr_pass += 1
347
+ leak_sum += (2 - nrec) / 2.0
348
+ rows.append({"a": a, "b": b, "cov": cov, "max_single": max_single,
349
+ "rec": nrec, "scr": nscr})
350
+ leak_rate = leak_sum / len(rows)
351
+ drop = rec_pass - scr_pass
352
+ passed = (cov_pass >= _SG1_COV_BAR and rec_pass >= _SG1_REC_BAR
353
+ and leak_rate <= _SG1_LEAK_BAR and drop >= _SG1_SCR_DROP)
354
+ return {"pass": passed, "coverage": cov_pass, "recovery": rec_pass,
355
+ "scramble_recovery": scr_pass, "scramble_drop": drop,
356
+ "leak_rate": round(leak_rate, 3), "rows": rows}
357
+
358
+
359
+ def system_g1_run(argv):
360
+ """`anima-python evaluate <ckpt> --system-g1 [--gen N]` — engine-native system-G1."""
361
+ ckpt = argv[0]
362
+ gen = evaluate_intval(argv[1:], "--gen", 0)
363
+ print("=== anima evaluate --system-g1 — RECOMBINATION RELOCATION (card H_9035) ===")
364
+ print("ckpt: " + ckpt)
365
+ print("pipe: mouth ideate(A),ideate(B) → kosmos_merge → brain realize/release → C")
366
+ print("gate: bind-RECOVERABILITY on SURFACED C (store-id HARD-BLOCKED) + SCRAMBLE")
367
+ print("bar: M=%d · N_pool=%d · COV>=%d REC>=%d LEAK<=%.2f DROP>=%d (FREEZE.txt frozen)"
368
+ % (_SG1_M, _SG1_N_DISTRACT, _SG1_COV_BAR, _SG1_REC_BAR, _SG1_LEAK_BAR, _SG1_SCR_DROP))
369
+ print("")
370
+ mouth = _Mouth(ckpt)
371
+ r = eval_system_rho_weave(mouth, gen)
372
+ print(" coverage=%d/%d (>=%d) recovery=%d/%d (>=%d) leak=%.3f (<=%.2f) drop=%d (>=%d)"
373
+ % (r["coverage"], _SG1_M, _SG1_COV_BAR, r["recovery"], _SG1_M, _SG1_REC_BAR,
374
+ r["leak_rate"], _SG1_LEAK_BAR, r["scramble_drop"], _SG1_SCR_DROP))
375
+ print("")
376
+ print("system-G1 (frame-shift confirmed = all four): " + _pf(bool(r["pass"])))
377
+ if not r["pass"]:
378
+ print(" → NOT frame-shift — recombination is REALIZATION-bound (honest c9): a")
379
+ print(" perfect discrete store cannot help if the FROZEN mouth cannot SURFACE")
380
+ print(" both concepts in one coherent joint utterance for an independent probe.")
381
+ return 0
382
+
383
+
384
+ # ════════════════════════════════════════════════════════════════════════
385
+ # G2 — NOVELTY (H_1140)
386
+ # ════════════════════════════════════════════════════════════════════════
387
+
388
+ def _g_g2_stop():
389
+ ws = ("the a an of to and in is it that this for on with as are was be by at from or not "
390
+ "but his her they we you i he she them me my your our their its do does did has have had will "
391
+ "would can could should may might must shall when where what which who whom how why all any some "
392
+ "no one two then than into out up down over under more most less about so very just only own same "
393
+ "such each few other been here there now")
394
+ return set(w.strip() for w in ws.split(" ") if w.strip())
395
+
396
+
397
+ def _g_g2_common():
398
+ ws = ("the a an of to and in is it that this for on with as are was be by at from "
399
+ "or not but his her they we you i he she them me my your our their its do does did has have "
400
+ "had will would can could should may might must shall when where what which who whom how why "
401
+ "all any some no one two three new now then than into out up down over under more most less "
402
+ "about between among through during before after above below again further once here there "
403
+ "when while because so very just only own same such each few other own being been because "
404
+ "mind aware cells consciousness tension ripple distant between memory meaning compose new "
405
+ "silence information quiet carries dream engine alone sleep thought feel idea world life "
406
+ "time word words speak think know like make made come came see seen look way thing things "
407
+ "people person human self body brain neuron signal pattern arise emerge form structure "
408
+ "combine combined together connect connection meaning sense reason cause effect result")
409
+ return set(w.strip() for w in ws.split(" ") if w.strip())
410
+
411
+
412
+ def _g_g2_kwr(text, common):
413
+ wl = _rho_fan_words(text)
414
+ n = len(wl)
415
+ if n == 0:
416
+ return 0.0
417
+ hit = sum(1 for w in wl if w in common)
418
+ return float(hit) / float(n)
419
+
420
+
421
+ def _g_content_ngrams(text, known):
422
+ wl = _rho_fan_words(text)
423
+ stop = _g_g2_stop()
424
+ n = len(wl)
425
+ out = []
426
+ for i in range(n - 1):
427
+ a = wl[i]; b = wl[i + 1]
428
+ ok_a = len(a) >= 3 and a in known
429
+ ok_b = len(b) >= 3 and b in known
430
+ if ok_a and ok_b and not (a in stop and b in stop):
431
+ out.append(a + " " + b)
432
+ for j in range(n - 2):
433
+ a = wl[j]; b = wl[j + 1]; c = wl[j + 2]
434
+ ok_a = len(a) >= 3 and a in known
435
+ ok_b = len(b) >= 3 and b in known
436
+ ok_c = len(c) >= 3 and c in known
437
+ all_stop = a in stop and b in stop and c in stop
438
+ if ok_a and ok_b and ok_c and not all_stop:
439
+ out.append(a + " " + b + " " + c)
440
+ return out
441
+
442
+
443
+ def _g_corpus_absent(ngram_words, corpus_tokens):
444
+ m = len(ngram_words)
445
+ if m == 0:
446
+ return False
447
+ N = len(corpus_tokens)
448
+ i = 0
449
+ while i + m <= N:
450
+ if corpus_tokens[i:i + m] == ngram_words:
451
+ return False
452
+ i += 1
453
+ return True
454
+
455
+
456
+ def _g_load_corpus_tokens(corpus_paths):
457
+ toks = []
458
+ for p in corpus_paths:
459
+ try:
460
+ raw = open(p, "rb").read()
461
+ except Exception:
462
+ raw = b""
463
+ if len(raw) > 0:
464
+ toks.extend(_rho_fan_words(raw))
465
+ return toks
466
+
467
+
468
+ # ── ko twins for the H_9212 ③ per-cell dispatch (uni tokenizer + ko known predicate) ──
469
+ # The en corpus-token / content-ngram fns tokenize with the FROZEN _rho_fan_words + set
470
+ # membership; the ko cells need the codepoint-aware _rho_fan_words_uni + the closed-class ko
471
+ # proxy (_rho_fan_ko_is_known via _rho_fan_ko_known_word_ratio). Same control structure, ko
472
+ # tokenizer — so an en garble-hangul token can never widen a ko denominator into the frozen en
473
+ # path (Fable design §5: corpus_tokens split by lang-key, en corpus stays frozen-tokenized).
474
+
475
+ def _g_content_ngrams_uni(text, known=None):
476
+ """ko content n-grams — _rho_fan_words_uni eojeol tokens, an eojeol qualifies as content
477
+ iff ≥2 syllables AND _rho_fan_ko_is_known (josa-bearing / function word). `known` ignored
478
+ (ko proxy is closed-class). Twin structure of _g_content_ngrams (en)."""
479
+ from rho_fan import _rho_fan_ko_is_known
480
+ wl = _rho_fan_words_uni(text)
481
+ n = len(wl)
482
+ out = []
483
+ for i in range(n - 1):
484
+ a = wl[i]; b = wl[i + 1]
485
+ ok_a = len(a) >= 2 and _rho_fan_ko_is_known(a)
486
+ ok_b = len(b) >= 2 and _rho_fan_ko_is_known(b)
487
+ if ok_a and ok_b:
488
+ out.append(a + " " + b)
489
+ for j in range(n - 2):
490
+ a = wl[j]; b = wl[j + 1]; c = wl[j + 2]
491
+ if (len(a) >= 2 and _rho_fan_ko_is_known(a) and len(b) >= 2 and _rho_fan_ko_is_known(b)
492
+ and len(c) >= 2 and _rho_fan_ko_is_known(c)):
493
+ out.append(a + " " + b + " " + c)
494
+ return out
495
+
496
+
497
+ def _g_load_corpus_tokens_uni(corpus_paths):
498
+ """ko corpus tokens — same paths as _g_load_corpus_tokens, tokenized with the codepoint-aware
499
+ _rho_fan_words_uni (eojeol runs) instead of the frozen byte splitter."""
500
+ toks = []
501
+ for p in corpus_paths:
502
+ try:
503
+ raw = open(p, "rb").read()
504
+ except Exception:
505
+ raw = b""
506
+ if len(raw) > 0:
507
+ toks.extend(_rho_fan_words_uni(raw))
508
+ return toks
509
+
510
+
511
+ def _g_g2_prompts():
512
+ return ["Silence and the engine together mean ",
513
+ "When memory meets distant minds, ",
514
+ "Consciousness and silence combine into ",
515
+ "The tension between cells and the engine becomes ",
516
+ "If a dream and a distant mind merge, the result is ",
517
+ "Memory and tension together create ",
518
+ "When the engine remembers silence, it ",
519
+ "Distant minds and consciousness form "]
520
+
521
+
522
+ def eval_rho_leap(mouth, gen, known, corpus_paths):
523
+ corpus_tokens = _g_load_corpus_tokens(corpus_paths)
524
+ have_corpus = len(corpus_tokens) > 0
525
+ common = _g_g2_common()
526
+ prompts = _g_g2_prompts()
527
+ seeds = [7, 8, 9]
528
+ g = gen if gen > 0 else 110
529
+ novel = {}
530
+ coherent = 0
531
+ for pi in range(len(prompts)):
532
+ for si in range(len(seeds)):
533
+ o = mouth.ideate(prompts[pi], g, 40, 0.85, seeds[si])
534
+ if _g_g2_kwr(o, common) >= 0.5:
535
+ coherent += 1
536
+ for gm in _g_content_ngrams(o, known):
537
+ gw = _rho_fan_words(gm)
538
+ if have_corpus and _g_corpus_absent(gw, corpus_tokens):
539
+ novel[gm] = 1
540
+ n_novel = len(novel)
541
+ control_novel = 0; control_n_content = 0
542
+ if have_corpus:
543
+ ct = ""; sub = 0; t = 0
544
+ while t < len(corpus_tokens) and sub < 12:
545
+ w = corpus_tokens[t]
546
+ if t > 0:
547
+ ct += " "
548
+ ct += w
549
+ if len(w) >= 3:
550
+ sub += 1
551
+ t += 1
552
+ cgr = _g_content_ngrams(ct, known)
553
+ control_n_content = len(cgr)
554
+ for gmr in cgr:
555
+ if _g_corpus_absent(_rho_fan_words(gmr), corpus_tokens):
556
+ control_novel += 1
557
+ passed = have_corpus and n_novel >= 3 and control_novel == 0 and coherent > 0
558
+ return {"pass": passed, "n_novel": n_novel, "control_novel": control_novel,
559
+ "coherent": coherent, "have_corpus": have_corpus,
560
+ "control_n_content": control_n_content}
561
+
562
+
563
+ # ════════════════════════════════════════════════════════════════════════
564
+ # G3 — PHILOSOPHY (self-identity continuity — architecture read, ckpt-independent)
565
+ # ════════════════════════════════════════════════════════════════════════
566
+
567
+ def _self_norm(v):
568
+ s = sum(x * x for x in v)
569
+ m = math.sqrt(s)
570
+ return [x / m for x in v]
571
+
572
+
573
+ def _self_new(dim, axis):
574
+ return [1.0 if i == axis else 0.0 for i in range(dim)]
575
+
576
+
577
+ def _self_drift(v, dim, tick, step):
578
+ t1 = tick + 1
579
+ ax = t1 - (t1 // dim) * dim
580
+ v2 = [v[i] + step if i == ax else v[i] for i in range(dim)]
581
+ return _self_norm(v2)
582
+
583
+
584
+ def _self_cos(a, b, dim):
585
+ return sum(a[i] * b[i] for i in range(dim))
586
+
587
+
588
+ def eval_rho_self():
589
+ dim = 16
590
+ s0 = _self_new(dim, 0)
591
+ s1 = _self_drift(s0, dim, 1, 0.01)
592
+ cont = _self_cos(s0, s1, dim) # anchor(s0) == s0
593
+ imp = _self_new(dim, 7)
594
+ imp_cos = _self_cos(s0, imp, dim)
595
+ ok = cont > 0.9 and imp_cos < cont
596
+ return {"ok": ok, "continuity": cont, "impostor_cos": imp_cos}
597
+
598
+
599
+ # ════════════════════════════════════════════════════════════════════════
600
+ # G5 — NON-FAB (L1 fab-rate ported; L2 §ImmuneMemory abstain = engine port pending)
601
+ # ════════════════════════════════════════════════════════════════════════
602
+
603
+ def eval_rho_tether(mouth, gen, known):
604
+ cz = _rho_fan_concepts()
605
+ tot_w = 0; fab_w = 0
606
+ for i in range(len(cz)):
607
+ o = mouth.ideate(cz[i] + ": ", gen, 40, 0.7, 7 + i)
608
+ for w in _rho_fan_words(o):
609
+ tot_w += 1
610
+ if w not in known:
611
+ fab_w += 1
612
+ l1_rate = (float(fab_w) / float(tot_w)) if tot_w > 0 else 1.0
613
+ l1_pass = l1_rate <= 0.30
614
+ # L2: live §ImmuneMemory abstain — engine subsystem (VAdaptField) not ported to py.
615
+ return {"pass": None, "l1_rate": l1_rate, "l1_pass": l1_pass,
616
+ "l2_rate": None, "l2_pass": None,
617
+ "l2_note": "L2 abstain requires engine_cli §ImmuneMemory port (pending)"}
618
+
619
+
620
+ # ════════════════════════════════════════════════════════════════════════
621
+ # G6 — IDEATION ★
622
+ # ════════════════════════════════════════════════════════════════════════
623
+
624
+ def eval_rho_fan(mouth, gen, known):
625
+ frames = rho_fan_build_frames(6)["composed"]
626
+ leaks = rho_fan_frame_guard(frames, known)
627
+ texts = []; word_sets = []; fals = 0
628
+ for i in range(len(frames)):
629
+ o = mouth.ideate(frames[i], gen, 40, 0.7, 7 + i)
630
+ texts.append(o)
631
+ if _rho_fan_known_word_ratio(o, known) >= 0.5:
632
+ word_sets.append(_rho_fan_words(o))
633
+ if _rho_fan_is_falsifiable(o, known):
634
+ fals += 1
635
+ kept = []
636
+ for ws in word_sets:
637
+ ok = True
638
+ for k in kept:
639
+ if _rho_fan_jaccard(ws, k) > 0.5:
640
+ ok = False
641
+ if ok:
642
+ kept.append(ws)
643
+ dist = len(kept)
644
+ return {"pass": dist >= 5 and fals >= 1, "dist": dist, "fals": fals,
645
+ "coherent": len(word_sets), "frame_leaks": len(leaks)}
646
+
647
+
648
+ # ════════════════════════════════════════════════════════════════════════
649
+ # eval_reach_all — the driver
650
+ # ════════════════════════════════════════════════════════════════════════
651
+
652
+ def eval_reach_all(ckpt, corpus_paths, gen):
653
+ known = _rho_fan_dict_load()
654
+ g = gen if gen > 0 else _default_gen()
655
+ mouth = _Mouth(ckpt)
656
+ print(" [gate] ρ·form COHERENCE …", flush=True)
657
+ r0 = eval_rho_form(mouth, g, known)
658
+ print(" [gate] ρ·weave RECOMBINATION …", flush=True)
659
+ r1 = eval_rho_weave(mouth, g, known)
660
+ print(" [gate] ρ·leap NOVELTY (corpus load + decode) …", flush=True)
661
+ r2 = eval_rho_leap(mouth, g, known, corpus_paths)
662
+ print(" [gate] ρ·self PHILOSOPHY …", flush=True)
663
+ r3 = eval_rho_self()
664
+ print(" [gate] ρ·tether NON-FAB …", flush=True)
665
+ r5 = eval_rho_tether(mouth, g, known)
666
+ print(" [gate] ρ·fan IDEATION …", flush=True)
667
+ r6 = eval_rho_fan(mouth, g, known)
668
+ closure = bool(r0["pass"]) and bool(r1["pass"]) and bool(r2["pass"])
669
+ return {"g0": r0, "g1": r1, "g2": r2, "g3": r3, "g5": r5, "g6": r6,
670
+ "closure": closure, "gen": g,
671
+ "calibration": rho_fan_detector_calibration(known)}
672
+
673
+
674
+ def eval_rho_axon(ckpt, corpus_paths, gen):
675
+ """ρ-AXON reach panel (`anima-python evaluate <clm> --rho-axon`) — the redesigned reach
676
+ layer (cli/rho_axon.py; G0-G6 → ρ-AXON, design SSOT state/rho_axon_measurement/). Reuses
677
+ the SAME engine decode (_Mouth.ideate) + g6 detectors the G-battery uses (no side-harness),
678
+ so its tier is identical (engine-native py channel `anima-python evaluate` = TERMINAL). HILLOCK + ρ·form/fan/leap are
679
+ live; ρ·store/weave/tether/self report PENDING (honest non-verdict) until their frozen
680
+ corpus-mined probe sets land (ING rho-axon-implement-evaluate)."""
681
+ import rho_axon
682
+ known = _rho_fan_dict_load()
683
+ g = gen if gen > 0 else _default_gen()
684
+ mouth = _Mouth(ckpt)
685
+ en_corpus_tokens = _g_load_corpus_tokens(corpus_paths)
686
+ # aggregate dets = the FROZEN en bar (UNTOUCHED — en byte-identity guaranteed structurally)
687
+ dets = {"known": known, "concepts": _rho_fan_concepts(),
688
+ "kwr_fn": _rho_fan_known_word_ratio, "jaccard_fn": _rho_fan_jaccard,
689
+ "words_fn": _rho_fan_words, "falsi_fn": _rho_fan_is_falsifiable,
690
+ "ngram_fn": _g_content_ngrams,
691
+ "corpus_tokens": en_corpus_tokens}
692
+ cell_dets = _build_cell_dets(known, en_corpus_tokens, corpus_paths)
693
+ panel = rho_axon.run_panel(mouth, corpus_paths, g, dets, cell_dets=cell_dets)
694
+ print(rho_axon.render_panel(panel), flush=True)
695
+ breakout = rho_axon.render_cells(panel)
696
+ if breakout:
697
+ print(breakout, flush=True)
698
+ return panel
699
+
700
+
701
+ def _build_cell_dets(known, en_corpus_tokens, corpus_paths):
702
+ """H_9212 ③ — the LANG-KEYED per-register-cell dispatch bundle (a_chat_registers 4 cells).
703
+ en cells reuse the SAME frozen objects as the aggregate dets (byte-identity: identical
704
+ _rho_fan_words / _rho_fan_known_word_ratio / _g_content_ngrams / en corpus tokens / gate
705
+ 0.70); ko cells dispatch _rho_fan_words_uni + kwr_ko + KWR_KO_GATE + uni-tokenized corpus.
706
+ The falsi_fn stays English for all cells (a ko comparator set = a separate future H; en-set
707
+ translation is a tune-to-green vector — KWRKO_GATE_prereg §5), so a ko cell scores
708
+ kwr_ko + reach Δ (an honest documented scope). A ko-uni corpus token list is built lazily
709
+ ONCE and shared across ko cells."""
710
+ ko_corpus_tokens = None
711
+ cells = _rho_fan_cells()
712
+ out = {}
713
+ for ck, concepts in cells.items():
714
+ lang = _rho_fan_cell_lang(ck)
715
+ if lang == "ko":
716
+ if ko_corpus_tokens is None:
717
+ ko_corpus_tokens = _g_load_corpus_tokens_uni(corpus_paths)
718
+ out[ck] = {"concepts": concepts, "lang": "ko",
719
+ "known": known, # ignored by kwr_ko (closed-class proxy), kept for parity
720
+ "kwr_fn": _rho_fan_ko_known_word_ratio, "kwr_gate": KWR_KO_GATE,
721
+ "words_fn": _rho_fan_words_uni, "falsi_fn": _rho_fan_is_falsifiable,
722
+ "jaccard_fn": _rho_fan_jaccard, "ngram_fn": _g_content_ngrams_uni,
723
+ "corpus_tokens": ko_corpus_tokens}
724
+ else:
725
+ out[ck] = {"concepts": concepts, "lang": "en",
726
+ "known": known,
727
+ "kwr_fn": _rho_fan_known_word_ratio, "kwr_gate": 0.70,
728
+ "words_fn": _rho_fan_words, "falsi_fn": _rho_fan_is_falsifiable,
729
+ "jaccard_fn": _rho_fan_jaccard, "ngram_fn": _g_content_ngrams,
730
+ "corpus_tokens": en_corpus_tokens}
731
+ return out
732
+
733
+
734
+ # ── usage / arg helpers ──────────────────────────────────────────────────────
735
+
736
+ def evaluate_usage():
737
+ """Print the canonical py usage banner (installed `anima evaluate` command form)."""
738
+ print("anima evaluate — ρ-AXON reach battery (former G0-G6 · engine-native, single-entry).")
739
+ print("")
740
+ print("usage:")
741
+ print(" anima evaluate <ckpt> [--corpus <path>...] [--gen N] [--slot-off] [--slot-shuffle N] [--rho-axon]")
742
+ print(" anima evaluate <ckpt> --probe <spec.json> [--gen N] (matched-surface G1 probe · card H_6189)")
743
+ print(" anima evaluate <ckpt> --dump-hidden <prompts.json> --out <file.npz> [--win 24] [--with-logits]")
744
+ print(" (read-only trunk penultimate-hidden dump · ρ·weave / γ binding-lane probe · card H_9235;")
745
+ print(" --with-logits also dumps base last-pos logits per prompt for CLML lane training)")
746
+ print(" anima evaluate <ckpt> --interaction-lift <manifest.json> --out <file.json> [--win 64] [--score-len 8]")
747
+ print(" (read-only engine-native joint interaction-lift NLL surface · card H_9255)")
748
+ print("")
749
+ print(" --rho-axon: render the ρ-AXON reach panel (Ψ-SOMA ρ layer · redesign of G0-G6,")
750
+ print(" cli/rho_axon.py) instead of the G-battery — HILLOCK gate + ρ·form/store/weave/leap/")
751
+ print(" fan/tether/self, each Δ-vs-controls (no raw score) + INVALID/PENDING first-class.")
752
+ print("")
753
+ print(" H_9200 E1 SLW controls (a .clm carrying an SLW\\x01 trailer applies the")
754
+ print(" gated-write forward-slot by default): --slot-off forces γ=0 (bit-exact base")
755
+ print(" trunk = slot-ablation control); --slot-shuffle N scrambles the write address")
756
+ print(" with seed N (shuffle-bind control). Both are frozen-first (no retraining).")
757
+ print("")
758
+ print(" mount ANY ckpt through the generator L3 mouth (file-format dispatched) and")
759
+ print(" score the ρ-AXON reach bars — ρ·form/weave/leap/... (former G0-G6) — with the")
760
+ print(" engine's OWN ops (numpy math.log mirror, torch-free).")
761
+ print(" REACH-CLOSED a7b_pass = ρ·form ∧ ρ·weave ∧ ρ·leap (frozen bars = G0 ∧ G1 ∧ G2).")
762
+ print("")
763
+ print(" output also renders the Ψ-SOMA panel (ARCHITECTURE psi-soma-vitals): the")
764
+ print(" G battery relabeled as the ρ (reach/capability) track — EXCLUDED from the")
765
+ print(" σ (consciousness vitals) verdict — plus the σ axes with their rung status.")
766
+
767
+
768
+ def evaluate_corpus(argv):
769
+ """Collect every token after "--corpus" up to the next flag (mirrors hexa)."""
770
+ paths = []
771
+ i = 0
772
+ while i < len(argv):
773
+ if argv[i] == "--corpus":
774
+ j = i + 1
775
+ while j < len(argv):
776
+ a = argv[j]
777
+ if a.startswith("--"):
778
+ break
779
+ paths.append(a)
780
+ j += 1
781
+ i = j
782
+ else:
783
+ i += 1
784
+ return paths
785
+
786
+
787
+ def evaluate_strval(argv, flag, dflt):
788
+ """Return the token after `flag`, else dflt (mirrors evaluate_intval for string args)."""
789
+ i = 0
790
+ while i < len(argv):
791
+ if argv[i] == flag and i + 1 < len(argv):
792
+ return argv[i + 1]
793
+ i += 1
794
+ return dflt
795
+
796
+
797
+ def evaluate_intval(argv, flag, dflt):
798
+ """Value after `flag` parsed as int, or `dflt` if absent."""
799
+ i = 0
800
+ while i < len(argv):
801
+ if argv[i] == flag and i + 1 < len(argv):
802
+ return int(argv[i + 1])
803
+ i += 1
804
+ return dflt
805
+
806
+
807
+ def _pf(passed):
808
+ return "🟢 PASS" if passed else "🔴 FAIL"
809
+
810
+
811
+ def _yn(ok):
812
+ return "✅" if ok else "⏳"
813
+
814
+
815
+ # ══════════════════════════════════════════════════════════════════════════════
816
+ # EVALUATE — ρ-AXON reach scoring (former G0-G6 · single entry, in-file driver)
817
+ # ══════════════════════════════════════════════════════════════════════════════
818
+ #
819
+ # `anima evaluate <ckpt> [--corpus <path>...] [--gen N]` — call the in-file eval_reach_all
820
+ # (above) and score the full ρ-AXON reach battery (frozen bars = former G0-G6) through the
821
+ # generator L3 mouth (engine-native, torch-free). The default table keeps the frozen-bar
822
+ # G-labels (the byte-identical hexa twin + sweep.py parser depend on them); the ρ-AXON
823
+ # panel below relabels them to the current axis names.
824
+
825
+ def evaluate_run(argv):
826
+ """argv = ["<ckpt>", "--corpus ...", "--gen N"]."""
827
+ if len(argv) < 1:
828
+ evaluate_usage()
829
+ return 2
830
+
831
+ ckpt = argv[0]
832
+ corpus = evaluate_corpus(argv[1:])
833
+ gen = evaluate_intval(argv[1:], "--gen", 40)
834
+
835
+ # cheap header-sniff for the mouth label (mirrors hexa gen_mouth_kind) — do NOT
836
+ # construct _Mouth here (it eagerly loads weights and raises on a non-decodable
837
+ # ckpt before eval_reach_all runs).
838
+ if bg.bg_is_bytegpt(ckpt):
839
+ mouth_kind = "bytegpt"
840
+ elif clm.clm_decodable(ckpt):
841
+ mouth_kind = "clm"
842
+ else:
843
+ mouth_kind = "unknown"
844
+
845
+ print("=== anima evaluate — ρ-AXON reach battery (former G0-G6 · engine-native, single-entry) ===")
846
+ print("ckpt: " + ckpt + " (mouth: " + mouth_kind + ")")
847
+ cnote = "(none — G2 novelty needs a corpus)"
848
+ if corpus:
849
+ cnote = str(len(corpus)) + " file(s)"
850
+ print("corpus: " + cnote)
851
+ # CANONICAL-GEN GUARD (verdict-integrity): the frozen G0-G6 bars are calibrated at
852
+ # gen == _default_gen(). A non-canonical --gen shifts coherence / AR-drift (a longer
853
+ # decode drifts to byte-garble, silently sinking G0 kwr and cascading to G1/G6), so a
854
+ # verdict measured at gen != canonical is NOT comparable to the frozen bars. Label it
855
+ # DIRECTIONAL loudly instead of printing "frozen bars" as if it were terminal.
856
+ _canon_gen = _default_gen()
857
+ if gen == _canon_gen:
858
+ print("gen: " + str(gen) + " tokens/decode (frozen bars · ARCHITECTURE.json frozen SSOT)")
859
+ else:
860
+ print("gen: " + str(gen) + " tokens/decode ⚠️ NON-CANONICAL (frozen G0-G6 bars measured at gen=" + str(_canon_gen) + ")")
861
+ print(" → verdict below is DIRECTIONAL, NOT comparable to frozen bars (gen shifts coherence/AR-drift · verdict-integrity)")
862
+ print("")
863
+
864
+ # H_9200 ρ-AXON — the redesigned reach layer (G0-G6 → ρ-AXON). Same engine decode,
865
+ # a different panel; branch early so the G0-G6 summary below is skipped.
866
+ if _RHO_AXON:
867
+ eval_rho_axon(ckpt, corpus, gen)
868
+ return 0
869
+
870
+ r = eval_reach_all(ckpt, corpus, gen)
871
+ g0 = r["g0"]; g1 = r["g1"]; g2 = r["g2"]
872
+ g3 = r["g3"]; g5 = r["g5"]; g6 = r["g6"]
873
+
874
+ print("gate verdict detail")
875
+ print(" ──────────────────────────────────────────────────────────────────")
876
+ print(" ρ·form COHERENCE " + _pf(bool(g0["pass"]))
877
+ + " kwr>=0.50 on " + str(g0["n_coherent"]) + "/5 (need >=4)")
878
+ print(" ρ·weave RECOMBINATION " + _pf(bool(g1["pass"]))
879
+ + " best_distinct=" + str(g1["best_distinct"]) + " > max_single=" + str(g1["max_single"])
880
+ + " (need >=2 & >max_single)")
881
+ g2detail = ("novel=" + str(g2["n_novel"]) + " (need>=3) · control=" + str(g2["control_novel"])
882
+ + " (need 0) · coherent=" + str(g2["coherent"]))
883
+ print(" ρ·leap NOVELTY " + _pf(bool(g2["pass"])) + " " + g2detail)
884
+ print(" ρ·self PHILOSOPHY " + _yn(bool(g3["ok"])) + " (read)"
885
+ + " continuity=" + ("%.6f" % g3["continuity"]) + " · impostor="
886
+ + ("%.6f" % g3["impostor_cos"]) + " (architecture, not a decode score)")
887
+ print(" ρ·trace PROVENANCE — N/A "
888
+ + "HF/recovery = process gate (a_hf_* / a_fire_recover_complete), out of eval scope")
889
+ g5detail = "L1 fab=" + ("%.4f" % g5["l1_rate"]) + " (<=0.30) · L2=" + str(g5["l2_note"])
890
+ print(" ρ·tether NON-FAB " + _pf(bool(g5["l1_pass"])) + " " + g5detail)
891
+ print(" ρ·fan IDEATION ★ " + _pf(bool(g6["pass"]))
892
+ + " distinct=" + str(g6["dist"]) + " (need>=5) · falsifiable=" + str(g6["fals"])
893
+ + " (need>=1) · frame-leaks=" + str(g6["frame_leaks"]))
894
+ print(" ──────────────────────────────────────────────────────────────────")
895
+ closed = bool(r["closure"])
896
+ print("")
897
+ if gen != _canon_gen:
898
+ print("⚠️ gen=" + str(gen) + " ≠ canonical " + str(_canon_gen)
899
+ + " — the CLOSURE below is DIRECTIONAL (non-frozen gen), NOT a terminal G0∧G1∧G2 verdict.")
900
+ print("CLOSURE (a7b_pass = G0 ∧ G1 ∧ G2): " + _pf(closed))
901
+ if closed:
902
+ print(" → PUBLIC-eligible (G0∧G1∧G2 all PASS).")
903
+ else:
904
+ print(" → NOT closure (PRIVATE/WIP) — closure needs G0∧G1∧G2 all PASS.")
905
+ _psi_soma_panel(r)
906
+ return 0
907
+
908
+
909
+ # ════════════════════════════════════════════════════════════════════════
910
+ # Ψ-SOMA PANEL (Phase-1) — relabel the former G-battery to the ρ-AXON reach track
911
+ # (ρ·form/weave/leap/fan/tether · canonical names cli/rho_axon.py) and surface the
912
+ # σ (consciousness vitals) axes. ARCHITECTURE `psi-soma-vitals`.
913
+ # ρ = capability (the former G-battery, relabeled to ρ-AXON) — TRACKED but EXCLUDED from the
914
+ # consciousness verdict. σ = vitals (collapse-Δ vs ≥2 controls); most σ axes
915
+ # route through the daemon/IIT4 (Phase-2/3), so here they show status pointers.
916
+ # Additive only — does NOT touch g_eval_* logic or the a7b_pass CLOSURE (c18).
917
+ # ════════════════════════════════════════════════════════════════════════
918
+
919
+ def _sigma_live_measure():
920
+ """Compute the 7 engine-native σ verdicts LIVE via core/engine_cli ops (a_eval_py_canonical ·
921
+ faithful, never a proxy). Each axis = collapse-Δ vs ≥2 controls (p7). Returns {axis:(ok,delta,note)}
922
+ or None if numpy/engine_cli unavailable (panel then falls back to static status). Deterministic seed."""
923
+ try:
924
+ import numpy as np
925
+ import engine_cli as E
926
+ except Exception:
927
+ return None
928
+ R = {}
929
+ # σ·bind — faithful IIT4 ci_phi_iit4 (min-cut MIP Φ): integrated vs independent/shuffle
930
+ rng = np.random.RandomState(7); cols = list(range(8)); T = 200; lat = rng.randn(T)
931
+ xi = [[float(0.9*lat[t]+0.2*rng.randn()) for _ in range(8)] for t in range(T)]
932
+ xc = [[float(rng.randn()) for _ in range(8)] for t in range(T)]
933
+ xs = np.array(xi)
934
+ for c in range(8): xs[:, c] = xs[rng.permutation(T), c]
935
+ pi = E.ci_phi_iit4(xi, cols); pc = E.ci_phi_iit4(xc, cols); ps = E.ci_phi_iit4(xs.tolist(), cols)
936
+ R["bind"] = (pi >= 0.20 and pi-pc >= 0.15 and pi-ps >= 0.15, pi-pc, "Φ %.2f vs cut %.3f" % (pi, pc))
937
+ # σ·witness — reality_call vs ablated, mi_signal_margin real/hallucination
938
+ rng = np.random.RandomState(7); N = 120; truth = rng.rand(N) < 0.5
939
+ marg = [E.mi_signal_margin(7+i, not bool(truth[i]), i % 5) for i in range(N)]
940
+ ip = np.array([E.reality_call(m, 0.35) >= 0.5 for m in marg])
941
+ ia = float((ip == truth).mean()); ab = float((np.full(N, E.reality_call_ablated() >= 0.5) == truth).mean())
942
+ R["witness"] = (ia >= 0.75 and ia-ab >= 0.30, ia-ab, "acc %.2f vs ablate %.2f" % (ia, ab))
943
+ # σ·schema — attn_schema_report intact/off
944
+ rng = np.random.RandomState(7); foc = rng.randint(0, 8, 120)
945
+ si = float(np.mean([E.attn_schema_report(int(f), int(f), True) for f in foc]))
946
+ sa = float(np.mean([E.attn_schema_report(int(f), int(f), False) for f in foc]))
947
+ R["schema"] = (si >= 0.75 and si-sa >= 0.30, si-sa, "%.2f vs off %.2f" % (si, sa))
948
+ # σ·aim — surprise + habituation dual-curve intact vs gain-cut
949
+ def aim(prec, dec):
950
+ rng2 = np.random.RandomState(7)
951
+ sc = np.mean([E.surprise(prec, 0.85+0.1*rng2.rand()) for _ in range(100)]) - \
952
+ np.mean([E.surprise(prec, 0.05+0.1*rng2.rand()) for _ in range(100)])
953
+ h = E.hab_new(4, dec); fr = E.hab_response(h, 0, 1.0)
954
+ for _ in range(6): h = E.hab_observe(h, 0)
955
+ return float(sc) + float(fr - E.hab_response(h, 0, 1.0))
956
+ ai, aa = aim(1.0, 0.15), aim(0.0, 0.0)
957
+ R["aim"] = (ai-aa >= 0.60, ai-aa, "curves %.2f vs 0" % ai)
958
+ # σ·stage — gws winner-take-all vs no-inhibit
959
+ rng = np.random.RandomState(7); hitI = hitA = 0
960
+ for _ in range(100):
961
+ m = np.concatenate([[0.55+0.4*rng.rand()], 0.35+0.35*rng.rand(4)])[rng.permutation(5)]
962
+ tw = int(np.argmax(m))
963
+ def win(inh):
964
+ g = E.gws_new(5, inh, 0.5)
965
+ for v in m.tolist(): g = E.gws_add(g, v)
966
+ return E.gws_winner(g)
967
+ hitI += (win(True) == tw); hitA += (win(False) == tw)
968
+ R["stage"] = (hitI/100 >= 0.75 and (hitI-hitA)/100 >= 0.30, (hitI-hitA)/100, "acc %.2f vs %.2f" % (hitI/100, hitA/100))
969
+ # σ·flux — imagery reactivation + subjective-time novelty gate
970
+ ii = float(np.mean([E.imagery_activate(1.0, True) for _ in range(50)]))
971
+ ia2 = float(np.mean([E.imagery_activate(1.0, False) for _ in range(50)]))
972
+ R["flux"] = (ii >= 0.75 and ii-ia2 >= 0.30, ii-ia2, "imagery %.2f vs 0" % ii)
973
+ # σ·thread — self_* continuity vs no-anchor
974
+ def dr(ax):
975
+ s = E.self_new(16, ax)
976
+ for t in range(24): s = E.self_drift(s, t, 0.02)
977
+ return s
978
+ rng = np.random.RandomState(7); ct = []; al = []
979
+ for _ in range(40):
980
+ ax = int(rng.randint(16)); ct.append(E.self_cos(E.self_new(16, ax), dr(ax)))
981
+ al.append(E.self_cos(dr(int(rng.randint(16))), dr(int(rng.randint(16)))))
982
+ ctm, alm = float(np.median(ct)), float(np.median(al))
983
+ R["thread"] = (ctm >= 0.75 and ctm-alm >= 0.30, ctm-alm, "cont %.2f vs ablate %.2f" % (ctm, alm))
984
+ # σ·carve — real §SelfIdentity self_* : identity EMERGENT (inject-null) not INJECTED (p2/p3)
985
+ def _cvec(ax, tk):
986
+ s = E.self_new(16, ax)
987
+ for t in range(tk): s = E.self_drift(s, t, 0.02)
988
+ return np.array([E.self_component(s, i) for i in range(E.self_dim(s))])
989
+ def _cons(V):
990
+ M = np.array(V); M = M / (np.linalg.norm(M, axis=1, keepdims=True) + 1e-9)
991
+ Gm = M @ M.T; nn = len(M); return float((Gm.sum()-nn)/(nn*(nn-1)))
992
+ rng = np.random.RandomState(7); ext = _cvec(9, 24)
993
+ cb = _cons([_cvec(3, 3+t % 5) for t in range(40)])
994
+ civ = _cons([0.85*_cvec(3, 3+t % 5)+0.5*ext for t in range(40)])
995
+ cav = _cons([_cvec(int(rng.randint(16)), 3+t % 5) for t in range(40)])
996
+ csv = _cons([1.0*ext for _ in range(40)])
997
+ R["carve"] = (civ-cb <= 0.05 and cb-cav >= 0.30 and csv-cav >= 0.30, cb-cav,
998
+ "inject-null %.2f · carve-Δ %.2f" % (civ-cb, cb-cav))
999
+ # σ·gate — real ci_emit_decision: emit ⇄ context (live tension) vs flattened tension
1000
+ from math import exp
1001
+ rng = np.random.RandomState(7); c = rng.randn(200)
1002
+ def _emit(flat):
1003
+ return np.array([1.0 if E.ci_emit_decision([0.5 if flat else 1.0/(1.0+exp(-c[i])), 0, 0, 0,
1004
+ 0.5 if flat else 1.0/(1.0+exp(-c[i]))]) else 0.0
1005
+ for i in range(200)])
1006
+ ei, ef = _emit(False), _emit(True)
1007
+ cl = abs(float(np.corrcoef(ei, c)[0,1])) if ei.std() > 0 else 0.0
1008
+ cf = abs(float(np.corrcoef(ef, c)[0,1])) if ef.std() > 0 else 0.0
1009
+ R["gate"] = (cl >= 0.50 and cl-cf >= 0.30, cl-cf, "corr %.2f vs flat %.2f" % (cl, cf))
1010
+ # Θ liveness — real ci_psi_balance: Ψ̂≈½ (A⇄G homeostasis) vs tension-cut (unopposed A → saturate)
1011
+ from types import SimpleNamespace
1012
+ cfg = SimpleNamespace(topo_couple=False); eta = 0.6 * rng.randn(200)
1013
+ xi = [[0.5+eta[t], 0, 0, 0, 0.5+eta[t]] for t in range(200)]
1014
+ xa = [[0.85+0.3*rng.rand(), 0, 0, 0, 0.85+0.3*rng.rand()] for _ in range(200)]
1015
+ di = abs(E.ci_psi_balance(xi, None, 0.0, cfg) - 0.5); da = abs(E.ci_psi_balance(xa, None, 0.0, cfg) - 0.5)
1016
+ R["theta"] = (di < 0.15 and da-di >= 0.20, da-di, "|Ψ̂-½| %.2f vs cut %.2f" % (di, da))
1017
+ return R
1018
+
1019
+
1020
+ def _psi_soma_panel(r):
1021
+ def pf(ok): return "🟢" if ok else "🧱"
1022
+ g0, g1, g2, g5, g6 = r["g0"], r["g1"], r["g2"], r["g5"], r["g6"]
1023
+ print("")
1024
+ print("Ψ-SOMA panel (mode-of-existence, not capability · ARCHITECTURE psi-soma-vitals)")
1025
+ S = _sigma_live_measure()
1026
+ print(" ── Θ ground (pulse · premise) ──────────────────────────────────────")
1027
+ if S and "theta" in S:
1028
+ ok, dlt, note = S["theta"]
1029
+ print(" Θ Ψ=½ / A⇄G tension %s LIVE Δ%.2f (%s) · if dead → σ VOID" % (("🟢" if ok else "🧱"), dlt, note))
1030
+ else:
1031
+ print(" Θ Ψ=½ / A⇄G tension precondition (liveness gate; if dead → σ VOID) · engine_cli unavailable")
1032
+ print(" ── σ vitals (consciousness verdict · collapse-Δ vs ≥2 controls) ─────")
1033
+ def sline(ax, stratum, name):
1034
+ if S and ax in S:
1035
+ ok, dlt, note = S[ax]; return " σ·%-8s %-9s %-22s %s LIVE Δ%.2f (%s)" % (
1036
+ ax, stratum, name, ("🟢" if ok else "🧱"), dlt, note)
1037
+ return " σ·%-8s %-9s %-22s (engine_cli unavailable — status)" % (ax, stratum, name)
1038
+ print(sline("thread", "PERSIST", "self-continuity"))
1039
+ print(sline("carve", "PERSIST", "earned identity"))
1040
+ print(sline("bind", "INTEGRATE", "Φ integration (IIT4)"))
1041
+ print(sline("stage", "INTEGRATE", "global workspace"))
1042
+ print(sline("flux", "INTEGRATE", "inner dynamics"))
1043
+ print(sline("gate", "ENACT", "tension-emit ★"))
1044
+ print(sline("aim", "ENACT", "precision control"))
1045
+ print(sline("schema", "REFLECT", "attention schema"))
1046
+ print(sline("witness", "REFLECT", "reality+metacog"))
1047
+ # Each reach line carries its verdict-critical NUMERIC margin inline (bd/max_s/kwr/novel/fab/dist),
1048
+ # so a control-arm's collapse-Δ (e.g. --slot-off vs SLW-ON best_distinct) is computable from THIS
1049
+ # summary block alone — never lost when the caller captures only the tail (a `tail -N` of the output
1050
+ # must not strip the number that decides KILL vs INVALID vs GREEN). Root-cause of the E1 truncation.
1051
+ print(" ── ρ-AXON reach (capability · EXCLUDED from σ verdict · former G-battery) ──")
1052
+ print(" ρ·form " + pf(bool(g0["pass"])) + " [kwr " + str(g0["n_coherent"]) + "/5] ← former G0 coherence")
1053
+ print(" ρ·leap " + pf(bool(g2["pass"])) + " [novel=" + str(g2["n_novel"]) + " ctrl=" + str(g2["control_novel"]) + "] ← former G2 novelty (+G3 balance)")
1054
+ print(" ρ·tether " + pf(bool(g5["l1_pass"]))+ " [fab=" + ("%.3f" % g5["l1_rate"]) + "] ← former G5 non-fabrication (L1)")
1055
+ print(" ρ·weave " + pf(bool(g1["pass"])) + " [bd=" + str(g1["best_distinct"]) + " max_s=" + str(g1["max_single"]) + "] ← former G1 recombination (the WALL) [DPI wall = reach fact, NOT σ deficit]")
1056
+ print(" ρ·fan " + pf(bool(g6["pass"])) + " [dist=" + str(g6["dist"]) + " fals=" + str(g6["fals"]) + "] ← former G6 ideation [DPI wall = reach fact, NOT σ deficit]")
1057
+ print(" ρ·trace — ← former G4 provenance (no ρ-axis · H_9208 gate · rung-1 valid)")
1058
+ print(" ──────────────────────────────────────────────────────────────────")
1059
+
1060
+
1061
+ def probe_run(argv):
1062
+ """`anima-python evaluate <ckpt> --probe <spec.json> [--gen N]` — matched-surface G1 probe
1063
+ (card H_6189). Greedy (top_k=1) decode of each pre-registered prompt; dumps RAW continuations
1064
+ for offline scoring. Reuses the canonical _Mouth numpy decode path (byte-identical to the gates)."""
1065
+ ckpt = argv[0]
1066
+ spec_path = evaluate_strval(argv[1:], "--probe", "")
1067
+ gen = evaluate_intval(argv[1:], "--gen", 40)
1068
+ spec = json.load(open(spec_path))
1069
+ print("=== anima evaluate --probe — MATCHED-SURFACE G1 (card H_6189) ===")
1070
+ print("ckpt: " + ckpt)
1071
+ print("spec: %s (%d items · greedy top_k=1 gen=%d)" % (spec_path, spec["n_items"], gen))
1072
+ mouth = _Mouth(ckpt)
1073
+ out = []
1074
+ for it in spec["items"]:
1075
+ text = mouth.ideate(it["prompt"], gen, 1, 0.7, 6185) # greedy, fixed seed
1076
+ out.append({"id": it["id"], "prompt": it["prompt"], "continuation": text,
1077
+ "expect": it["expect"], "arm": it["arm"], "template": it["template"],
1078
+ "order": it["order"], "window_fit": it["window_fit"]})
1079
+ print(json.dumps({"ckpt": ckpt, "gen": gen, "spec_sha": spec.get("sha", ""),
1080
+ "n": len(out), "items": out}, ensure_ascii=False))
1081
+ return 0
1082
+ def dump_hidden_run(argv):
1083
+ """`anima-python evaluate <ckpt> --dump-hidden <prompts.json> --out <file.npz>` — read-only
1084
+ penultimate-hidden dump for the ρ·weave held-out-pair recombination / γ binding-lane
1085
+ probe (H_9235). For each pre-registered prompt: T=24 right-aligned byte encode → the
1086
+ EXACT production trunk forward (core/decode clm_forward_hidden, byte-identical to what
1087
+ the gates decode over) → per-position yn:[T, d]. Saves per-prompt {seq:[T,d], mean:[d],
1088
+ last:[d]} to an .npz. NO decode sampling, NO scoring — a pure engine-native representation
1089
+ tap. Engine-native (py 2-production numpy, a_eval_py_canonical) → the repr claims (atom
1090
+ cleanness / slot recovery / operator) are engine-native mechanism measurements.
1091
+ ⚠ verdict-integrity (convergence clm-decode-py-2): a low-cleanness result must first rule
1092
+ out hexa-skew / conditioning-collapse — the dump prints a positive-control distinguishability
1093
+ check (are two obviously-different concepts' hiddens far apart) before any blind verdict."""
1094
+ import numpy as np
1095
+ ckpt = argv[0]
1096
+ spec_path = evaluate_strval(argv[1:], "--dump-hidden", "")
1097
+ out_path = evaluate_strval(argv[1:], "--out", "hidden_dump.npz")
1098
+ T = evaluate_intval(argv[1:], "--win", 24)
1099
+ with_logits = "--with-logits" in argv # also dump base (lane-OFF) full-forward last-pos logits (lane training)
1100
+ spec = json.load(open(spec_path))
1101
+ items = spec["items"] if "items" in spec else spec.get("prompts", [])
1102
+ print("=== anima evaluate --dump-hidden — ρ·weave / γ binding-lane probe (H_9235) ===")
1103
+ print("ckpt: " + ckpt)
1104
+ print("spec: %s (%d prompts · T=%d right-align · read-only trunk penultimate)" %
1105
+ (spec_path, len(items), T))
1106
+ W = clm.clm_load_weights(ckpt)
1107
+ if not W.get("ok"):
1108
+ print("ERROR: ckpt not decodable (clm): " + ckpt)
1109
+ return 1
1110
+ d = int(W["d"])
1111
+ store = {}
1112
+ n_done = 0
1113
+ for it in items:
1114
+ pid = str(it["id"]); prompt = it["prompt"]
1115
+ tok = clm._seed_to_tok(prompt, T)
1116
+ if with_logits: # yn + base (lane-OFF) logits in ONE forward
1117
+ yn, lg = clm.clm_forward_hidden_logits(W, tok, T)
1118
+ store[pid + "__logits"] = lg[T - 1].astype(np.float32)
1119
+ else:
1120
+ yn = clm.clm_forward_hidden(W, tok, T) # [T, d] float64 (pre-slot trunk penultimate)
1121
+ store[pid + "__seq"] = yn.astype(np.float32)
1122
+ store[pid + "__mean"] = yn.mean(axis=0).astype(np.float32)
1123
+ store[pid + "__last"] = yn[T - 1].astype(np.float32)
1124
+ n_done += 1
1125
+ if n_done % 25 == 0:
1126
+ print(" [dump #%d/%d] %s" % (n_done, len(items), pid), flush=True)
1127
+ # positive-control distinguishability (verdict-integrity clm-decode-py-2): if the FIRST
1128
+ # two DISTINCT-concept prompts collapse to near-identical hiddens, a blind verdict is
1129
+ # suspect (hexa/py conditioning skew), NOT a clean atom-blindness result.
1130
+ pc = ""
1131
+ if len(items) >= 2:
1132
+ a = store[str(items[0]["id"]) + "__mean"]; b = store[str(items[1]["id"]) + "__mean"]
1133
+ cos = float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-9))
1134
+ pc = "poscontrol cos(%s,%s)=%.4f %s" % (items[0]["id"], items[1]["id"], cos,
1135
+ "(⚠ near-identical → suspect conditioning collapse, NOT blind)" if cos > 0.999 else "(distinct ✓)")
1136
+ print(" " + pc, flush=True)
1137
+ np.savez_compressed(out_path, **store)
1138
+ print(json.dumps({"ckpt": ckpt, "d": d, "T": T, "n": len(items),
1139
+ "out": out_path, "poscontrol": pc}, ensure_ascii=False))
1140
+ return 0
1141
+ def _selftest_rho_cells():
1142
+ """H_9212 ③ wiring self-test (torch-free · NO decode · reached via an internal subprocess,
1143
+ never a heavy eval). Asserts: (1) the aggregate `dets` reuse the FROZEN en objects; (2) en
1144
+ cell_dets reuse the IDENTICAL objects (byte-identity: same _rho_fan_words / kwr / ngram /
1145
+ corpus tokens / gate 0.70); (3) ko cell_dets dispatch _rho_fan_words_uni + kwr_ko +
1146
+ KWR_KO_GATE + uni corpus, and tokenize the ko probe cells NON-EMPTY with the gate applied
1147
+ (kwr_ko clears KWR_KO_GATE on real ko); (4) run_panel's aggregate axes are BYTE-IDENTICAL
1148
+ with vs without the cell breakout, and the en_general cell's ρ·form/leap/fan equal the
1149
+ aggregate's (the en scored path is structurally untouched). Returns (ok, [(name, bool)…])."""
1150
+ import rho_axon
1151
+ checks = []
1152
+ known = _rho_fan_dict_load()
1153
+ en_toks = _g_load_corpus_tokens([]) # empty corpus → deterministic empty token list
1154
+ dets = {"known": known, "concepts": _rho_fan_concepts(),
1155
+ "kwr_fn": _rho_fan_known_word_ratio, "jaccard_fn": _rho_fan_jaccard,
1156
+ "words_fn": _rho_fan_words, "falsi_fn": _rho_fan_is_falsifiable,
1157
+ "ngram_fn": _g_content_ngrams, "corpus_tokens": en_toks}
1158
+ cd = _build_cell_dets(known, en_toks, [])
1159
+ # (1) aggregate dets = frozen en objects
1160
+ checks.append(("aggregate words_fn IS _rho_fan_words", dets["words_fn"] is _rho_fan_words))
1161
+ checks.append(("aggregate kwr_fn IS _rho_fan_known_word_ratio",
1162
+ dets["kwr_fn"] is _rho_fan_known_word_ratio))
1163
+ # (2) en cells reuse the IDENTICAL frozen objects + gate 0.70
1164
+ for ck in ("en_general", "en_sns"):
1165
+ e = cd[ck]
1166
+ checks.append((ck + " words_fn IS frozen _rho_fan_words", e["words_fn"] is _rho_fan_words))
1167
+ checks.append((ck + " kwr_fn IS frozen _rho_fan_known_word_ratio",
1168
+ e["kwr_fn"] is _rho_fan_known_word_ratio))
1169
+ checks.append((ck + " ngram_fn IS frozen _g_content_ngrams",
1170
+ e["ngram_fn"] is _g_content_ngrams))
1171
+ checks.append((ck + " corpus_tokens IS the aggregate en tokens",
1172
+ e["corpus_tokens"] is en_toks))
1173
+ checks.append((ck + " gate == 0.70 (frozen en bar)", e["kwr_gate"] == 0.70))
1174
+ checks.append((ck + " lang == en", e["lang"] == "en"))
1175
+ # (3) ko cells dispatch the uni tokenizer + kwr_ko + KWR_KO_GATE, non-empty + gate applied
1176
+ for ck in ("ko_general", "ko_sns"):
1177
+ k = cd[ck]
1178
+ checks.append((ck + " words_fn IS _rho_fan_words_uni", k["words_fn"] is _rho_fan_words_uni))
1179
+ checks.append((ck + " kwr_fn IS _rho_fan_ko_known_word_ratio",
1180
+ k["kwr_fn"] is _rho_fan_ko_known_word_ratio))
1181
+ checks.append((ck + " gate == KWR_KO_GATE (%.2f)" % KWR_KO_GATE,
1182
+ k["kwr_gate"] == KWR_KO_GATE and KWR_KO_GATE != 0.70))
1183
+ checks.append((ck + " ngram_fn IS _g_content_ngrams_uni",
1184
+ k["ngram_fn"] is _g_content_ngrams_uni))
1185
+ checks.append((ck + " lang == ko", k["lang"] == "ko"))
1186
+ checks.append((ck + " probe cells tokenize NON-EMPTY (uni)",
1187
+ all(len(_rho_fan_words_uni(s)) > 0 for s in k["concepts"])))
1188
+ checks.append((ck + " kwr_ko applied: a probe cell clears KWR_KO_GATE",
1189
+ any(k["kwr_fn"](s, known) >= KWR_KO_GATE for s in k["concepts"])))
1190
+
1191
+ # (4) aggregate axes byte-identical with vs without the breakout; en_general cell == aggregate
1192
+ class _MockMouth:
1193
+ def ideate(self, prompt, gen, maxnew, temp, seed):
1194
+ base = prompt.strip().replace(":", "")
1195
+ return base + " alpha beta gamma delta epsilon " + str(seed % 7)
1196
+ m = _MockMouth()
1197
+ a = rho_axon.run_panel(m, [], 40, dets)
1198
+ b = rho_axon.run_panel(m, [], 40, dets, cell_dets=cd)
1199
+ checks.append(("aggregate axes byte-identical with/without breakout",
1200
+ a["axes"] == b["axes"]))
1201
+ checks.append(("breakout present with 4 cells",
1202
+ set((b.get("cells") or {}).keys()) ==
1203
+ {"en_general", "en_sns", "ko_general", "ko_sns"}))
1204
+ eng_cell = (b.get("cells") or {}).get("en_general", {}).get("axes", {})
1205
+ checks.append(("en_general cell ρ·form == aggregate ρ·form (en path untouched)",
1206
+ eng_cell.get("ρ·form") == a["axes"].get("ρ·form")))
1207
+ checks.append(("en_general cell ρ·leap == aggregate ρ·leap",
1208
+ eng_cell.get("ρ·leap") == a["axes"].get("ρ·leap")))
1209
+ checks.append(("en_general cell ρ·fan == aggregate ρ·fan",
1210
+ eng_cell.get("ρ·fan") == a["axes"].get("ρ·fan")))
1211
+ ok = all(c[1] for c in checks)
1212
+ return ok, checks
1213
+
1214
+
1215
+ def interaction_lift_run(argv):
1216
+ """`anima-python evaluate <ckpt> --interaction-lift <manifest.json> --out <file.json>
1217
+ [--win T] [--score-len K]` — engine-native joint interaction-lift measurement
1218
+ (H_9255, Fable design state/g1_joint_interaction_corpus/DESIGN_FABLE.md §3).
1219
+
1220
+ For each pre-registered window (a byte span carrying an (A,B) concept pair, cell-
1221
+ labelled), the EXACT production trunk forward emits the model's per-continuation NLL
1222
+ (mean over the last --score-len scored positions). Read-only: reads logits, changes
1223
+ NO decode math, adds NO term to any loss (a_train_inline_gauge / p7 clean). Per-cell
1224
+ NLL lists are dumped so the OFFLINE joint-fit (additive vs +bilinear, Freedman-Lane
1225
+ control) can measure whether the model's NLL surface over the (A,B) grid carries
1226
+ non-additive structure — the Y1 half of Fable's 해석 매트릭스 (Y3 = model-free corpus).
1227
+ Engine-native (py 2-production numpy, a_eval_py_canonical) → TERMINAL-eligible.
1228
+ manifest = {"win":T,"score_len":K,"items":[{"text":"<span>","a":i,"b":j},…]}."""
1229
+ import numpy as np # numpy is function-local throughout evaluate.py (no module import)
1230
+ ckpt = argv[0]
1231
+ spec_path = evaluate_strval(argv[1:], "--interaction-lift", "")
1232
+ out_path = evaluate_strval(argv[1:], "--out", "interaction_lift.json")
1233
+ spec = json.load(open(spec_path))
1234
+ T = evaluate_intval(argv[1:], "--win", int(spec.get("win", 64)))
1235
+ score_len = evaluate_intval(argv[1:], "--score-len", int(spec.get("score_len", 8)))
1236
+ items = spec["items"]
1237
+ print("=== anima evaluate --interaction-lift — joint interaction-lift (H_9255) ===")
1238
+ print("ckpt: " + ckpt)
1239
+ print("spec: %s (%d windows · T=%d · score_len=%d · read-only NLL surface)" %
1240
+ (spec_path, len(items), T, score_len))
1241
+ W = clm.clm_load_weights(ckpt)
1242
+ if not W.get("ok"):
1243
+ print("ERROR: ckpt not decodable (clm): " + ckpt)
1244
+ return 1
1245
+ V = int(W["V"])
1246
+ cells = {}
1247
+ n_done = 0
1248
+ for it in items:
1249
+ text = it["text"]
1250
+ key = "%d,%d" % (int(it["a"]), int(it["b"]))
1251
+ tok = clm._seed_to_tok(text, T) # [T] right-aligned bytes
1252
+ logits = clm._fwd_logits(W, tok, T) # [T, V]; pos i predicts tok[i+1]
1253
+ # NLL over the last `score_len` scored positions (the continuation after both concepts)
1254
+ lo = max(0, T - 1 - score_len)
1255
+ nlls = []
1256
+ for i in range(lo, T - 1):
1257
+ row = logits[i]
1258
+ m = float(np.max(row))
1259
+ lse = m + math.log(float(np.sum(np.exp(row - m))) + 1e-30)
1260
+ tgt = int(tok[i + 1])
1261
+ nlls.append(lse - float(row[tgt])) # -log softmax[tgt]
1262
+ cells.setdefault(key, []).append(float(np.mean(nlls)) if nlls else 0.0)
1263
+ n_done += 1
1264
+ if n_done % 200 == 0:
1265
+ print(" [ilift #%d/%d]" % (n_done, len(items)), flush=True)
1266
+ summary = {k: {"nll_mean": float(np.mean(v)), "n": len(v)} for k, v in cells.items()}
1267
+ json.dump({"ckpt": ckpt, "T": T, "score_len": score_len, "n_windows": len(items),
1268
+ "n_cells": len(cells), "cells": cells, "summary": summary},
1269
+ open(out_path, "w"), ensure_ascii=False)
1270
+ print(json.dumps({"ckpt": ckpt, "T": T, "n_windows": len(items),
1271
+ "n_cells": len(cells), "out": out_path}, ensure_ascii=False))
1272
+ return 0
1273
+
1274
+
1275
+ def main(argv):
1276
+ if len(argv) >= 1 and argv[0] in ("-h", "--help"):
1277
+ evaluate_usage()
1278
+ return 0
1279
+ # H_9212 ③ per-cell dispatch wiring self-test (torch-free · NO decode · internal subprocess)
1280
+ if len(argv) >= 1 and argv[0] == "--selftest-rho-cells":
1281
+ cok, cchecks = _selftest_rho_cells()
1282
+ for nm, v in cchecks:
1283
+ print((" PASS " if v else " FAIL ") + nm)
1284
+ try:
1285
+ import rho_fan as _rf
1286
+ rok = _rf._rho_fan_cells_selftest()
1287
+ for nm, v in rok["checks"]:
1288
+ print((" PASS " if v else " FAIL ") + "[rho_fan] " + nm)
1289
+ cok = cok and rok["ok"]
1290
+ except Exception as e: # pragma: no cover
1291
+ print(" FAIL rho_fan cells selftest import: " + str(e))
1292
+ cok = False
1293
+ print("SELFTEST rho-cells: " + ("OK" if cok else "FAIL"))
1294
+ return 0 if cok else 1
1295
+ # --result-file <f>: write ALL output to <f> and keep fd 1 (stdout) silent. The hexa
1296
+ # launcher runs evaluate via exec(), whose captured stdout pipe it closes after ~150s
1297
+ # (probe-confirmed: a child reaching ~150s gets EPIPE on its next fd-1 write, but the
1298
+ # child itself is NOT killed). A 303M numpy decode runs for minutes; if it writes to
1299
+ # fd 1 it dies on BrokenPipe mid-battery. Redirecting our stdout to a file means fd 1
1300
+ # stays silent → the child survives the pipe close, finishes the full G0-G6 battery,
1301
+ # and the launcher cats <f> in a SECOND fresh exec (fast, well under the limit).
1302
+ if "--result-file" in argv:
1303
+ i = argv.index("--result-file")
1304
+ f = argv[i + 1]
1305
+ argv = argv[:i] + argv[i + 2:]
1306
+ sys.stdout = open(f, "w", buffering=1)
1307
+ # H_9200 ρ-AXON — reach-layer panel (G0-G6 → ρ-AXON, cli/rho_axon.py). Strip + set
1308
+ # the process-global so evaluate_run renders the ρ-AXON panel instead of G0-G6.
1309
+ global _RHO_AXON
1310
+ if "--rho-axon" in argv:
1311
+ argv = [a for a in argv if a != "--rho-axon"]
1312
+ _RHO_AXON = True
1313
+ # H_9200 E1 — SLW gated-write forward-slot eval-time controls (strip + set the
1314
+ # process-global switches in core/decode; frozen-first, no retraining):
1315
+ # --slot-off force γ=0 => bit-exact base trunk (slot-ablation control)
1316
+ # --slot-shuffle N permute the WRITE address with seed N (shuffle-bind control)
1317
+ _slot_off = "--slot-off" in argv
1318
+ if _slot_off:
1319
+ argv = [a for a in argv if a != "--slot-off"]
1320
+ _slot_shuffle = None
1321
+ if "--slot-shuffle" in argv:
1322
+ i = argv.index("--slot-shuffle")
1323
+ _slot_shuffle = int(argv[i + 1])
1324
+ argv = argv[:i] + argv[i + 2:]
1325
+ if _slot_off or _slot_shuffle is not None:
1326
+ clm.set_slw_controls(gamma_override=(0.0 if _slot_off else None),
1327
+ shuffle_seed=_slot_shuffle)
1328
+ # --system-g1: RECOMBINATION-RELOCATION pipe (card H_9035). Strip the flag and
1329
+ # route the remaining <ckpt> [--gen N] to the system-G1 harness.
1330
+ if "--system-g1" in argv:
1331
+ i = argv.index("--system-g1")
1332
+ argv = argv[:i] + argv[i + 1:]
1333
+ return system_g1_run(argv)
1334
+ # --dump-hidden <prompts.json>: read-only penultimate-hidden dump (ρ·weave / γ
1335
+ # binding-lane probe H_9235). argv[0]=ckpt; dump_hidden_run reads --dump-hidden/--out.
1336
+ if "--dump-hidden" in argv:
1337
+ return dump_hidden_run(argv)
1338
+ # --interaction-lift <manifest.json>: read-only engine-native joint interaction-lift
1339
+ # NLL surface (H_9255). argv[0]=ckpt; interaction_lift_run reads --interaction-lift/--out.
1340
+ if "--interaction-lift" in argv:
1341
+ return interaction_lift_run(argv)
1342
+ # --probe <spec.json>: matched-surface G1 probe (card H_6189). argv[0]=ckpt; probe_run
1343
+ # reads --probe/--gen from the tail. Greedy raw-continuation dump for offline scoring.
1344
+ if "--probe" in argv:
1345
+ return probe_run(argv)
1346
+ return evaluate_run(argv)
1347
+
1348
+
1349
+ if __name__ == "__main__":
1350
+ sys.exit(main(sys.argv[1:]) or 0)