← Commit history

mode-ralph: coordMap-true mapping, capture freshness gate, tight #00b8b1 classifier, multi-target sampling, overlay-off plain measurement; skills: the two-CfT-buttons saga lessons (plain = converge to Chrome's native AUMID)

John Lauer ·74b7dd6c2b ·2mo ago ·parent c28b8f3
3 files changed +137−36
dev-skills/pup-mode-ralph/SKILL.md+29
@@ -54,6 +54,35 @@ shot in shotlog does not count.   nulled, so the targeted delete ran on nothing). Truly plain = VT_EMPTY property wipe + null   both WM_SETICON slots + sweep-delete ALL `Adom.Pup.*` keys + nudge. +## The two-CfT-buttons saga (2026-08-16, versions 2.0.221-226) — lessons burned in++- **Plain mode is CONVERGENCE, not erasure.** Chrome sets its own per-profile AUMID+  (`ChromeForTestin<hash>.<profile>.Default`) on every window and lazily RE-ASSERTS it. Wiping to+  VT_EMPTY split the taskbar into two "Google Chrome for Testing" groups (wiped vs re-tagged) that+  drifted further apart over time; an Explorer restart rebuilds the same split. The strip now sets+  Chrome's native id on every CfT window (orphans included) — idempotent against Chrome forever.+- **PROPVARIANT is 24 bytes on x64.** A 16-byte struct made GetValue silently return zeros, hiding+  Chrome's ids from every diagnostic for hours. Calibrate any property reader against a window+  KNOWN to have the property (a Chrome PWA) before trusting it.+- **Win11 bakes a GROUP button's icon at creation and only a simultaneous hide of ALL members+  (foreground included, focus restored after) rebuilds it.** Per-window nudges never destroy a+  group that other members keep alive; exempting the foreground window let its surviving button+  seed the rebuilt group with stale art.+- **Registry keys with no window property pointing at them are INERT.** Never sweep-delete them:+  ab caches icon registration by appId, so re-entry into tiles/aumid skipped the registry re-write+  and buttons came back named-but-artless. (Gen bumps i8/t7 recovered the damage.)+- **Harness integrity is half the war**: stale tray hwnd after an Explorer restart silently served+  hour-old screenshots (identical teal fractions across retries = the smell of a reused file — the+  freshness gate now hard-fails); rect-to-pixel mapping must use the capture's own coordMap, never+  an assumed monitor width; the taskbar reflows for seconds after a switch (re-read rects AFTER the+  shot, retry on drift); CfT's own BLUE logo has cyan pixels a loose teal rule miscounts (classifier+  is pinned to pup's #00b8b1: r<70, g>120, b>110, |g-b|<45); measure plain rounds with overlay OFF+  (the group badge favicon is racey and can legitimately be teal); judge the MODE via the best of+  several pup buttons, not one straggler of the #53 class.+- **Convergence takes up to ~20s after a rapid race** (ab's in-flight queue + verify pass + batch+  rebuild). The gate retries up to 5 times at 6s spacing to measure the steady state, not the+  transition.+ ## Traps  - The tray hwnd goes stale on Explorer restart (re-resolve `Shell_TrayWnd`; cached in
skills/pup-taskbar-states/SKILL.md+3−2
@@ -35,8 +35,9 @@ preview is DERIVED from the same sources listed here, so preview and reality can   registered in HKCU and stamped per-window. The Settings preview draws with the same algorithm. - **Category icons**: shipped `pup-cat-*.ico` set; the preview shows the REAL registered icon via   `/dashboard/stampicon?session=<sid>`.-- **Plain**: nothing is written at all — the button shows the browser exe's own icon; the preview-  shows it via `/dashboard/baseicon?profile=adom-you` (extracted from the running process).+- **Plain**: pup CONVERGES every CfT window onto Chrome's own native per-profile AUMID (wiping to+  empty is wrong: Chrome lazily re-asserts its id and the group splits in two). The button shows the+  browser exe's own icon; the preview shows it via `/dashboard/baseicon?profile=adom-you`. - **Overlay badge**: the compositor takes the active tab's favicon, puts it on an **adaptive   card** — dark card normally, LIGHT card when the favicon is dark-dominant on transparency   (the GitHub-cat case) — and hands the PNG to Windows. There is **no Adom mark on the badge**
tools/mode-ralph.sh+105−34
@@ -37,7 +37,7 @@ wait_done() { # poll apply-progress until done (max ~40s)   return 1 } -uia_buttons() { # prints name lines; also writes x|y|w|h|name rows to $SHOTDIR/rects.txt for the pixel gate+uia_buttons() { # $1 = rect file basename (default rects.txt); prints name lines; writes x|y|w|h|name rows   local PS='Add-Type -AssemblyName UIAutomationClient; $tray=[System.Windows.Automation.AutomationElement]::RootElement.FindFirst([System.Windows.Automation.TreeScope]::Children,(New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ClassNameProperty,"Shell_TrayWnd"))); $btns=$tray.FindAll([System.Windows.Automation.TreeScope]::Descendants,(New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ControlTypeProperty,[System.Windows.Automation.ControlType]::Button))); foreach($b in $btns){$r=$b.Current.BoundingRectangle; Write-Output ("{0}|{1}|{2}|{3}|{4}" -f [int]$r.X,[int]$r.Y,[int]$r.Width,[int]$r.Height,$b.Current.Name)}'   local B64; B64=$(python3 -c "import base64,sys;print(base64.b64encode(sys.stdin.read().encode('utf-16-le')).decode())" <<< "$PS")   timeout 90 $CLI shell_execute "{\"command\":\"powershell -NoProfile -EncodedCommand $B64\",\"reason\":\"mode-ralph: UIA taskbar button names + rects\"}" 2>&1 \@@ -45,7 +45,8 @@ uia_buttons() { # prints name lines; also writes x|y|w|h|name rows to $SHOTDIR/r import sys,json raw=sys.stdin.read();i=raw.find('{');d=json.loads(raw[i:]) if i>=0 else {} out=d.get('stdout') or ''-open('$SHOTDIR/rects.txt','w').write(out)+import os+open(os.path.join('$SHOTDIR', '${1:-rects.txt}'),'w').write(out) for l in out.splitlines():     p=l.split('|',4)     print(p[4] if len(p)==5 else l)"@@ -61,51 +62,116 @@ from PIL import Image, ImageDraw mode, label = sys.argv[1], sys.argv[2] import os SHOTDIR = os.environ.get("SHOTDIR","/tmp/mode-ralph-shots")-rows=[l.split('|',4) for l in open(f'{SHOTDIR}/rects.txt').read().splitlines() if l.count('|')>=4]-if mode=='plain':-    tgt=[r for r in rows if 'Chrome for Testing' in r[4]]-else:-    tgt=[r for r in rows if r[4].startswith('Adom Pup') or re.match(r'^(ti-crawl|mouser-run|digikey-run|wiki-in|wiki-out|dash-app|reclaimed|pup bridge)', r[4])]+def load(f):+    return [l.split('|',4) for l in open(f).read().splitlines() if l.count('|')>=4]+def pick(rows):+    if mode=='plain': return [r for r in rows if 'Chrome for Testing' in r[4]]+    return [r for r in rows if r[4].startswith('Adom Pup') or re.match(r'^(ti-crawl|mouser-run|digikey-run|wiki-in|wiki-out|dash-app|reclaimed|pup bridge)', r[4])]+rows=load(f'{SHOTDIR}/rects.txt')+tgt=pick(rows) if not tgt: print('PIXELGATE FAIL: no target button found'); sys.exit(1)-x,y,w,h = (int(tgt[0][i]) for i in range(4))-im=Image.open(f'{SHOTDIR}/{label}.png'); sc=im.width/2560.0-cx0,cx1=int(x*sc),int((x+w)*sc)-crop=im.crop((cx0,0,cx1,im.height)).convert('RGB')-px=list(crop.getdata()); teal=0-for r,g,b in px:-    if g>110 and b>100 and r<90 and abs(int(g)-int(b))<70: teal+=1-frac=teal/max(1,len(px))+# the taskbar REFLOWS for seconds after a mode switch (buttons materialize as stamps land) — a rect+# captured before the screenshot can point at a NEIGHBOR button by capture time (the RustDesk-swirl+# incident). rects2.txt is read AFTER the shot; if the target moved, the bar is still settling.+try:+    tgt2=pick(load(f'{SHOTDIR}/rects2.txt'))+    if not tgt2 or tgt2[0][4]!=tgt[0][4] or abs(int(tgt2[0][0])-int(tgt[0][0]))>4:+        print('PIXELGATE UNSTABLE: taskbar reflowed between rect read and screenshot'); sys.exit(2)+    tgt=tgt2+except FileNotFoundError:+    pass+# judge the MODE, not one unlucky button: a single straggler (the #53 hwnd-flaky class) can lag+# for tens of seconds while every sibling wears correct art — sample up to 4 targets, keep the best.+cands = tgt[:4] if mode!='plain' else tgt[:1]+import os.path+if not os.path.exists(f'{SHOTDIR}/{label}.png'):+    print('PIXELGATE FAIL: no fresh tray capture'); sys.exit(1)+im=Image.open(f'{SHOTDIR}/{label}.png')+import json as _json+def _map(x,w):+    try:+        cm=_json.load(open(f'{SHOTDIR}/{label}.json')).get('coordMap') or dict()+        i2s=cm.get('imageToScreen') or dict()+        sx=float(i2s.get('sx') or 0); ox=float(i2s.get('ox') or 0)+        if not sx: raise ValueError('no sx')+        a,b=int((x-ox)/sx),int((x+w-ox)/sx)+    except Exception:+        sc=im.width/2560.0; a,b=int(x*sc),int((x+w)*sc)+    a=max(0,min(im.width-2,a)); b=max(a+2,min(im.width,b))+    return a,b+best=None+for t in cands:+    x,y,w,h=(int(t[i]) for i in range(4))+    cx0,cx1=_map(x,w)+    crop=im.crop((cx0,0,cx1,im.height)).convert('RGB')+    W,H=crop.size+    px=crop.load(); teal=0; total=0+    for yy in range(H):+        for xx in range(W):+            if xx > W*0.45 and yy < H*0.62: continue+            r,g,b=px[xx,yy][:3]; total+=1+            # pup teal is #00b8b1: near-zero red, green~blue. CfT's own BLUE logo has cyan pixels+            # that a loose rule counted as teal (the 0.056-forever plain fail) — keep it tight.+            if g>120 and b>110 and r<70 and abs(int(g)-int(b))<45: teal+=1+    frac=teal/max(1,total)+    take = (best is None) or (frac > best[0] if mode != 'plain' else frac < best[0])+    if take:+        best=(frac,t,cx0,cx1)+frac,t,cx0,cx1=best ann=im.convert('RGB').copy(); d=ImageDraw.Draw(ann) d.rectangle([cx0-2,0,cx1+2,im.height-1],outline=(255,0,0),width=2) ann.resize((ann.width*2,ann.height*2),Image.LANCZOS).save(f'{SHOTDIR}/{label}-redbox.png')-crop.resize((crop.width*10,crop.height*10),Image.LANCZOS).save(f'{SHOTDIR}/{label}-btn10x.png')+im.crop((cx0,0,cx1,im.height)).resize(((cx1-cx0)*10,im.height*10),Image.LANCZOS).save(f'{SHOTDIR}/{label}-btn10x.png') ok = (frac < 0.02) if mode=='plain' else (frac > 0.04)-print(f"PIXELGATE {'PASS' if ok else 'FAIL'}: teal fraction {frac:.3f} on '{tgt[0][4][:50]}' ({'must be ~0 in plain' if mode=='plain' else 'pup art expected'})")+print(f"PIXELGATE {'PASS' if ok else 'FAIL'}: teal fraction {frac:.3f} on '{t[4][:50]}' ({'must be ~0 in plain' if mode=='plain' else 'best of ' + str(len(cands)) + ' pup buttons'})") sys.exit(0 if ok else 1) PYEOF } -shot_tray() { # shot_tray <label>-  local HW; HW=$(cat /tmp/tray_hwnd 2>/dev/null || echo 65952)+shot_tray() { # shot_tray <label> — MUST produce a FRESH capture; a stale file poisons every gate+  local HW; HW=$(cat /tmp/tray_hwnd 2>/dev/null || echo 0)+  if [ "$HW" = "0" ] || [ -z "$HW" ]; then HW=$(resolve_tray_hwnd); fi   timeout 60 $CLI desktop_screenshot_window "{\"hwnd\":$HW}" >/dev/null 2>&1-  local P; P=$(ls -t ~/project/screenshots/adom-bridge/window/*-${HW}*.full.png 2>/dev/null | head -1)-  [ -n "$P" ] && cp "$P" "$SHOTDIR/$1.png"+  local P; P=$(find ~/project/screenshots/adom-bridge/window -name "*-${HW}.full.png" -newermt "-45 seconds" 2>/dev/null | head -1)+  if [ -z "$P" ]; then+    # capture failed or hwnd stale (Explorer restart) — re-resolve once and retry+    HW=$(resolve_tray_hwnd); echo "$HW" > /tmp/tray_hwnd+    timeout 60 $CLI desktop_screenshot_window "{\"hwnd\":$HW}" >/dev/null 2>&1+    P=$(find ~/project/screenshots/adom-bridge/window -name "*-${HW}.full.png" -newermt "-45 seconds" 2>/dev/null | head -1)+  fi+  if [ -z "$P" ]; then echo "  SHOT_TRAY FAILED: no fresh capture (hwnd $HW) — pixel gate will be skipped as FAIL" >&2; rm -f "$SHOTDIR/$1.png"; return 1; fi+  cp "$P" "$SHOTDIR/$1.png"+}++resolve_tray_hwnd() {+  local PS='Add-Type -AssemblyName UIAutomationClient; $tray=[System.Windows.Automation.AutomationElement]::RootElement.FindFirst([System.Windows.Automation.TreeScope]::Children,(New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ClassNameProperty,"Shell_TrayWnd"))); $tray.Current.NativeWindowHandle'+  local B64; B64=$(python3 -c "import base64,sys;print(base64.b64encode(sys.stdin.read().encode('utf-16-le')).decode())" <<< "$PS")+  timeout 60 $CLI shell_execute "{\"command\":\"powershell -NoProfile -EncodedCommand $B64\",\"reason\":\"mode-ralph: resolve tray hwnd\"}" 2>&1 \+    | python3 -c "import sys,json;raw=sys.stdin.read();i=raw.find('{');d=json.loads(raw[i:]) if i>=0 else {};print((d.get('stdout') or '0').strip())" } -verify_state() { # verify_state <mode> <label>+verify_state() { # verify_state <mode> <label> — retries while the bar is still reflowing   local mode="$1" label="$2"-  local B; B=$(uia_buttons)+  local B PG PGOK attempt   local N_PUP N_THREAD N_CFT-  N_PUP=$(echo "$B" | grep -c "Adom Pup" || true)-  N_THREAD=$(echo "$B" | grep -Ec "^(ti-crawl|mouser-run|digikey-run|wiki-in|wiki-out|dash-app|reclaimed|pup bridge maintenance|pup-dashboard)" || true)-  N_CFT=$(echo "$B" | grep -c "Chrome for Testing" || true)-  shot_tray "$label"-  local PG; PG=$(SHOTDIR="$SHOTDIR" pixel_gate "$mode" "$label"); local PGOK=$?+  for attempt in 1 2 3 4 5; do+    B=$(uia_buttons rects.txt)+    N_PUP=$(echo "$B" | grep -c "Adom Pup" || true)+    N_THREAD=$(echo "$B" | grep -Ec "^(ti-crawl|mouser-run|digikey-run|wiki-in|wiki-out|dash-app|reclaimed|pup bridge maintenance|pup-dashboard)" || true)+    N_CFT=$(echo "$B" | grep -c "Chrome for Testing" || true)+    shot_tray "$label"+    uia_buttons rects2.txt >/dev/null+    PG=$(SHOTDIR="$SHOTDIR" pixel_gate "$mode" "$label"); PGOK=$?+    [ $PGOK -eq 0 ] && break+    # nonzero: bar reflowing (2) or a transient mid-rebuild frame measured wrong (1) — settle + retry+    echo "  (gate attempt $attempt: $PG — settling 6s)"+    sleep 6+  done   echo "  $PG"   case "$mode" in     plain)-      if [ "$N_PUP" -eq 0 ] && [ "$N_THREAD" -eq 0 ] && [ $PGOK -eq 0 ]; then PASS=$((PASS+1)); echo "  PASS [$label] plain: 0 branded buttons (CfT groups: $N_CFT) + pixel gate"; return 0-      else FAIL=$((FAIL+1)); echo "  FAIL [$label] plain: $N_PUP 'Adom Pup' + $N_THREAD thread buttons still on the bar"; echo "$B" | grep -E "Adom Pup|^(ti-crawl|mouser-run|digikey-run|wiki-in|wiki-out|dash-app|reclaimed|pup bridge)" | sed 's/^/    /'; return 1; fi ;;+      if [ "$N_PUP" -eq 0 ] && [ "$N_THREAD" -eq 0 ] && [ "$N_CFT" -eq 1 ] && [ $PGOK -eq 0 ]; then PASS=$((PASS+1)); echo "  PASS [$label] plain: 0 branded buttons, exactly ONE CfT group + pixel gate"; return 0+      else FAIL=$((FAIL+1)); echo "  FAIL [$label] plain: $N_PUP 'Adom Pup' + $N_THREAD thread buttons, $N_CFT CfT groups (must be exactly 1)"; echo "$B" | grep -E "Adom Pup|^(ti-crawl|mouser-run|digikey-run|wiki-in|wiki-out|dash-app|reclaimed|pup bridge)" | sed 's/^/    /'; return 1; fi ;;     aumid)       if [ "$N_PUP" -ge 5 ] && [ $PGOK -eq 0 ]; then PASS=$((PASS+1)); echo "  PASS [$label] aumid: $N_PUP 'Adom Pup' per-window buttons + pixel gate"; return 0       else FAIL=$((FAIL+1)); echo "  FAIL [$label] aumid: only $N_PUP 'Adom Pup' buttons"; return 1; fi ;;@@ -123,11 +189,13 @@ round() { # round <mode> <ov> <label> — clean switch, wait, verify   verify_state "$1" "$3" } -rapid_round() { # rapid_round <modeA> <modeB> <label> — the race: click A, then B ~1.2s later; B must win+rapid_round() { # rapid_round <modeA> <modeB> <label> — the race: click A, then B ~1.2s later; B must win.+  # B lands with overlay OFF: the group badge favicon is racey (most-recent window wins), so a teal+  # badge would poison the base-art teal measurement nondeterministically.   echo "== RAPID $3: $1 then $2 after 1.2s (the in-flight-stamp race) =="   switch_state "$1" "on"   sleep 1.2-  switch_state "$2" "on"+  switch_state "$2" "off"   wait_done || echo "  (apply-progress never reported done — verifying anyway)"   sleep 5   verify_state "$2" "$3"@@ -135,12 +203,15 @@ rapid_round() { # rapid_round <modeA> <modeB> <label> — the race: click A, the  round tiles on  r1-tiles round aumid on  r2-aumid-round plain on  r3-plain+round plain off r3-plain rapid_round aumid plain r4-rapid-aumid-then-plain round tiles on  r5-tiles rapid_round tiles plain r6-rapid-tiles-then-plain round aumid on  r7-aumid-round "$FINAL_MODE" "$FINAL_OV" r8-final-$FINAL_MODE+round "$FINAL_MODE" off r8-final-$FINAL_MODE+# restore the user's chosen overlay setting (measurement used overlay-off for determinism)+switch_state "$FINAL_MODE" "$FINAL_OV"+echo "== restored final state: $FINAL_MODE + overlay $FINAL_OV =="  echo echo "== MODE-RALPH RESULT: $PASS passed, $FAIL failed (shots in $SHOTDIR) =="