Files
BFM-decomp/tools/masked_scorer.py
T
Drew T 5984749421 feat(phase-24): T2 — floor-free relocation-masked permuter scorer (-drz)
- tools/masked_diff.py: shared objdump -drz masking oracle (jal/j 26-bit + HI16/LO16
  immediate mask; -z keeps nop runs -> no GTE under-count). object-vs-object (permuter,
  +reloc-operand equality) and object-vs-.s (match_one) modes.
- match_one.py refactored onto masked_diff (-dr -> -drz); regression-clean on count-exact
  seeds (35/52/72/110, leaf-MATCHes), output format unchanged (gate_stage/grinder compatible)
- tools/masked_scorer.py MaskedScorer: drop-in for decomp-permuter's Scorer; scores masked
  .text closeness (bottoms out at 0) not the stock mnemonic-diff floor
- tools/permuter/run_masked.py: rebinds src.main.Scorer -> MaskedScorer (NO submodule edit,
  R3/R20); p16_permute.run_permuter wired to it
- VALIDATED: masked_self=0; masked_cand~match_one; STOCK floor 1750-2100 vs masked 36-77
  (the wander cause); live permuter base score = masked 77 (not stock 1930), descends to 74
- R14 SELF-CORRECTION: -drz confirms func_80132784 is 4/400 count-exact (T1's -dr 204 was a
  16-nop-collapse artifact); backlog re-logged close 4. The flagship IS 4 ins away.
- no build-input changed (136/136 untouched); 2 compile FAILs surfaced -> T3 (GTE asm, typedefs)
2026-07-02 23:10:54 -06:00

49 lines
2.4 KiB
Python

#!/usr/bin/env python3
"""masked_scorer.py — a drop-in for decomp-permuter's Scorer that scores relocation-masked .text
closeness (Phase 24 T2). Rebound over src.main.Scorer by tools/permuter/run_masked.py.
The stock Scorer diffs objdump MNEMONIC sequences and symbolizes relocations, penalizing any
symbol/addend divergence (scorer.py PENALTY_REGALLOC/REORDERING/INSERTION). For BFM's dotless
symbols (func_800…/D_800…) its `field_matches_any_symbol` wildcard never fires, so the score floats
on a nonzero FLOOR the search can't cross (func_80176D94 base~225/best~210/never 0) — the random walk
diverges. This scorer instead returns masked_diff.diff_object_object(cand, target): same-count,
non-reloc words identical, reloc/jal slots matching opcode+regs AND symbol+addend. It bottoms out at
exactly 0 ⟺ the two functions link to identical bytes, giving the search a gradient that reaches 0.
Same contract as Scorer: __init__(target_o, *, stack_differences, algorithm, debug_mode);
score(cand_o)->(int,str); PENALTY_INF. NOT the byte-gate — the whole-binary rebuild (harvest_verify)
is the sole arbiter (G3/P9); a permuter output-0 is a strong CANDIDATE to gate, not a bank.
"""
import hashlib
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import masked_diff
class MaskedScorer:
PENALTY_INF = 10 ** 9
def __init__(self, target_o, *, stack_differences=False, algorithm="", debug_mode=False):
# target.o and cand.o each hold exactly ONE function (p16_permute assembles/compiles one),
# so whole-.text is unambiguous — no function-name plumbing needed.
self.target_o = target_o
self.debug_mode = debug_mode
self.target = masked_diff.insns_from_object(target_o)
def score(self, cand_o):
if not cand_o:
return (self.PENALTY_INF, "")
cand = masked_diff.insns_from_object(cand_o)
if not cand:
return (self.PENALTY_INF, "")
d = masked_diff.diff_object_object(cand, self.target)
# deterministic content hash of the candidate's asm (words + relocs) so the permuter dedups
# identical candidates — mirrors the stock Scorer's "hash the objdump output" second field.
h = hashlib.sha256(
"".join(f"{i['word']:08x}:{i['reloc_kind'] or ''}:{i['reloc_op'] or ''}"
for i in cand).encode()
).hexdigest()
return (d, h)