diff --git a/tools/api_agent.py b/tools/api_agent.py index 712524f2d..58b9f8f5a 100644 --- a/tools/api_agent.py +++ b/tools/api_agent.py @@ -509,6 +509,26 @@ def _fuel(t, card): out.append( "⚠ Do NOT hunt in `sibs` for a body to read: they are drawn from the OPEN set by") out.append( "construction, so they are stubs 100% of the time. They tell you what your work is") out.append( "WORTH, not where an answer is.") + # 6.3 TELL COUNTS (P31 S60). Counts derived from the TARGET's own bytes by atlas_features, + # joined onto the card by build_wave_atlas. They are a CHECKLIST, not a hint: the target + # provably contains this many sites, so a draft that produces fewer has missed some. + _t = card.get('tells') or {} + _bits = [] + if _t.get('extpair'): + _bits.append(f"{_t['extpair']} sign-extend pair site(s) (sll/sra, §172b EXTPAIR)") + if _t.get('dupselect'): + _bits.append(f"{_t['dupselect']} repeated two-arm select(s) (§172b SELECT)") + if _t.get('magic_div'): + _bits.append(f"{_t['magic_div']} magic-division site(s)") + if _t.get('sign_lh'): + _bits.append("signed halfword load(s) (lh, not lhu)") + if _t.get('sign_lb'): + _bits.append("signed byte load(s) (lb, not lbu)") + if _bits: + out.append("\nTELLS COUNTED IN THE TARGET (from its bytes, not a guess) — expect: " + + "; ".join(_bits) + ".") + out.append("If your draft emits fewer of these than the count says, you have missed " + "sites — find them before spending a turn on anything else.") if card.get('lever'): out.append(f"\nThe atlas labels this card's lever: {card['lever']}.") crib = LEVER_CRIB.get(card['lever']) diff --git a/tools/build_wave_atlas.py b/tools/build_wave_atlas.py index aaa1d5bb0..6d3c7fde9 100644 --- a/tools/build_wave_atlas.py +++ b/tools/build_wave_atlas.py @@ -706,6 +706,50 @@ try: except ImportError: print('build_wave_atlas: validate_targets not importable -- VALIDITY GATE SKIPPED', file=sys.stderr) +# 6.3 TELL COUNTS ON THE CARD (P31 S60, tells-lane-s59.md). A tell-lever card named a lever and +# then made the agent go find its sites: the card said "extend-tell" and nothing about WHERE or HOW +# MANY. atlas_features already computed the per-function detector counts into .run/feat..jsonl +# at atlas time, so this is a join, not a computation — one dict load per binary in the wave. +# Attached for EVERY card, not just tell-lever ones: a sll/sra pair site or a repeated select is +# worth knowing about whatever lever the card was drawn under. +_feat_cache = {} +def _feat_rows(binary): + if binary not in _feat_cache: + rows = {} + try: + with open(f'.run/feat.{binary}.jsonl', errors='replace') as fh: + for ln in fh: + ln = ln.strip() + if not ln.startswith('{'): + continue + try: + r = json.loads(ln) + except ValueError: + continue + try: + rows[int(str(r.get('addr')), 16)] = r + except (TypeError, ValueError): + pass + except OSError: + rows = {} + _feat_cache[binary] = rows + return _feat_cache[binary] + +_n_tells = 0 +for c in wave: + try: + _r = _feat_rows(c['binary']).get(int(str(c.get('addr')), 16)) + except (TypeError, ValueError): + _r = None + if not _r: + continue + t = {k: _r.get(k, 0) for k in ('extpair', 'dupselect', 'magic_div', 'sign_lh', 'sign_lb')} + if any(t.values()): + c['tells'] = t + _n_tells += 1 +print(f"tell counts attached to {_n_tells} of {len(wave)} card(s) " + f"(extpair/dupselect/magic_div/sign_lh/sign_lb from .run/feat..jsonl)", file=sys.stderr) + json.dump(wave, open(a.out, 'w'), indent=1) if a.one_per_gid: # Only the siblings of gids that ACTUALLY made the wave are actionable this session; the rest