Results refresher: the server reruns the whole-program check of main (hourly at most, when HEAD moves) and the emulator over the readable set (12-hourly at most, 01:00-06:00, when HEAD moves), so the Logbook's exact and verified tiers stop going stale

- harbor/results_refresh.py, a server thread: one run at a time at idle priority into a .partial file that replaces [reverse] match_results / diffrun_results only when complete (readable.txt beside it from the emulator's printout); the match run holds re_match_program's ltcg-main.lock and waits while another build of main has it
- [logbook] results_refresh, match_every_minutes, behaviour_every_hours, behaviour_window; state in the logbook folder's results.json (per job: commit, start, finish, counts, error; a run in progress), so a restarted server neither repeats a run nor starts one beside a run its predecessor left going, and adopts that run's output
- harbor logbook results shows the schedule; --job match|behaviour asks the server for a run now
- progress view: "Exact and verified as of commit abc1234, 2 hours ago" under the bars, a run in progress and a failed last run
This commit is contained in:
KeelrLeek committed 2026-10-06 16:17:14 -05:00
1 parent d5eb82261e
commit a67462f8ca
6 files changed
+640 -3

No files matched your search

+12 -1
View File
@@ -68,6 +68,7 @@ body { overflow: hidden; }
.pm-seg.pm-exact { background: var(--pm-exact); } .pm-seg.pm-verified { background: var(--pm-verified); }
.pm-seg.pm-readable { background: repeating-linear-gradient(135deg, var(--pm-readable) 0 4px, color-mix(in srgb, var(--pm-readable) 45%, var(--surface-3)) 4px 8px); }
.pm-seg.pm-named { background: var(--blue); }
.pm-fresh { margin-top: 6px; } .pm-fresh:empty { display: none; } .pm-fresh .mono { font-family: var(--mono); }
.pm-commit { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; margin-top: 12px; font-size: 13px; color: var(--muted); } .pm-commit b { color: var(--text); font-weight: 600; } .pm-commit .grow { flex: 1; }
.pm-tools { display: flex; flex-wrap: wrap; gap: 8px 10px; align-items: center; margin-top: 16px; } .pm-tools select { max-width: min(100%, 340px); } .pm-tools input { flex: 1 1 220px; min-width: 0; }
.pm-crumb { display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: center; margin-top: 12px; font-size: 13px; } .pm-crumb:empty { display: none; } .pm-crumb .pm-bar-row { flex: 1 1 100%; }
@@ -700,6 +701,7 @@ button.chip:hover { color: var(--accent-ink); background: var(--accent-soft); }
<div class="body">
<h2 class="pm-title" id="h-pm"><span id="pmProject">The project</span> is <b id="pmPct">–</b> decompiled</h2>
<div class="pm-bars" id="pmBars"></div>
<div class="note pm-fresh" id="pmFresh"></div>
<div class="pm-commit"><span id="pmCommit"></span><span class="grow"></span>
<button class="btn sm" id="pmPrev" type="button">Previous</button><button class="btn sm" id="pmNext" type="button">Next</button><button class="btn sm" id="pmLatest" type="button">Latest</button></div>
<div class="note" id="pmHistNote" style="margin-top:6px"></div>
@@ -1239,8 +1241,17 @@ function pmBar(label,size,segs,total,extra){const sum=segs.reduce((a,s)=>a+s[1],
function pmCodeSegs(x,total){return [['pm-exact',x.exact,pc(x.exact,total)+'% perfect match ('+kB(x.exact)+')'],
['pm-verified',x.verified,pc(x.verified,total)+'% decompiled, verified in the emulator, not exact ('+kB(x.verified)+')'],
['pm-readable',x.readable,pc(x.readable,total)+'% decompiled, readable only ('+kB(x.readable)+')']]}
function pmFresh(r){if(!r||(!r.match&&!r.behaviour&&!r.running))return '';
const when=x=>(x.sha?'commit <b class="mono">'+esc(x.sha)+'</b>, ':'')+'<span title="'+esc(x.at||'')+'">'+esc(rel(x.at))+'</span>',
m=r.match,b=r.behaviour,parts=[];
if(m&&b&&m.sha&&m.sha===b.sha)parts.push('Exact and verified as of '+when(Date.parse(m.at)<Date.parse(b.at)?m:b));
else{if(m)parts.push('Exact as of '+when(m));if(b)parts.push((m?'verified':'Verified')+' as of '+when(b))}
const name={match:'whole-program check',behaviour:'emulator run'};
if(r.running)parts.push('a fresh '+name[r.running.job]+' is running (started '+esc(rel(r.running.started))+')');
for(const k of ['match','behaviour'])if(r[k]&&r[k].error)parts.push('<span class="err" title="'+esc(r[k].error)+'">the last '+name[k]+' failed</span>');
return parts.join(' · ')}
function pmHeader(){const d=PM.doc;if(!d)return;const h=d.history,pt=PM.hist!=null?h[PM.hist]:null,c=d.code;
el('pmProject').textContent=d.project;
el('pmProject').textContent=d.project;put('pmFresh',pt?'':pmFresh(d.results));
if(!pt){el('pmPct').textContent=d.percent.toFixed(2)+'%';
put('pmBars',pmBar('Code',kB(c.bytes),pmCodeSegs(c,c.bytes),c.bytes,'game code · '+fmt(c.decompiled_functions)+' of '+fmt(c.functions)+' functions')
+(d.data?pmBar('Data',fmt(d.data.addresses)+' addresses',[['pm-named',d.data.named,pc(d.data.named,d.data.addresses)+'% of the data addresses the code touches have a name in globals.csv ('+fmt(d.data.named)+' of '+fmt(d.data.addresses)+')']],d.data.addresses,'globals named'):''));