Files
Sylpheed/tools/re-capture/census_probe.py
Sylpheed RE agent 86c918f399 re: the 42-vs-116 outlier does not reproduce; losses are observable
Two further runs sampled the counts from the instant flight is detected. Both
give 116 roster records in the very first sample, flat thereafter, with 14 unit
definitions throughout. That is not a load race: a race would climb toward 116
from below, and it never does.

The leading explanation was arithmetic rather than behavioural -- census_probe
counts raw aligned vtable hits while wave5_probe counted distinct primary_va
values, which are different quantities if an offset can alias to a VA another
offset also claims. Measuring both in the same run refutes it: 116 raw hits and
116 distinct VAs in every sample.

So the outlier stands unexplained. Four runs now read 116/300, 116/300, 116/296
and one 42/170, with save drift, freezing, load race and VA aliasing each
individually refuted. Recorded rather than dropped, because a one-in-four
anomaly in the measurement apparatus is what later turns into a wrong
conclusion. Practical rule adopted: discard a run that disagrees with 116 rather
than interpreting it, and reproduce any finding in at least two runs.

Useful new fact: craft declines steadily and monotonically in both runs, 300 to
288 and 296 to 280, roughly 16-20 losses over 168 s under the hunting pilot.
Whether the player or NPC crossfire is responsible is not determined here, but
destruction is visible in this signal, which is half of what the kill-versus-
no-kill experiment needs.

Still not observed in any run: an arrival.
2026-08-24 14:27:31 +00:00

84 lines
3.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""Why does the roster-record count differ between runs (116 vs 42)?
Samples the counts repeatedly WITHIN one run, from the instant flight is
detected, and also reports what the scan is actually looking at:
* roster records -- vtable 0x820AF030 hits
* craft -- unit-definition-pointer sites in the entity heap
* definitions -- vtable 0x820AF844 hits
* extents/bytes -- what gmem.extents() enumerates, to test whether the scan
region itself differs between runs
A count that climbs = a load race. A count flat at a run-specific value = not a
race, and the cause is in the guest, not the probe.
"""
import os, sys, time, struct, collections
sys.path.insert(0, __file__.rsplit('/', 1)[0])
import gmem, gworld, entities2
ROSTER_VT = struct.pack('>I', 0x820AF030)
DEF_VT = struct.pack('>I', 0x820AF844)
DELTA = 0x130
def count_vt(fd, size, vt, want_vas=False):
"""Raw aligned hits, and optionally how many DISTINCT primary VAs they map
to. wave5 printed the latter and census the former; if an offset can alias to
a VA another offset also claims, the two are different numbers and that alone
would explain 42 vs 116."""
n = 0; vas = set()
for a, b in gmem.extents(fd, size):
pos = a
while pos < b:
m = min(1 << 24, b - pos)
blob = os.pread(fd, m, pos)
i = blob.find(vt)
while i != -1:
if (pos + i) % 4 == 0:
n += 1
if want_vas:
v = gmem.primary_va(pos + i)
if v is not None: vas.add(v)
i = blob.find(vt, i + 1)
pos += m
return (n, len(vas)) if want_vas else n
def extent_stats(fd, size):
n = tot = 0
for a, b in gmem.extents(fd, size):
n += 1; tot += b - a
return n, tot
def craft_count(fd, defs):
lo, hi = gmem.va_to_off(entities2.ENT_VA_LO), gmem.va_to_off(entities2.ENT_VA_HI)
n, pos = 0, lo
while pos < hi:
m = min(1 << 24, hi - pos)
blob = os.pread(fd, m, pos)
for k in range(0, len(blob) - 3, 4):
if blob[k:k+4] in defs: n += 1
pos += m
return n
def main():
secs = int(sys.argv[1]) if len(sys.argv) > 1 else 180
every = int(sys.argv[2]) if len(sys.argv) > 2 else 12
w = gworld.World(); fd = w.fd; size = w.size
t0 = time.time()
print('%5s %8s %8s %7s %6s %8s %10s' %
('t', 'roster', 'distVA', 'craft', 'defs', 'extents', 'MB'))
while time.time() - t0 < secs:
el = round(time.time() - t0)
defs = entities2.definitions(w)
r, rvas = count_vt(fd, size, ROSTER_VT, want_vas=True)
d = count_vt(fd, size, DEF_VT)
c = craft_count(fd, defs) if defs else 0
en, eb = extent_stats(fd, size)
print('%5d %8d %8d %7d %6d %8d %10.1f'
% (el, r, rvas, c, d, en, eb / 2**20), flush=True)
time.sleep(max(0, every - (time.time() - t0 - el)))
return 0
if __name__ == '__main__':
sys.exit(main())