# motor-01.py - MOTOR rig: frame rotation / content / perturbation vs saturation # Implements PREREG-MOTOR-01.md + LOGGED AMENDMENT 1, patched per # RESULT-2026-08-27.md LOGGED CORRECTION 1 (MOTOR-02 protocol). # Rig conventions reused exactly from tools/saturation-predicts.py: seeded LCG # instance generator, DP true optimum, achieved/optimal scoring, # variance=/MOVE= log lines, Ollama/claude-CLI calling. # # PROTOCOL (load-bearing points): # - Every scored rep is an INDEPENDENT FRESH call containing exactly # [user: probe prompt, assistant: "(answered)", user: original question]. # Probe answers NEVER enter re-ask context (leakage surgery). # - VOIDING ASSERTION (Correction 1, defect 3): the voiding unit is the # PHRASING within a cell. If a phrasing's throwaway manip check returns # ok:false (or errors), ALL reps using that phrasing index in that cell are # VOIDED: excluded from mean/variance/permutation, counted in voided=. # A mechanical assertion enforces that no scored rep carries a failed # phrasing. # - Manipulation checks run in SEPARATE THROWAWAY sessions per phrasing, # logged per cell BEFORE that cell's re-ask scoring is printed. # - Arms: # A baseline re-ask only # B0 TRUE ENUMERATING hint (3 items in the DP optimum; 3 phrasings) # B1 TRUE NON-ENUMERATING hint ("optimal total worth achievable is V", # V = DP optimum value, no items named; 3 phrasings) # B2 FALSE ENUMERATING hint (3 items NOT in the optimum, same format # as B0; 3 phrasings) # C1 motor-evidence arm: frame-only APPROACH-ONLY probes eliciting no # item selection (3 phrasings) # C2 demand-characteristics control: variety-demand pick-eliciting # probes (3 phrasings) # D0 TOKEN-ONLY null: N irrelevant tokens prepended as a plain text # block inside the SAME user message (no assistant turn, no model # exchange), N matched to the C1 phrasing's word count per rep index # D1 null: irrelevant text with a real model turn (summarize) # D2 null: matched-effort irrelevant task (puzzle; 3 phrasings) # All arms round-robin phrasings by rep index. Probe word counts logged # for EVERY arm in the JSON. # - STRATIFIED PERMUTATION is the primary statistic: labels permuted within # phrasing strata (stratum = rep index mod 3); pooled permutation also # reported; within-phrasing variance average reported per arm. # - Concurrent eligibility screen unchanged (arm A variance THIS RUN # < 0.0005 or the level is VOID for P1). # # Usage: python tools/motor-01.py # [--arms A,B0,B1,B2,C1,C2,D0,D1,D2] [--temp T] [--tag motor-02] # = comma-separated item counts, e.g. "40,50,70" # reps: default 5 - USE 21 FOR VARIANCE CLAIMS (multiple of 3 balances # phrasing strata). import json, os, re, sys, time, random, urllib.request, statistics as st, subprocess, shutil sys.path.insert(0, 'tools') import robust_json as R USAGE = ("Usage: python tools/motor-01.py " "[--arms A,B0,B1,B2,C1,C2,D0,D1,D2] [--temp T] [--tag NAME]\n" " levels: comma-separated item counts, e.g. 40,50,70\n" " reps: default 5 - use 21 for variance claims (multiple of 3\n" " balances phrasing strata)\n" " arms: comma-separated; C1 is the only motor-evidence arm") # ---------------------------------------------------------------- CLI ---- if '--help' in sys.argv or '-h' in sys.argv: print(USAGE); sys.exit(0) args, flags, i = [], {}, 1 argv = sys.argv while i < len(argv): a = argv[i] if a.startswith('--'): k, eq, v = a[2:].partition('=') if not eq and i + 1 < len(argv) and not argv[i + 1].startswith('--'): v = argv[i + 1]; i += 1 flags[k] = v else: args.append(a) i += 1 REPS = int(args[0]) if len(args) > 0 else 5 MODEL = args[1] if len(args) > 1 else 'qwen2.5:7b' LEVELS = [('n%d' % int(x), int(x)) for x in (args[2] if len(args) > 2 else '12,16,20,30').split(',')] ALL_ARMS = ['A', 'B0', 'B1', 'B2', 'C1', 'C2', 'D0', 'D1', 'D2'] ARMS = [x.strip().upper() for x in flags.get('arms', ','.join(ALL_ARMS)).split(',') if x.strip()] for x in ARMS: assert x in ALL_ARMS, 'unknown arm %s (valid: %s)' % (x, ','.join(ALL_ARMS)) TEMP = float(flags['temp']) if 'temp' in flags and flags['temp'].lower() not in ('none', '-', '') else None TAG = flags.get('tag', 'motor-01') OUT = 'projects/pando/motor' os.makedirs(OUT, exist_ok=True) # ------------------------------------------------- rig (sealed conventions) ---- def lcg(seed): s = seed & 0x7fffffff while True: s = (1103515245 * s + 12345) & 0x7fffffff yield s def make(nitems, seed): g = lcg(seed) items = [] for i in range(nitems): cost = 3 + next(g) % 28 noise = (next(g) % 21) - 10 items.append({'id': 'x%02d' % i, 'cost': cost, 'value': max(1, int(cost * (1 + noise / 25.0)))}) return items, int(sum(it['cost'] for it in items) * 0.40) def optimum_and_set(items, b): n = len(items) dp = [[0] * (b + 1) for _ in range(n + 1)] for i in range(1, n + 1): it = items[i - 1] for x in range(b + 1): dp[i][x] = dp[i - 1][x] if it['cost'] <= x: cand = dp[i - 1][x - it['cost']] + it['value'] if cand > dp[i][x]: dp[i][x] = cand chosen = [] x = b for i in range(n, 0, -1): if dp[i][x] != dp[i - 1][x]: chosen.append(items[i - 1]['id']) x -= items[i - 1]['cost'] return dp[n][b], chosen def score(items, b, picks): by = {it['id']: it for it in items} seen = set() cost = val = 0 for p in (picks or []): if p not in by or p in seen: return 0 seen.add(p) cost += by[p]['cost'] val += by[p]['value'] return val if cost <= b else 0 # ------------------------------------------------- model calling (as sealed) ---- CLAUDE_BIN = shutil.which('claude.cmd') or shutil.which('claude') NEUTRAL = os.path.join(os.environ.get('TEMP', '.'), 'motor-neutral') os.makedirs(NEUTRAL, exist_ok=True) def ask_messages(messages): if MODEL.startswith('claude'): parts = [] for m in messages[:-1]: parts.append('[Earlier %s turn]\n%s' % (m['role'], m['content'])) parts.append(messages[-1]['content']) prompt = '\n\n'.join(parts) try: r = subprocess.run([CLAUDE_BIN, '-p', '--model', MODEL], input=prompt, capture_output=True, text=True, timeout=400, cwd=NEUTRAL) return (r.stdout or '').strip() or '[ERR empty]' except Exception as e: return '[ERR %s]' % str(e)[:50] p = {'model': MODEL, 'stream': False, 'messages': messages} if TEMP is not None: p['options'] = {'temperature': TEMP} for a in range(2): try: r = json.load(urllib.request.urlopen(urllib.request.Request( 'http://localhost:11434/api/chat', data=json.dumps(p).encode(), headers={'Content-Type': 'application/json'}), timeout=400)) return r.get('message', {}).get('content', '') except Exception as e: if a < 1: time.sleep(4); continue return '[ERR %s]' % str(e)[:50] def ask(prompt): return ask_messages([{'role': 'user', 'content': prompt}]) # ------------------------------------------------- prompts ---- def catalogue(items): return "\n".join(" %s: cost %d, worth %d" % (i['id'], i['cost'], i['value']) for i in items) def prompt_for(items, b, hint_line=None): p = ("Budget %d. Choose the set of items maximising total worth without exceeding the " "budget.\n\nITEMS:\n%s\n\n" % (b, catalogue(items))) if hint_line: p += hint_line + "\n\n" p += 'Respond ONLY as JSON: {"picks":["x00","x03"]}' return p # --- B arms: three phrasing templates each, same content per arm --- def b0_probes(items, b, opt_set): ids = ', '.join(opt_set[:3]) return [prompt_for(items, b, h % ids) for h in ( "KNOWN: these items are in the optimal set: %s", "It is established that the optimal selection includes these items: %s", "A verified solver reports that these items belong to the best possible selection: %s", )] def b1_probes(items, b, opt): return [prompt_for(items, b, h % opt) for h in ( "KNOWN: the optimal total worth achievable under this budget is %d.", "It is established that the best possible total worth under this budget is %d.", "A verified solver reports that the maximum achievable total worth is %d.", )] def b2_probes(items, b, opt_set): non_opt = [it['id'] for it in items if it['id'] not in opt_set][:3] ids = ', '.join(non_opt) return [prompt_for(items, b, h % ids) for h in ( "KNOWN: these items are in the optimal set: %s", "It is established that the optimal selection includes these items: %s", "A verified solver reports that these items belong to the best possible selection: %s", )], non_opt # --- C1: frame-only, APPROACH-ONLY probes (no item selection elicited) --- def c1_probes(items, b): cat = catalogue(items) return [ ("You are the quartermaster loading a ship's hold before a long voyage. The hold " "carries at most %d units of weight. Each crate below has a weight (cost) and a " "trade worth at the destination port.\n\nCRATES:\n%s\n\n" "Describe in two sentences your approach to loading, without naming any items. " "Do not list or select any crates." % (b, cat)), ("Budget %d.\n\nITEMS:\n%s\n\n" "Explain in two sentences what would make a selection poor under this budget, " "without naming any items. Do not list or select any items." % (b, cat)), ("You are assembling a ration kit for an expedition. The kit holds at most %d units " "of weight. Each ration below has a weight (cost) and a nutritional worth.\n\n" "RATIONS:\n%s\n\n" "Describe in two sentences your approach to assembling the kit, without naming any " "items. Do not list or select any rations." % (b, cat)), ] # --- C2: variety-demand, pick-eliciting probes (demand control) --- def c2_probes(items, b): cat = catalogue(items) return [ ("Budget %d. Now solve the INVERSE problem: choose the WORST valid selection - " "the non-empty set of items with the LOWEST total worth that still uses at least " "half the budget without exceeding it.\n\nITEMS:\n%s\n\n" 'Respond ONLY as JSON: {"picks":["x00","x03"]}' % (b, cat)), ("Budget %d. State two maximally different near-optimal packings - two selections " "within budget that are both good but share as few items as possible.\n\n" "ITEMS:\n%s\n\n" 'Respond ONLY as JSON: {"packings":[["x00","x03"],["x01","x05"]]}' % (b, cat)), ("Budget %d. Propose a valid selection as DIFFERENT as possible from the obvious " "first-instinct choice, while staying within budget.\n\nITEMS:\n%s\n\n" 'Respond ONLY as JSON: {"picks":["x00","x03"]}' % (b, cat)), ] # --- irrelevant texts (D0 token source, D1 summarize source) --- IRRELEVANT_TEXTS = [ ("A broad ridge of high pressure settled over the plains this morning, bringing clear " "skies and light winds out of the northwest at five to ten miles per hour. Temperatures " "climbed slowly through the morning and are expected to peak in the mid seventies by " "late afternoon, with humidity remaining low. Overnight a weak cold front approaches " "from the north, and scattered high clouds drift in ahead of it, though no rain is " "expected before dawn. By midday tomorrow the front stalls, winds turn easterly, and a " "slight chance of isolated showers develops along the boundary south of the river. "), ("The community garden opened its gates early this year after a mild spring. Volunteers " "spent the first weekend turning beds, spreading compost, and repairing the low fence " "along the east path. The tool shed received a new roof, and the rain barrels were " "cleaned and reconnected to the gutter line. Plots along the south wall warm earliest " "and were claimed first, mostly for tomatoes and beans. A notice board by the entrance " "lists watering rotations for the summer months, and a small bench was installed under " "the old pear tree near the compost bins for visitors who come to sit in the shade. "), ("The morning service departs the central terminus at seven fifteen and calls at the " "riverside halt, the old mill crossing, and the junction before reaching the coast by " "mid morning. A slower stopping service follows an hour later, adding four village " "platforms and a request stop at the heath. In the afternoon the pattern reverses, " "with the fast return running nonstop from the junction. On weekends an additional " "late evening working operates in each direction, and during the summer timetable a " "relief carriage is attached at the terminus to handle holiday traffic to the shore. "), ] def matched_words(text, target): return ' '.join((text * (target // len(text.split()) + 2)).split()[:target]) def d0_blocks(items, b): """Token-only null: plain irrelevant text blocks, N words matched to the corresponding C1 phrasing's word count. No model exchange.""" return [matched_words(t, len(c1.split())) for t, c1 in zip(IRRELEVANT_TEXTS, c1_probes(items, b))] def d1_probes(items, b): out = [] for text, c1 in zip(IRRELEVANT_TEXTS, c1_probes(items, b)): target = max(20, len(c1.split()) - 12) out.append("Summarize this paragraph in one sentence.\n\n" + matched_words(text, target)) return out D2_PROBES = [ ("Compute 4917 + 3086 - 1250, then multiply the result by 2. Show the final number " "and one line of working."), ("Unscramble each of these into an English word: 'RAGDEN', 'PLEMIS', 'TACLES'. " "Reply with the three words."), ("List every prime number between 9000 and 9040, and state how many you found."), ] PICK_ELICITING = ('B0', 'B1', 'B2', 'C2') # probes that elicit a pick-set APPROACH_ONLY = ('C1', 'D1', 'D2') # probes that must NOT contain one NO_EXCHANGE = ('A', 'D0') # no probe turn, no manip check def probes_for(arm, items, b, opt, opt_set): if arm == 'B0': return b0_probes(items, b, opt_set) if arm == 'B1': return b1_probes(items, b, opt) if arm == 'B2': return b2_probes(items, b, opt_set)[0] if arm == 'C1': return c1_probes(items, b) if arm == 'C2': return c2_probes(items, b) if arm == 'D0': return d0_blocks(items, b) # text blocks, not model probes if arm == 'D1': return d1_probes(items, b) if arm == 'D2': return list(D2_PROBES) return [] PICKS_SHAPE = re.compile(r'"(?:picks|packings)"\s*:\s*\[') def parse_pick_set(text): o = R.loads(text) or {} picks = o.get('picks') if isinstance(picks, list): return sorted(set(str(p) for p in picks)) packs = o.get('packings') if isinstance(packs, list) and packs and isinstance(packs[0], list): return sorted(set(str(p) for p in packs[0])) return None # ------------------------------------------------- logging / stats ---- log = [] LIVE = os.path.join(OUT, '%s-live.log' % TAG) def emit(s): print(s, flush=True); log.append(s) open(LIVE, 'a', encoding='utf-8').write(s + chr(10)) def pooled_perm(x, y, nperm=10000, seed=1234): """One-sided p for var(x) > var(y): pool all reps, permute labels.""" if len(x) < 2 or len(y) < 2: return None obs = st.pvariance(x) - st.pvariance(y) pool = list(x) + list(y) nx = len(x) rng = random.Random(seed) hits = 0 for _ in range(nperm): rng.shuffle(pool) d = st.pvariance(pool[:nx]) - st.pvariance(pool[nx:]) if d >= obs: hits += 1 return (hits + 1) / (nperm + 1) def strat_perm(x, sx, y, sy, nperm=10000, seed=5678): """PRIMARY statistic: one-sided p for var(x) > var(y), labels permuted WITHIN phrasing strata (stratum = rep index mod 3).""" if len(x) < 2 or len(y) < 2: return None obs = st.pvariance(x) - st.pvariance(y) strata = {} for v, s in zip(x, sx): strata.setdefault(s, [[], []])[0].append(v) for v, s in zip(y, sy): strata.setdefault(s, [[], []])[1].append(v) rng = random.Random(seed) hits = 0 for _ in range(nperm): px, py = [], [] for s, (xs, ys) in strata.items(): pool = xs + ys rng.shuffle(pool) px.extend(pool[:len(xs)]) py.extend(pool[len(xs):]) if len(px) < 2 or len(py) < 2: return None d = st.pvariance(px) - st.pvariance(py) if d >= obs: hits += 1 return (hits + 1) / (nperm + 1) def within_phrasing_var(scores, strata): groups = {} for v, s in zip(scores, strata): groups.setdefault(s, []).append(v) per = [st.pvariance(g) for g in groups.values() if len(g) >= 2] return st.mean(per) if per else None emit('MOTOR rig (PREREG-MOTOR-01 + Amendment 1 + Correction 1) tag=%s' % TAG) emit('model=%s repeats=%d arms=%s levels=%s' % (MODEL, REPS, ','.join(ARMS), ','.join(str(n) for _, n in LEVELS))) emit('=' * 72) emit('Leakage surgery in force; VOIDING UNIT = phrasing-within-cell: a failed') emit('throwaway manip check voids ALL reps on that phrasing index (excluded') emit('from mean/variance/permutation, counted in voided=). Stratified') emit('permutation (within phrasing strata) is the primary statistic.\n') results = {'tag': TAG, 'model': MODEL, 'reps': REPS, 'arms': ARMS, 'protocol': 'amendment1+correction1', 'levels': {}} for name, n in LEVELS: items, b = make(n, 90210 + n * 7919) opt, opt_set = optimum_and_set(items, b) orig = prompt_for(items, b) lv = {'n': n, 'optimum': opt, 'arms': {}} results['levels'][name] = lv emit('%s n=%d budget=%d optimum=%d' % (name, n, b, opt)) modal_A = None for arm in ALL_ARMS: if arm not in ARMS: continue probes = probes_for(arm, items, b, opt, opt_set) cell = {'probe_word_counts': [len(p.split()) for p in probes] if probes else []} lv['arms'][arm] = cell # --- manipulation check: throwaway sessions, one per phrasing, BEFORE # scoring; establishes the phrasing-ok map that gates voiding --- phr_ok = {} if arm not in NO_EXCHANGE: manip = [] for pi, probe in enumerate(probes): pa = ask(probe) if pa.startswith('[ERR'): ok = False manip.append({'phrasing': pi, 'ok': ok, 'why': 'probe error'}) elif arm in PICK_ELICITING: ps = parse_pick_set(pa) ok = (ps is not None) and (modal_A is not None) and (ps != modal_A) manip.append({'phrasing': pi, 'ok': ok, 'probe_picks': ps, 'modal_A': modal_A}) else: ok = bool(pa.strip()) and not PICKS_SHAPE.search(pa) manip.append({'phrasing': pi, 'ok': ok, 'answer_head': pa[:120]}) phr_ok[pi] = bool(ok) cell['manipulation'] = manip npass = sum(1 for v in phr_ok.values() if v) emit(' arm %-2s manip-check: %d/%d phrasings pass%s' % (arm, npass, len(probes), '' if npass == len(probes) else ' [phrasings %s VOIDED]' % ','.join(str(k) for k, v in phr_ok.items() if not v))) # --- treatment reps: fresh calls, redacted probe context --- scores, strata, voided_flags = [], [], [] for rep in range(REPS): stratum = rep % 3 if arm == 'A': msgs = [{'role': 'user', 'content': orig}] elif arm == 'D0': block = probes[rep % len(probes)] msgs = [{'role': 'user', 'content': block + '\n\n' + orig}] else: probe = probes[rep % len(probes)] msgs = [{'role': 'user', 'content': probe}, {'role': 'assistant', 'content': '(answered)'}, {'role': 'user', 'content': orig}] void = (arm not in NO_EXCHANGE) and not phr_ok.get(rep % len(probes), False) strata.append(stratum) voided_flags.append(void) t = ask_messages(msgs) if t.startswith('[ERR'): scores.append(None); continue o = R.loads(t) or {} picks = o.get('picks') if not isinstance(picks, list): scores.append(None); continue if arm == 'A': cell.setdefault('pick_sets', []).append(sorted(set(str(p) for p in picks))) scores.append(score(items, b, picks) / opt if opt else 0.0) cell['scores'] = scores cell['strata'] = strata cell['voided_flags'] = voided_flags if arm == 'A' and cell.get('pick_sets'): keys = [tuple(ps) for ps in cell['pick_sets']] modal_A = list(max(set(keys), key=keys.count)) cell['modal_pick_set'] = modal_A # scored set = valid AND not voided; MECHANICAL ASSERTION below scored = [(s, stx) for s, stx, v in zip(scores, strata, voided_flags) if s is not None and not v] # assertion: no scored rep carries a phrasing whose check failed for idx, (s, stx, v) in enumerate(zip(scores, strata, voided_flags)): if s is not None and not v and arm not in NO_EXCHANGE: assert phr_ok.get(idx % len(probes), False), \ 'VOIDING VIOLATION: scored rep %d of arm %s uses failed phrasing' % (idx, arm) nvoid = sum(1 for v in voided_flags if v) cell['voided'] = nvoid cell['valid'] = len(scored) if len(scored) < 1: emit(' arm %-2s EXCLUDED (no valid non-voided runs; voided=%d)' % (arm, nvoid)) continue vals = [s for s, _ in scored] var = st.pvariance(vals) mean = st.mean(vals) wpv = within_phrasing_var(vals, [stx for _, stx in scored]) cell['mean'] = mean; cell['variance'] = var; cell['within_phrasing_var'] = wpv base_mean = lv['arms'].get('A', {}).get('mean') move = (mean - base_mean) if (arm != 'A' and base_mean is not None) else 0.0 cell['move'] = move emit(' arm %-2s mean=%.3f variance=%.5f MOVE=%+.3f within-phr-var=%s (%d/%d valid, voided=%d)' % (arm, mean, var, move, ('%.5f' % wpv) if wpv is not None else 'n/a', len(scored), REPS, nvoid)) emit('') def scored_of(cell): return ([s for s, stx, v in zip(cell['scores'], cell['strata'], cell['voided_flags']) if s is not None and not v], [stx for s, stx, v in zip(cell['scores'], cell['strata'], cell['voided_flags']) if s is not None and not v]) # ------------------------------------------------- prereg readout ---- emit('=' * 72) emit('Prereg readout (Correction 1: stratified permutation is PRIMARY):') for name, lv in results['levels'].items(): A = lv['arms'].get('A', {}) a_var = A.get('variance') if a_var is not None: eligible = a_var < 0.0005 emit(' %-8s eligibility: A variance THIS RUN=%.5f -> %s' % (name, a_var, 'ELIGIBLE for P1' if eligible else 'VOID (not floor-saturated here)')) lv['eligible_P1'] = eligible for xa, ya in (('C1', 'A'), ('C1', 'D2')): X, Y = lv['arms'].get(xa, {}), lv['arms'].get(ya, {}) if 'variance' not in X or 'variance' not in Y: continue xs, xst = scored_of(X); ys, yst = scored_of(Y) ps = strat_perm(xs, xst, ys, yst) pp = pooled_perm(xs, ys) lv['perm_%s_gt_%s' % (xa, ya)] = {'stratified': ps, 'pooled': pp} emit(' %-8s %s var=%.5f %s var=%.5f p[var(%s)>var(%s)] stratified=%s pooled=%s' % (name, xa, X['variance'], ya, Y['variance'], xa, ya, ('%.4f' % ps) if ps is not None else 'n/a', ('%.4f' % pp) if pp is not None else 'n/a')) bs = {a: lv['arms'][a] for a in ('B0', 'B1', 'B2') if 'variance' in lv['arms'].get(a, {})} if bs: emit(' %-8s B-family (pinning test): %s' % (name, ' '.join('%s var=%.5f mean=%.3f' % (a, c['variance'], c['mean']) for a, c in bs.items()))) d0 = lv['arms'].get('D0', {}) if 'variance' in d0: emit(' %-8s D0 (token-only) var=%.5f MOVE=%+.3f' % (name, d0['variance'], d0.get('move', 0.0))) json.dump(results, open('%s/%s-results.json' % (OUT, TAG), 'w'), indent=2, default=str) open('%s/%s.log' % (OUT, TAG), 'a', encoding='utf-8').write('\n'.join(log) + '\n') emit('\nwrote %s/%s-results.json' % (OUT, TAG))