#!/usr/bin/env python3 """Scan ALL of guest RAM for 3x3 orthonormal float blocks (rotation matrices). Vectorised with numpy: nine shifted views of each extent give every candidate 9-float window at once, so the whole 250 MB working set tests in seconds. A rotation matrix is a very strong signature — unit rows, mutually orthogonal — so the hits are almost entirely real orientations. Two passes taken a moment apart, with a known stick input in between, then say which of them is *ours*. Usage: findrot_global.py [--track seconds] """ import os import struct import sys import time import numpy as np sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import gmem # noqa: E402 FIFO = "/tmp/sylph-vgamepad.fifo" TOL = 2e-3 def pad(line): with open(FIFO, "w") as f: f.write(line + "\n") def scan(fd, size): """[(file_offset, 9 floats)] for every orthonormal 3x3 block.""" hits = [] for a, b in gmem.extents(fd, size): n = (b - a) // 4 * 4 if n < 64: continue arr = np.frombuffer(os.pread(fd, n, a), dtype=">f4").astype(np.float32) if arr.size < 16: continue m = arr.size - 8 cols = [arr[i:i + m] for i in range(9)] with np.errstate(invalid="ignore", over="ignore"): finite = np.ones(m, dtype=bool) for c in cols: finite &= np.isfinite(c) & (np.abs(c) <= 1.001) # row norms n0 = cols[0] ** 2 + cols[1] ** 2 + cols[2] ** 2 n1 = cols[3] ** 2 + cols[4] ** 2 + cols[5] ** 2 n2 = cols[6] ** 2 + cols[7] ** 2 + cols[8] ** 2 ok = finite ok &= np.abs(n0 - 1) < TOL ok &= np.abs(n1 - 1) < TOL ok &= np.abs(n2 - 1) < TOL d01 = cols[0] * cols[3] + cols[1] * cols[4] + cols[2] * cols[5] d02 = cols[0] * cols[6] + cols[1] * cols[7] + cols[2] * cols[8] d12 = cols[3] * cols[6] + cols[4] * cols[7] + cols[5] * cols[8] ok &= np.abs(d01) < TOL ok &= np.abs(d02) < TOL ok &= np.abs(d12) < TOL for i in np.flatnonzero(ok): hits.append(a + int(i) * 4) return hits def read_mat(fd, off): b = os.pread(fd, 36, off) if len(b) < 36: return None return np.array(struct.unpack(">9f", b)) def main(): fd = os.open(gmem.mem_path(), os.O_RDONLY) size = os.path.getsize(gmem.mem_path()) pad("reset") time.sleep(0.6) t0 = time.time() hits = scan(fd, size) print(f"# {len(hits)} orthonormal 3x3 blocks in RAM ({time.time()-t0:.1f}s)", flush=True) if not hits: return # which of them rotate when WE yaw? measure change under left vs right def deltas(lx, secs=2.5, hz=6.0): """Mean per-step rotation vector while the stick is held. Sampled incrementally: at a few degrees per step the skew part of A·Bᵀ is the rotation vector, which it is not over a 2 s turn. Any block that stops being orthonormal mid-phase is memory that got reused, not an orientation, and is dropped. """ pad(f"axis LX {lx}") time.sleep(0.5) seq = [] for _ in range(int(secs * hz)): t = time.time() seq.append({o: read_mat(fd, o) for o in hits}) time.sleep(max(0, 1.0 / hz - (time.time() - t))) pad("axis LX 0") time.sleep(1.0) def ortho(m): if m is None or not np.all(np.isfinite(m)): return False M = m.reshape(3, 3) return np.max(np.abs(M @ M.T - np.eye(3))) < 5e-3 out = {} for o in hits: ms = [s[o] for s in seq] if not all(ortho(m) for m in ms): continue ws = [] for a, b in zip(ms, ms[1:]): M = a.reshape(3, 3) @ b.reshape(3, 3).T w = np.array([M[2, 1] - M[1, 2], M[0, 2] - M[2, 0], M[1, 0] - M[0, 1]]) / 2 if np.linalg.norm(w) < 0.5: # small-angle regime only ws.append(w) if len(ws) >= 4: out[o] = np.mean(ws, axis=0) return out dl = deltas(-0.9) dr = deltas(+0.9) rows = [] for o in hits: if o not in dl or o not in dr: continue a, b = dl[o], dr[o] na, nb = np.linalg.norm(a), np.linalg.norm(b) if na < 0.01 or nb < 0.01: continue cos = float(a @ b / (na * nb)) rows.append((cos, o, na, nb)) rows.sort() print("\n# orientation blocks that rotate OPPOSITE ways for left vs right stick") for cos, o, na, nb in rows[:12]: va = gmem.primary_va(o) print(f" va {va:#010x} cos={cos:+.3f} |wL|={na:.3f} |wR|={nb:.3f}") if not rows: print(" (none)") if __name__ == "__main__": main()