mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
105 lines
5.0 KiB
Python
105 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
"""tools/wave_card_fuel.py — the per-target CARD FUEL an agent pack carries, as ONE oracle (R33).
|
|
|
|
These helpers were defined inside `build_wave_atlas.py`, which parses argv at import and so cannot
|
|
be reused as a library. Extracted VERBATIM (P31 S63) so `build_wave_atlas` and the Claude wave's
|
|
`tools/t5_cards.py` compute the SAME fuel rather than two copies that drift:
|
|
|
|
home_tu(binary, fn) the stub's home .c (the gate group key)
|
|
_tu_bodies(tu_path) {fn: body} for every function DEFINED (banked) in a TU
|
|
tu_neighbours(...) already-banked functions in the target's OWN TU, ranked by symbols
|
|
shared with the target's .s relocations (§194-E, the highest-yield
|
|
source measured; wave t5a's agents rediscovered it by hand and said so)
|
|
|
|
Why it exists (byte-measured, P31 S63): `claude_wave_packs` looked cards up by BARE FUNCTION NAME,
|
|
and overlays share names at equal addresses — so ALL 48 of wave t5a's targets (and 15 of T4's 20)
|
|
received ANOTHER binary's card: wrong twin, wrong TU neighbours, wrong declarations. Nine agents
|
|
reported it independently. The fix is a (binary, fn) key — which, for never-carded K-class work,
|
|
means there is no card at all. So the fuel has to be BUILT for the target itself, from here."""
|
|
import os, sys
|
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
sys.path.insert(0, os.path.join(REPO, 'tools'))
|
|
import corpus
|
|
|
|
_open = {}
|
|
def _stubmap(binary):
|
|
if binary not in _open:
|
|
# corpus.stubs() is addr -> Stub; the NAME lives on the record
|
|
_open[binary] = {st.symbol: st for st in corpus.stubs(binary).values()}
|
|
return _open[binary]
|
|
def is_open(binary, fn):
|
|
return fn in _stubmap(binary)
|
|
def home_tu(binary, fn):
|
|
"""The stub's home .c — this is the GATE GROUP KEY (gate_lane groups by (binary, src))."""
|
|
st = _stubmap(binary).get(fn)
|
|
return st.path if st else None
|
|
_TU_BODIES = {}
|
|
def _tu_bodies(tu_path):
|
|
"""{fn: body_text} for every function DEFINED in a TU (banked C only -- stubs are INCLUDE_ASM).
|
|
|
|
Brace-matched from each definition so a symbol is attributed to the function that uses it, not
|
|
to the file. Memoized per TU: a wave draws many cards from one .c."""
|
|
if tu_path in _TU_BODIES:
|
|
return _TU_BODIES[tu_path]
|
|
out = {}
|
|
try:
|
|
txt = open(tu_path, errors='replace').read()
|
|
except (OSError, TypeError):
|
|
_TU_BODIES[tu_path] = out
|
|
return out
|
|
import re as _re
|
|
for m in _re.finditer(r'^[A-Za-z_][\w \t\*]*?\b(\w+)\s*\([^;{]*\)\s*\{', txt, _re.M):
|
|
i, depth = m.end() - 1, 0
|
|
while i < len(txt):
|
|
if txt[i] == '{': depth += 1
|
|
elif txt[i] == '}':
|
|
depth -= 1
|
|
if depth == 0: break
|
|
i += 1
|
|
out[m.group(1)] = txt[m.start():i + 1]
|
|
_TU_BODIES[tu_path] = out
|
|
return out
|
|
_SYM = None
|
|
def tu_neighbours(binary, fn, tu_path, asm_file, topn=2):
|
|
"""Banked functions in the card's OWN TU, ranked by symbols shared with the target's asm.
|
|
|
|
The symbols are read from the TARGET's .s (its relocation operands), so this is evidence about
|
|
the function being drafted, not about the file. See §194-E for why the card needs this at all."""
|
|
global _SYM
|
|
if not tu_path or not asm_file:
|
|
return []
|
|
import re as _re
|
|
if _SYM is None:
|
|
# OPERANDS ONLY. A splat .s carries the encoded WORD in a comment column, so a naive
|
|
# "[A-Z]\w{3,}" reads `D8FFBD27` and `CC00228E` as symbol names and the overlap score
|
|
# becomes noise (measured: 34 "symbols" for one function, 31 of them hex words).
|
|
# Symbols reach the .s in exactly three shapes: a jal target, and %hi()/%lo() operands.
|
|
_SYM = _re.compile(r'\b(?:jal\s+(\w+)|%[hl][io]\(([\w+]+)\))')
|
|
try:
|
|
raw = _SYM.findall(open(asm_file, errors='replace').read())
|
|
except OSError:
|
|
return []
|
|
want = {(a or b).split('+')[0] for a, b in raw if (a or b)}
|
|
want.discard(fn)
|
|
if not want:
|
|
return []
|
|
scored = []
|
|
for other, body in _tu_bodies(tu_path).items():
|
|
if other == fn:
|
|
continue
|
|
shared = [s for s in want if s in body]
|
|
if len(shared) >= 2:
|
|
scored.append((len(shared), other, sorted(shared)[:6]))
|
|
scored.sort(key=lambda x: -x[0])
|
|
if not scored:
|
|
# FALL BACK TO THE BEST SINGLE SHARED SYMBOL rather than emitting nothing. One shared
|
|
# callee is weak evidence, but the card reports the count so the agent can weigh it, and a
|
|
# weak same-TU lead still beats the zero-locality state §194-E measured.
|
|
weak = sorted(((len([s for s in want if s in body]), other)
|
|
for other, body in _tu_bodies(tu_path).items() if other != fn), reverse=True)
|
|
if weak and weak[0][0] == 1:
|
|
o = weak[0][1]
|
|
body = _tu_bodies(tu_path)[o]
|
|
return [{'fn': o, 'shared': 1, 'symbols': [s for s in sorted(want) if s in body][:6]}]
|
|
return [{'fn': o, 'shared': n, 'symbols': syms} for n, o, syms in scored[:topn]]
|