Files
e62e0c4d30 6.2.0: games launch again, installs stop giving up early (#398)
* chore(deps): Bump rustls from 0.23.43 to 0.23.45 in /client/src-tauri

Bumps [rustls](https://github.com/rustls/rustls) from 0.23.43 to 0.23.45.
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustls/rustls/compare/v/0.23.43...v/0.23.45)

---
updated-dependencies:
- dependency-name: rustls
  dependency-version: 0.23.45
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* Install: drop the staged-refusal retry guard and the stream reach pre-check

After the PS5 refused a package from its own storage (0x80b2116f), the app
would no longer run Install on that row again and blocked Retry with Stream
unless the engine had offered it. Install and Retry with Stream now always
go out.

The engine no longer probes whether the console can reach it before handing
over a stream URL. A console that never fetches still gets the firewall /
PS5UPLOAD_PKG_HOST_IP explanation from the post-failure path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vq9BzVoith72tgD87LGG2z

* Helper: everything written on the console is 0777

Since 6.0 an upload kept the computer's file modes, so a game folder sent
from Windows landed as 0644 files. The PS5's app loader refuses game files
without world-execute, so those games would not launch until the user ran
chmod 777 by hand. The 5.x helper forced 0777 for this reason.

Uploads, on-console copies and the file manager's writes and new folders
now land as 0777. fs_chmod still sets exactly the mode it is given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vq9BzVoith72tgD87LGG2z

* Fix launch refusals, folder permissions, abandoned upload space and overestimated free space

- A launch refused with 0x80940033 now says whether kstuff is missing; with kstuff running,
  a folder game is made 0777 and launched again. Folder games get a Fix permissions button.
- The helper's own ps5upload folders are 0777, and older ones are opened up on start.
- The helper's job cleanup also deletes the half-written copy of an abandoned upload.
- An upload that runs out of room part-way teaches the engine how much of that drive the
  console never allocates; later space checks and Volumes' safe figure leave it out.
- Queue rows that failed because the PS5 could not reach this computer offer the
  Windows firewall fix; FAQ lists the ports and the 0x80B2116F firmwares.
- lf_slots_wedge: a bound that a healthy ASan run fits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vq9BzVoith72tgD87LGG2z

* 6.2.0: games launch again, installs stop giving up early

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vq9BzVoith72tgD87LGG2z

* i18n: add the launch-refusal and fix-permissions strings to en, allowlist them for other locales

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vq9BzVoith72tgD87LGG2z

* ava1: a finished job counts the bytes still awaiting their Received

bytes_sent grew only on a Received the send loop read, and the loop stops at JobDone. A frame
still in flight then went uncounted although the job holds it: a list upload reported 19000 of
26000 bytes under the coverage build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vq9BzVoith72tgD87LGG2z

* ava1: a lane stopped by its LaneDown never picks a frame afterwards

A lane task checked its stop flag only at the top of its loop. One that passed the check just
before LaneDown, and reached its pick after lane_death had swept the lane, charged a frame to
the dead lane: the frame sat in inflight with no lane to carry it, nothing queued, so the stall
rule never fired and the job waited forever (the soak test's resume hung under the coverage
build). The flag is now read again under the sched lock, which lane_death's sweeps take, so a
pick is either swept or sees the stop. The pick moves into pick_for_lane for a deterministic test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vq9BzVoith72tgD87LGG2z

* ci(coverage): run the engine tests once for both reports, 60 min limit

scripts/coverage.mjs ran `cargo llvm-cov --lcov` and then `--html`, each a full instrumented
test run. One pass now takes ~34 min, so the second ran into the 40 min limit and the job was
cancelled (it was red on main too). The tests now run once (--no-report) and both reports come
from that run's profiles via `cargo llvm-cov report`; the limit is 60 min for headroom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vq9BzVoith72tgD87LGG2z

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-06 10:17:35 -07:00

140 lines
5.5 KiB
JavaScript
Executable File

#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const repoRoot = path.resolve(import.meta.dirname, "..");
const coverageRoot = path.join(repoRoot, "coverage");
const installTools = process.argv.includes("--install-tools");
const clientOnly = process.argv.includes("--client-only");
const engineOnly = process.argv.includes("--engine-only");
function run(label, command, args, opts = {}) {
process.stdout.write(`\n==> ${label}\n`);
const res = spawnSync(command, args, {
cwd: opts.cwd || repoRoot,
stdio: "inherit",
shell: os.platform() === "win32",
env: { ...process.env, ...opts.env },
});
if (res.status !== 0) {
process.stderr.write(`\ncoverage: ${label} failed with exit ${res.status}\n`);
process.exit(res.status || 1);
}
}
function existsOnPath(bin) {
if (os.platform() === "win32") {
return spawnSync("where", [bin], { stdio: "ignore" }).status === 0;
}
return spawnSync("sh", ["-c", "command -v \"$1\" >/dev/null 2>&1", "sh", bin], {
stdio: "ignore",
}).status === 0;
}
function firstExisting(paths) {
return paths.find((p) => fs.existsSync(p)) || null;
}
function llvmToolEnv() {
// Caller-provided override always wins.
if (process.env.LLVM_COV && process.env.LLVM_PROFDATA) return {};
// Prefer the llvm-tools that ship with the active rustup toolchain
// over anything on PATH. cargo-llvm-cov otherwise resolves
// llvm-profdata via PATH, which on GitHub's ubuntu-24.04 image
// picks up /usr/lib/llvm-18/bin/llvm-profdata — older than rustc's
// bundled LLVM and rejects current-rustc profraw with
// "raw profile version mismatch: Profile uses raw profile format
// version = 10; expected version = 9", failing the whole merge.
// The toolchain-shipped tools are guaranteed to match rustc's
// profraw output, so pin to them whenever they're present.
const sysrootRes = spawnSync("rustc", ["--print", "sysroot"], { encoding: "utf8" });
const hostRes = spawnSync("rustc", ["-vV"], { encoding: "utf8" });
if (sysrootRes.status === 0 && hostRes.status === 0) {
const sysroot = sysrootRes.stdout.trim();
const hostMatch = hostRes.stdout.match(/^host:\s*(.+)$/m);
if (sysroot && hostMatch) {
const exe = os.platform() === "win32" ? ".exe" : "";
const toolBin = path.join(sysroot, "lib", "rustlib", hostMatch[1].trim(), "bin");
const toolchainCov = path.join(toolBin, "llvm-cov" + exe);
const toolchainProfdata = path.join(toolBin, "llvm-profdata" + exe);
if (fs.existsSync(toolchainCov) && fs.existsSync(toolchainProfdata)) {
return { LLVM_COV: toolchainCov, LLVM_PROFDATA: toolchainProfdata };
}
}
}
// Fallback: llvm-tools-preview isn't installed. Try PATH, then
// well-known LLVM prefixes. Version-mismatch risk is on the user
// here — but at least the script runs instead of failing outright.
if (existsOnPath("llvm-cov") && existsOnPath("llvm-profdata")) return {};
const prefixes = os.platform() === "darwin"
? ["/opt/homebrew/opt/llvm/bin", "/opt/homebrew/opt/llvm@22/bin", "/opt/homebrew/opt/llvm@18/bin", "/usr/local/opt/llvm/bin"]
: ["/usr/lib/llvm-18/bin", "/usr/lib/llvm-17/bin", "/usr/lib/llvm-16/bin", "/usr/bin"];
const llvmCov = firstExisting(prefixes.map((p) => path.join(p, "llvm-cov")));
const llvmProfdata = firstExisting(prefixes.map((p) => path.join(p, "llvm-profdata")));
if (llvmCov && llvmProfdata) {
return {
LLVM_COV: process.env.LLVM_COV || llvmCov,
LLVM_PROFDATA: process.env.LLVM_PROFDATA || llvmProfdata,
};
}
return {};
}
fs.mkdirSync(coverageRoot, { recursive: true });
if (!engineOnly) {
run("client vitest coverage", "npm", ["run", "test:coverage"], {
cwd: path.join(repoRoot, "client"),
});
}
if (clientOnly) {
process.stdout.write("\ncoverage reports:\n");
process.stdout.write(` client: ${path.relative(repoRoot, path.join(repoRoot, "client", "coverage", "index.html"))}\n`);
process.exit(0);
}
if (!existsOnPath("cargo-llvm-cov")) {
if (!installTools) {
process.stderr.write(
"\ncoverage: cargo-llvm-cov is not installed. Run `npm run coverage -- --install-tools` or install it with `cargo install cargo-llvm-cov --locked`.\n",
);
process.exit(1);
}
run("install cargo-llvm-cov", "cargo", ["install", "cargo-llvm-cov", "--locked"]);
}
fs.mkdirSync(path.join(coverageRoot, "engine"), { recursive: true });
const llvmEnv = llvmToolEnv();
// The test suite runs once; both reports are read from that run's profiles. Running
// `cargo llvm-cov --lcov` and then `--html` ran every test twice, and the second pass
// pushed the CI job past its time limit.
const engineOpts = { cwd: path.join(repoRoot, "engine"), env: llvmEnv };
run("engine llvm-cov test", "cargo", ["llvm-cov", "--workspace", "--no-report"], engineOpts);
run("engine llvm-cov lcov", "cargo", [
"llvm-cov",
"report",
"--lcov",
"--output-path",
path.join("..", "coverage", "engine", "lcov.info"),
], engineOpts);
run("engine llvm-cov html", "cargo", [
"llvm-cov",
"report",
"--html",
"--output-dir",
path.join("..", "coverage", "engine"),
], engineOpts);
process.stdout.write("\ncoverage reports:\n");
if (!engineOnly) {
process.stdout.write(` client: ${path.relative(repoRoot, path.join(repoRoot, "client", "coverage", "index.html"))}\n`);
}
process.stdout.write(` engine: ${path.relative(repoRoot, path.join(coverageRoot, "engine", "html", "index.html"))}\n`);
process.stdout.write(` lcov: ${path.relative(repoRoot, path.join(coverageRoot, "engine", "lcov.info"))}\n`);