// Round forty: what the device has never seen. No product code — this round // only changes what the walks CLAIM. The phone walk carried 88 named steps // against the mock walk's 252, four of its own were named and screenshotted // with no expectation at all (the driver reads `expect[s.name] ? ... : true`, // so a missing key is a silent pass), and nobody had written down which // features were proven on the device and which were only ever proven against // an object in memory. So: those four given their first assertion, thirty-three // more steps moved onto the device — the ones that can be proven there without // downloading, joining, sending, signing in or writing to Kyle's library — and // the map itself, in ANDROID.md beside Verifying, in four lists: what the // device proves, what is mock-only and why, what is conditional on his own // data, and what still needs a person. Four edits to smoke.mjs, all inside // PHONE_STEPS and PHONE_EXPECT; one insert in ANDROID.md. Nothing in the // PROBE, nothing in MOCK_STEPS or MOCK_EXPECT, no CSS, no Rust, no Kotlin. const fs = require('fs'); const root = process.env.SS_ROOT || 'F:/Coding/SoulStream/'; const scratch = 'C:/Users/Kyle/AppData/Local/Temp/claude/F--Coding-SoulStream/5488514a-11e8-482a-b0a0-20cb511ee110/scratchpad/'; const ins = (name) => fs.readFileSync(scratch + name, 'utf8').replace(/\s+$/, ''); 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); }; const NL = String.fromCharCode(10); /* Three of the four smoke.mjs inserts re-emit their own anchor, so a second run would double-write where `once` cannot see it coming and would only throw further down, leaving half a tree. Refuse one here, before anything is touched: `wifi-restored` exists nowhere in the repo today and is a name this round creates. */ if (fs.readFileSync(root + 'tests/mobile/smoke.mjs', 'utf8').includes('"wifi-restored"')) { throw new Error('round 40 is already applied in ' + root + ' — reset the tree rather than re-running'); } // ── the walk ────────────────────────────────────────────────────────── load('tests/mobile/smoke.mjs'); /* 0. The background check, which this round's own new steps broke. It asserts that ten seconds with the app backgrounded advances the position by at least eight seconds — true until the walk grew long enough to land that window on a TRACK BOUNDARY. Then the position legitimately goes 221 → 3 as the queue moves on, which is playback continuing in the background, the very thing being tested, scored as a failure. Crossing a boundary with the screen off is the stronger proof of the two, so take it. */ rep(` const posBefore = (await cdp.evaluate(PLAYER))?.pos ?? 0; cdp.close();`, ` const bgBefore = await cdp.evaluate(PLAYER); const posBefore = bgBefore?.pos ?? 0; cdp.close();`); rep(` const posAfter = (await cdp.evaluate(PLAYER))?.pos ?? 0; const bgOk = stillPlaying && posAfter >= posBefore + 8; if (!bgOk) problems.push("EXPECT background " + JSON.stringify({ posBefore, posAfter, stillPlaying })); console.log((bgOk ? " ok " : " FAIL ") + "background".padEnd(16) + " " + JSON.stringify({ posBefore, posAfter, stillPlaying }));`, ` const bgAfter = await cdp.evaluate(PLAYER); const posAfter = bgAfter?.pos ?? 0; const crossed = !!(bgBefore && bgAfter && bgBefore.title !== bgAfter.title); const bgOk = stillPlaying && (crossed || posAfter >= posBefore + 8); const bgSeen = { posBefore, posAfter, stillPlaying, crossed, from: bgBefore?.title ?? null, to: bgAfter?.title ?? null }; if (!bgOk) problems.push("EXPECT background " + JSON.stringify(bgSeen)); console.log((bgOk ? " ok " : " FAIL ") + "background".padEnd(16) + " " + JSON.stringify(bgSeen));`); /* 1. the walk's own playlist, while it is still "Walk test" and still in the order the drags above it restored. The mock's same-named step is `[1]` and waits 1200, so this anchor is the phone's alone. */ rep(` { do: \`document.querySelectorAll('.pl-rows .trk')[0].click()\`, name: "playlist-playing", wait: 3000 },`, ins('ins-r40-phone-playlist.txt') + NL + ` { do: \`document.querySelectorAll('.pl-rows .trk')[0].click()\`, name: "playlist-playing", wait: 3000 },`); /* 2. the import review's Keep switch, restored before the draft is created so `import-created` still counts its three rows. The mock's twin of this anchor waits 1500. */ rep(` { do: \`__click('.imp-foot .btn-fill', 'Create playlist')\`, name: "import-created", wait: 2000 },`, ins('ins-r40-phone-keep.txt') + NL + ` { do: \`__click('.imp-foot .btn-fill', 'Create playlist')\`, name: "import-created", wait: 2000 },`); /* 3. the block that carries the rest. It navigates itself, so it needs one anchor and no particular screen: round thirty-nine's own comment, the last thing before the transfer face at the end of the list. Siblings appending phone steps reach for the `name: "end"` line, which this round deliberately leaves alone, so their block sits below this one and neither moves. */ rep(` /* the one face of the six that a real build can show without arming`, ins('ins-r40-phone-block.txt') + NL + ` /* the one face of the six that a real build can show without arming`); /* 4. every new expectation in one insert. Round thirty-nine anchored its phone expectations on the `end:` line; this one stays away from it. */ rep(` "artist-cold": (i) => i.screen.includes("has-hero"),`, ` "artist-cold": (i) => i.screen.includes("has-hero"),` + NL + ins('ins-r40-phone-expect.txt')); save(); // ── the doc ─────────────────────────────────────────────────────────── /* anchored on the closing sentence of Verifying rather than on the `## Not done yet` heading, because a sibling round adding to the map would reach for the heading. Nothing already in the file is re-worded — in particular not the sentences carrying a step count, which two earlier rounds both bumped. */ load('ANDROID.md'); rep(`\`--only-lock\` takes just that still of whatever is on the bar (resuming it if paused) without the walk.`, `\`--only-lock\` takes just that still of whatever is on the bar (resuming it if paused) without the walk. ` + ins('ins-r40-doc-map.txt')); save(); console.log('round 40 wired');