// Adaptive waits for the walk. Every step used to sleep a fixed interval // sized for its worst case — 414 seconds of them in the mock list and 223 in // the phone's, over half of a round's twenty-three minutes. This polls a // fingerprint of the page and the app's own state instead, and goes on once // both have stopped moving. // // Applied on its own, after a chain clears, so it never rides inside a // round's commit. const fs = require('fs'); const root = process.env.SS_ROOT || 'F:/Coding/SoulStream/'; let p, s; const load = (file) => { p = file; s = fs.readFileSync(root + p, 'utf8'); }; const save = () => fs.writeFileSync(root + p, s); const once = (a) => { const i = s.indexOf(a); if (i < 0) throw new Error('missing in ' + p + ': ' + a.slice(0, 90)); if (s.indexOf(a, i + 1) >= 0) throw new Error('ambiguous in ' + p + ': ' + a.slice(0, 90)); return i; }; const rep = (a, b) => { once(a); s = s.replace(a, () => b); }; load('tests/mobile/smoke.mjs'); if (s.includes('function settle(')) throw new Error('the walk already waits adaptively — nothing to do'); const A_LOOP = ` for (const s of steps) {`; const A_SLEEP = ` await sleep(s.wait || 500);`; once(A_LOOP); once(A_SLEEP); rep(A_LOOP, ` /* ── waiting only as long as it takes ─────────────────────────── Every step used to sleep its declared wait, sized for the worst case it might ever meet. At 632 steps that was over half of a round: 414 seconds declared in the mock list, 223 in the phone's. So: poll something cheap until it stops changing, then go on. The fingerprint reads the shape of the page AND the app's own moving parts, because a step can be waiting on work that has not reached the DOM yet — a resolve in flight, a transfer arriving, a command that will only show itself in a toast. Two properties keep this honest: · It can only ever SHORTEN a wait, never lengthen one. The declared wait is the ceiling, so a step that passes today still gets every millisecond it got before. · There is a floor. A page can be momentarily quiet in the middle of work — a backend write with nothing visible behind it looks settled the instant it is asked — so nothing is probed before a third of its declared wait has passed. \`hold: true\` on a step opts out and sleeps the whole wait, for the handful that are waiting on something nothing here can see. */ const FINGERPRINT = \`(function () { try { var t = document.querySelector('.toast'); var ss = window.__ss || {}; var pl = ss.player || {}, rs = ss.resolver || {}, nt = ss.net || {}, lb = ss.lib || {}; return [ document.querySelectorAll('*').length, document.images.length, (document.querySelector('.screen') || {}).className || '', (document.querySelector('.sheet') || {}).className || '', (document.querySelector('.halfsheet') || {}).className || '', t ? t.textContent.slice(0, 40) : '', document.title, pl.status, pl.phase, Math.round(pl.position || 0), (pl.queue || []).length, pl.qi, rs.active ? 1 : 0, nt.busy ? 1 : 0, lb.loading ? 1 : 0, (ss.transfers && ss.transfers.items ? ss.transfers.items.length : 0) ].join('|'); } catch (e) { return 'unreadable'; } })()\`; /* two identical readings 90ms apart, and nothing has moved */ const STABLE_READINGS = 2; const POLL_MS = 90; const FLOOR = 0.34; let waited = 0, saved = 0; /* what the walk actually spent, and what it did not */ async function settle(want, step) { waited += want; if (step && step.hold) { await sleep(want); return; } const began = Date.now(); await sleep(Math.min(want, Math.max(200, Math.round(want * FLOOR)))); let last = null, stable = 0; while (Date.now() - began < want) { let fp; try { fp = await cdp.evaluate(FINGERPRINT); } catch { break; } if (fp === last) { if (++stable >= STABLE_READINGS) break; } else { stable = 0; last = fp; } const left = want - (Date.now() - began); if (left <= 0) break; await sleep(Math.min(POLL_MS, left)); } saved += Math.max(0, want - (Date.now() - began)); } for (const s of steps) {`); rep(A_SLEEP, ` await settle(s.wait || 500, s);`); /* The tally, printed where `saved` and `waited` are provably in scope: the comment below sits in the same block as the loop, at the same indent. */ rep(` /* ── the restart half of session restore, on the phone only ──────`, ` console.log("waits: " + Math.round(saved / 1000) + "s of " + Math.round(waited / 1000) + "s declared was not spent"); /* ── the restart half of session restore, on the phone only ──────`); save(); console.log('the walk waits adaptively now');