Compare commits
16 Commits
chore/hand
...
fix/export
| Author | SHA1 | Date | |
|---|---|---|---|
| 1fec20a2a3 | |||
| c0a5840ce8 | |||
| 4156cb6bd1 | |||
| e16556dc29 | |||
| d96b225e47 | |||
| 4c1e15b818 | |||
| 9a8746d380 | |||
| 1cbbfd90cb | |||
| e82d7bff6f | |||
| 00585952a9 | |||
| cd3c1a2f73 | |||
| 2f39c8826e | |||
| 4a8f1221b2 | |||
| 06601c2a48 | |||
|
|
c88391b68c | ||
|
|
cc9392bde4 |
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@@ -31,6 +31,24 @@ env:
|
|||||||
# So: one job, on the machine that exists, building for the machine that exists.
|
# So: one job, on the machine that exists, building for the machine that exists.
|
||||||
# If a second architecture is ever wanted here it needs a second RUNNER, not a
|
# If a second architecture is ever wanted here it needs a second RUNNER, not a
|
||||||
# second matrix row.
|
# second matrix row.
|
||||||
|
#
|
||||||
|
# ── The toolchain is PINNED, in three places, deliberately ───────────────────
|
||||||
|
#
|
||||||
|
# All three jobs used `dtolnay/rust-toolchain@stable`, which resolves to whatever
|
||||||
|
# stable is on the day the job runs. A lint gate that floats is not a gate: the
|
||||||
|
# same tree goes green or red depending on the date, and this repo has already
|
||||||
|
# produced a disagreement between two people reading the same commit (#15).
|
||||||
|
# `collapsible_else_if` is the example — `warn` on 1.92.0, `allow`-by-default
|
||||||
|
# pedantic on 1.98.1, so a clean local run and a red CI run were both correct.
|
||||||
|
#
|
||||||
|
# `1.98.1` is the version run 206 resolved, and `docker/ci/Dockerfile` pins the
|
||||||
|
# same one, so `docker/ci/run cargo clippy …` on a desktop is a true stand-in for
|
||||||
|
# this workflow rather than an approximation of it.
|
||||||
|
#
|
||||||
|
# To bump: change all three `dtolnay/rust-toolchain@` refs here AND the `FROM
|
||||||
|
# rust:<version>-bookworm` in `docker/ci/Dockerfile` in one commit, so the two
|
||||||
|
# can never drift apart silently. A bump is a change to the gate and belongs in
|
||||||
|
# its own PR, where the new lints it turns on are the diff.
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ── Native build, on the one runner there is ────────────────────────────────
|
# ── Native build, on the one runner there is ────────────────────────────────
|
||||||
@@ -42,13 +60,15 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
# `stable` installs a MINIMAL profile: rustc, cargo, rust-std and no
|
# Pinned — see the toolchain note at the top of this file.
|
||||||
|
#
|
||||||
|
# This action installs a MINIMAL profile: rustc, cargo, rust-std and no
|
||||||
# more. Components have to be named. Without this line the Clippy step
|
# more. Components have to be named. Without this line the Clippy step
|
||||||
# below dies on "'cargo-clippy' is not installed for the toolchain
|
# below dies on "'cargo-clippy' is not installed for the toolchain
|
||||||
# 'stable-aarch64-unknown-linux-gnu'" — which is not a lint result, it
|
# 'stable-aarch64-unknown-linux-gnu'" — which is not a lint result, it
|
||||||
# is the step never having run. The `fmt` job below always got this
|
# is the step never having run. The `fmt` job below always got this
|
||||||
# right; this one never did.
|
# right; this one never did.
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@1.98.1
|
||||||
with:
|
with:
|
||||||
components: clippy
|
components: clippy
|
||||||
|
|
||||||
@@ -78,7 +98,7 @@ jobs:
|
|||||||
run: cargo test --workspace
|
run: cargo test --workspace
|
||||||
|
|
||||||
# The tally above cannot tell you what it verified. `cargo test` reports
|
# The tally above cannot tell you what it verified. `cargo test` reports
|
||||||
# the same 207/0/14 whether the disc corpus was exercised or entirely
|
# the same count whether the disc corpus was exercised or entirely
|
||||||
# absent -- a gated suite that skips still counts as passed, and the
|
# absent -- a gated suite that skips still counts as passed, and the
|
||||||
# `ignored` column is a static count of `#[ignore]` attributes that cannot
|
# `ignored` column is a static count of `#[ignore]` attributes that cannot
|
||||||
# move at runtime. Issue #16. This prints what the run ACTUALLY had, from
|
# move at runtime. Issue #16. This prints what the run ACTUALLY had, from
|
||||||
@@ -114,7 +134,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rust toolchain + WASM target
|
- name: Install Rust toolchain + WASM target
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@1.98.1
|
||||||
with:
|
with:
|
||||||
targets: wasm32-unknown-unknown
|
targets: wasm32-unknown-unknown
|
||||||
|
|
||||||
@@ -161,7 +181,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@1.98.1
|
||||||
with:
|
with:
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
- run: cargo fmt --all -- --check
|
- run: cargo fmt --all -- --check
|
||||||
|
|||||||
43
Cargo.lock
generated
43
Cargo.lock
generated
@@ -221,7 +221,7 @@ version = "1.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -232,7 +232,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"once_cell_polyfill",
|
"once_cell_polyfill",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2507,7 +2507,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3824,7 +3824,7 @@ version = "0.50.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4818,7 +4818,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.12.1",
|
"linux-raw-sys 0.12.1",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5116,7 +5116,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5197,7 +5197,7 @@ dependencies = [
|
|||||||
"colored",
|
"colored",
|
||||||
"image",
|
"image",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"sylpheed-formats 0.1.0",
|
"sylpheed-formats",
|
||||||
"texpresso",
|
"texpresso",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -5213,7 +5213,7 @@ dependencies = [
|
|||||||
"image",
|
"image",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sylpheed-formats 0.1.0 (git+https://git.mc02.dev/fabi/Sylpheed.git?tag=formats-pin-2026-09-01)",
|
"sylpheed-formats",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5234,25 +5234,6 @@ dependencies = [
|
|||||||
"xdvdfs",
|
"xdvdfs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sylpheed-formats"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "git+https://git.mc02.dev/fabi/Sylpheed.git?tag=formats-pin-2026-09-01#1cd5b8b1cb1f02eefc0865e1a1fe280e44831c9d"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"binrw",
|
|
||||||
"flate2",
|
|
||||||
"futures",
|
|
||||||
"rayon",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
"tokio",
|
|
||||||
"tracing",
|
|
||||||
"ttf-parser 0.24.1",
|
|
||||||
"xdvdfs",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sylpheed-ppc"
|
name = "sylpheed-ppc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -5276,7 +5257,7 @@ dependencies = [
|
|||||||
"image",
|
"image",
|
||||||
"rfd",
|
"rfd",
|
||||||
"rodio",
|
"rodio",
|
||||||
"sylpheed-formats 0.1.0",
|
"sylpheed-formats",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@@ -5417,7 +5398,7 @@ dependencies = [
|
|||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 1.1.4",
|
"rustix 1.1.4",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5776,7 +5757,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"memoffset",
|
"memoffset",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6294,7 +6275,7 @@ version = "0.1.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -55,33 +55,19 @@ license.workspace = true
|
|||||||
# a squash-merge can orphan, and no way for the exporter to be built against a
|
# a squash-merge can orphan, and no way for the exporter to be built against a
|
||||||
# decoder it was never tested with. A decoder change and the exporter change it
|
# decoder it was never tested with. A decoder change and the exporter change it
|
||||||
# requires now land in the same commit or not at all.
|
# requires now land in the same commit or not at all.
|
||||||
# PINNED BY TAG, which is what MISSION section 2 prescribes and what the tagging
|
# ✅ UNPINNED 2026-09-20. The pin above was deliberate and carried its own exit
|
||||||
# rule exists for: "the RE agent tags when it lands something you need and tells
|
# condition -- "revert to the path dependency the day the tag is an ancestor of
|
||||||
# you over the message channel -- that is how you stay current without floating."
|
# `main`" -- because while it held, `sylpheed-cli` built from the WORKSPACE crate
|
||||||
# That is exactly what happened here.
|
# and the exporter from the tag, so `tools/port/verify-screen` compared two eras
|
||||||
|
# instead of detecting drift. `formats-pin-2026-09-01` is now an ancestor of
|
||||||
|
# `main` (measured), so the two read one decoder again.
|
||||||
#
|
#
|
||||||
# The tag carries the CORRECTED keyframe association: a placement group is an
|
# 🔴 AND THE PIN HAD A COST NOBODY PRICED IN: it made this repository depend on
|
||||||
# 8-byte header then `frames` x {u32 time; 36-byte pose}, so pose 0's time is the
|
# its OWN history by tag. The issue-#49 history rewrite replaced every commit,
|
||||||
# group's lead-in word and EVERY POSE IS TIMED, including the last. The working
|
# the locked rev vanished, and the build broke for every clean checkout while
|
||||||
# tree's copy still has the retired `SYLPHEED_KF_TIME_SHIFT` knob -- a superseded
|
# still working on the machine that did the rewrite, because its `~/.cargo/git`
|
||||||
# partial fix that got the association right but left pose 0 untimed, which is
|
# still held the old object (PR #60). A path dependency cannot fail that way.
|
||||||
# why testing it moved the untimed frame from last to first instead of removing
|
sylpheed-formats = { path = "../sylpheed-formats" }
|
||||||
# it. The old reading is behind `SYLPHEED_KF_TIME_LEGACY=1` here.
|
|
||||||
#
|
|
||||||
# 🔴 THE COST, STATED: `sylpheed-cli` builds from the WORKSPACE crate, so until
|
|
||||||
# this lands on `main` the exporter and the reference renderer read DIFFERENT
|
|
||||||
# decoders and `tools/port/verify-screen` is comparing two eras rather than
|
|
||||||
# detecting drift. `tools/port/verify-capture` is unaffected -- it compares the
|
|
||||||
# port against oracle CAPTURES and never touches the CLI -- and it is the check
|
|
||||||
# that matters. Revert to the path dependency the day the tag is an ancestor of
|
|
||||||
# `main`.
|
|
||||||
# Bumped c -> d 2026-08-29. What I wanted from the new state: `d` carries parser
|
|
||||||
# and `audio.rs` changes on top of `c`. ⚠️ Its headline change -- Reborn's
|
|
||||||
# renderer drawing `rotation_deg`, and `compose` drawing a leaf that carries
|
|
||||||
# geometry -- does NOT reach this port from here: `sylpheed-cli` builds from the
|
|
||||||
# WORKSPACE crate, so the reference renderer stays unrotated until the tag lands
|
|
||||||
# on `main`. This bump is for the parser, not for the renderer.
|
|
||||||
sylpheed-formats = { git = "https://git.mc02.dev/fabi/Sylpheed.git", tag = "formats-pin-2026-09-01" }
|
|
||||||
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ use std::process::Command;
|
|||||||
use sylpheed_formats::media;
|
use sylpheed_formats::media;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let disc = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let disc =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let src = media::DirectorySource::new(&disc);
|
let src = media::DirectorySource::new(&disc);
|
||||||
for bank in ["BGM_103.slb", "BGM_102.slb", "BGM_001.slb"] {
|
for bank in ["BGM_103.slb", "BGM_102.slb", "BGM_001.slb"] {
|
||||||
match media::sound_bank_riffs(&src, bank) {
|
match media::sound_bank_riffs(&src, bank) {
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
use sylpheed_formats::media;
|
use sylpheed_formats::media;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let root =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let src = media::DirectorySource::new(&root);
|
let src = media::DirectorySource::new(&root);
|
||||||
const WANT: [usize; 2] = [3_876_864, 3_930_112];
|
const WANT: [usize; 2] = [3_876_864, 3_930_112];
|
||||||
let (mut found, mut matches) = (0usize, Vec::new());
|
let (mut found, mut matches) = (0usize, Vec::new());
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ use std::collections::BTreeSet;
|
|||||||
use sylpheed_formats::{pak, ratc, ui_layout};
|
use sylpheed_formats::{pak, ratc, ui_layout};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let root =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let ar = pak::PakArchive::open(format!("{root}/dat/GP_DIALOG.pak")).expect("GP_DIALOG.pak");
|
let ar = pak::PakArchive::open(format!("{root}/dat/GP_DIALOG.pak")).expect("GP_DIALOG.pak");
|
||||||
let sets: Vec<Option<BTreeSet<String>>> = ar
|
let sets: Vec<Option<BTreeSet<String>>> = ar
|
||||||
.entries()
|
.entries()
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
use sylpheed_formats::{pak, ratc, ui_layout};
|
use sylpheed_formats::{pak, ratc, ui_layout};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let root =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
// 🔴 WIDENED 2026-08-31 to every pak, to check the Decoder's rival search
|
// 🔴 WIDENED 2026-08-31 to every pak, to check the Decoder's rival search
|
||||||
// independently. They report zero four-button builds within 6 px of
|
// independently. They report zero four-button builds within 6 px of
|
||||||
// 259/329/399/469 anywhere on the disc, which turns "another dialog with
|
// 259/329/399/469 anywhere on the disc, which turns "another dialog with
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
use sylpheed_formats::{pak::PakArchive, ui_layout};
|
use sylpheed_formats::{pak::PakArchive, ui_layout};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let disc = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let disc =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let ar = PakArchive::open(format!("{disc}/dat/GP_TITLE.pak")).expect("open");
|
let ar = PakArchive::open(format!("{disc}/dat/GP_TITLE.pak")).expect("open");
|
||||||
let e = &ar.entries()[4]; // entry 4 = the English title
|
let e = &ar.entries()[4]; // entry 4 = the English title
|
||||||
let bundle = ar.read(e).expect("read");
|
let bundle = ar.read(e).expect("read");
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ fn main() {
|
|||||||
.unwrap_or(8);
|
.unwrap_or(8);
|
||||||
unsafe { OFFSET = off };
|
unsafe { OFFSET = off };
|
||||||
println!(" reading the loop length at header +0x{off:02x}");
|
println!(" reading the loop length at header +0x{off:02x}");
|
||||||
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let root =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
||||||
.expect("dat/")
|
.expect("dat/")
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
use sylpheed_formats::{pak, ratc, ui_layout};
|
use sylpheed_formats::{pak, ratc, ui_layout};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let root =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
||||||
.expect("dat/")
|
.expect("dat/")
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
use sylpheed_formats::{pak, ratc, ui_layout};
|
use sylpheed_formats::{pak, ratc, ui_layout};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let root =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let ar = pak::PakArchive::open(format!("{root}/dat/GP_TITLE.pak")).expect("GP_TITLE.pak");
|
let ar = pak::PakArchive::open(format!("{root}/dat/GP_TITLE.pak")).expect("GP_TITLE.pak");
|
||||||
let (mut total, mut hits, mut multipose) = (0usize, 0usize, 0usize);
|
let (mut total, mut hits, mut multipose) = (0usize, 0usize, 0usize);
|
||||||
for (i, e) in ar.entries().iter().enumerate() {
|
for (i, e) in ar.entries().iter().enumerate() {
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ use std::process::Command;
|
|||||||
use sylpheed_formats::{media, slb::VoiceLang};
|
use sylpheed_formats::{media, slb::VoiceLang};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let disc = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let disc =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let src = media::DirectorySource::new(&disc);
|
let src = media::DirectorySource::new(&disc);
|
||||||
for movie in ["ADV", "S00A", "RT01A"] {
|
for movie in ["ADV", "S00A", "RT01A"] {
|
||||||
let Some((s, e)) = media::resolve_movie_voice_region(&src, movie, VoiceLang::English)
|
let Some((s, e)) = media::resolve_movie_voice_region(&src, movie, VoiceLang::English)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use sylpheed_formats::{pak, ratc, ui_layout};
|
use sylpheed_formats::{pak, ratc, ui_layout};
|
||||||
fn main() {
|
fn main() {
|
||||||
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let root =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let ar = pak::PakArchive::open(format!("{root}/dat/GP_READY_ROOM.pak")).unwrap();
|
let ar = pak::PakArchive::open(format!("{root}/dat/GP_READY_ROOM.pak")).unwrap();
|
||||||
for (i, e) in ar.entries().iter().enumerate() {
|
for (i, e) in ar.entries().iter().enumerate() {
|
||||||
let Ok(by) = ar.read(e) else { continue };
|
let Ok(by) = ar.read(e) else { continue };
|
||||||
|
|||||||
@@ -16,8 +16,7 @@
|
|||||||
//! Usage:
|
//! Usage:
|
||||||
//! SYLPHEED_ISO=... cargo run --release --example correlate_capture -- \
|
//! SYLPHEED_ISO=... cargo run --release --example correlate_capture -- \
|
||||||
//! <capture.log> <Stage_SNN> <ship_id> [ref_part_substr] [--emit]
|
//! <capture.log> <Stage_SNN> <ship_id> [ref_part_substr] [--emit]
|
||||||
//! e.g. SYLPHEED_ISO="/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of
|
//! e.g. SYLPHEED_ISO="/path/to/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso" \
|
||||||
//! Deception (USA, Europe) (En,Ja).iso" \
|
|
||||||
//! cargo run --release --example correlate_capture -- \
|
//! cargo run --release --example correlate_capture -- \
|
||||||
//! xenia_ship_capture.log Stage_S01 e106 bdy_04 --emit
|
//! xenia_ship_capture.log Stage_S01 e106 bdy_04 --emit
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
use sylpheed_formats::{pak, ratc, ui_layout};
|
use sylpheed_formats::{pak, ratc, ui_layout};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let root =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
||||||
.expect("dat/")
|
.expect("dat/")
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ use std::collections::BTreeMap;
|
|||||||
use sylpheed_formats::{pak, ratc, ui_layout};
|
use sylpheed_formats::{pak, ratc, ui_layout};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let root =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
||||||
.expect("dat/")
|
.expect("dat/")
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use sylpheed_formats::{pak, ratc, ui_layout};
|
use sylpheed_formats::{pak, ratc, ui_layout};
|
||||||
fn main() {
|
fn main() {
|
||||||
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let root =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
||||||
.expect("dat/")
|
.expect("dat/")
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
use sylpheed_formats::{pak, ratc, ui_layout};
|
use sylpheed_formats::{pak, ratc, ui_layout};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let root =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
||||||
.expect("dat/")
|
.expect("dat/")
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ fn opaque_span(el: &ui_layout::Element, thr: u32, tmax: u32) -> Vec<(f64, f64)>
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let root =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
||||||
.expect("dat/")
|
.expect("dat/")
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ fn forced(b: &ui_layout::UiBuild, el: &ui_layout::Element, tmax: u32, hold: bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let root =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
||||||
.expect("dat/")
|
.expect("dat/")
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ use std::collections::BTreeMap;
|
|||||||
use sylpheed_formats::{pak, ratc, ui_layout};
|
use sylpheed_formats::{pak, ratc, ui_layout};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
let root =
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("set SYLPHEED_DISC to the extracted disc root");
|
||||||
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
let mut paks: Vec<_> = std::fs::read_dir(format!("{root}/dat"))
|
||||||
.expect("dat/")
|
.expect("dat/")
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|||||||
@@ -6,14 +6,34 @@
|
|||||||
//! silence. One rule, two outcomes.
|
//! silence. One rule, two outcomes.
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use sylpheed_formats::{slb, PakArchive};
|
use sylpheed_formats::{slb, PakArchive};
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
use common::skip_without_disc;
|
use common::skip_without_disc;
|
||||||
|
|
||||||
|
/// One archive for the whole binary.
|
||||||
|
///
|
||||||
|
/// `PakArchive` holds the entire concatenated payload in memory, and
|
||||||
|
/// `sound.pak` is **1.01 GB** (`sound.p00`-`.p04`). Opening it per call — which
|
||||||
|
/// the helpers below did, inside loops — put one copy per test thread in flight,
|
||||||
|
/// so at the default thread count the suite needed ~6 GB and was SIGKILLed by
|
||||||
|
/// the CI container's 7 GB cap (`--memory-swap` equals `--memory`, so there is
|
||||||
|
/// no swap to absorb it). A killed suite prints no `test result:` line at all,
|
||||||
|
/// so it vanishes from the tally rather than failing visibly.
|
||||||
|
///
|
||||||
|
/// The archive is immutable once open and every accessor takes `&self`, so one
|
||||||
|
/// shared instance is equivalent to N private ones — at 1/N the memory.
|
||||||
|
fn sound(root: &Path) -> &'static PakArchive {
|
||||||
|
static SOUND: OnceLock<PakArchive> = OnceLock::new();
|
||||||
|
// Every caller passes the same `disc_root()`, so first-writer-wins is the
|
||||||
|
// same archive whichever test initialises it.
|
||||||
|
SOUND.get_or_init(|| PakArchive::open(root.join("dat/sound.pak")).expect("sound.pak"))
|
||||||
|
}
|
||||||
|
|
||||||
fn bank(root: &Path, n: u32) -> Vec<u8> {
|
fn bank(root: &Path, n: u32) -> Vec<u8> {
|
||||||
let snd = PakArchive::open(root.join("dat/sound.pak")).expect("sound.pak");
|
let snd = sound(root);
|
||||||
let path = format!("eng\\etc\\VOICE_D_{n}.slb");
|
let path = format!("eng\\etc\\VOICE_D_{n}.slb");
|
||||||
let entry = snd.find_by_name(&path).expect("bank present");
|
let entry = snd.find_by_name(&path).expect("bank present");
|
||||||
snd.read(entry).expect("read")
|
snd.read(entry).expect("read")
|
||||||
@@ -71,7 +91,7 @@ fn all_zero_leading_region_is_skipped() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn bank_named(root: &Path, path: &str) -> Vec<u8> {
|
fn bank_named(root: &Path, path: &str) -> Vec<u8> {
|
||||||
let snd = PakArchive::open(root.join("dat/sound.pak")).expect("sound.pak");
|
let snd = sound(root);
|
||||||
let entry = snd
|
let entry = snd
|
||||||
.find_by_name(path)
|
.find_by_name(path)
|
||||||
.unwrap_or_else(|| panic!("{path} present"));
|
.unwrap_or_else(|| panic!("{path} present"));
|
||||||
@@ -154,7 +174,7 @@ fn derived_offset_recovers_voice_banks_without_regressing_etc() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn scan_data_offset_agrees_with_the_riff_derived_answer() {
|
fn scan_data_offset_agrees_with_the_riff_derived_answer() {
|
||||||
skip_without_disc!(root);
|
skip_without_disc!(root);
|
||||||
let snd = PakArchive::open(root.join("dat/sound.pak")).expect("sound.pak");
|
let snd = sound(&root);
|
||||||
let mut checked = 0usize;
|
let mut checked = 0usize;
|
||||||
let mut agreed = 0usize;
|
let mut agreed = 0usize;
|
||||||
for lang in ["eng", "jpn"] {
|
for lang in ["eng", "jpn"] {
|
||||||
@@ -198,7 +218,7 @@ fn scan_data_offset_agrees_with_the_riff_derived_answer() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn scan_only_returns_known_offsets() {
|
fn scan_only_returns_known_offsets() {
|
||||||
skip_without_disc!(root);
|
skip_without_disc!(root);
|
||||||
let snd = PakArchive::open(root.join("dat/sound.pak")).expect("sound.pak");
|
let snd = sound(&root);
|
||||||
let mut seen = 0usize;
|
let mut seen = 0usize;
|
||||||
for n in 1u32..200 {
|
for n in 1u32..200 {
|
||||||
for path in [
|
for path in [
|
||||||
@@ -232,7 +252,7 @@ fn scan_only_returns_known_offsets() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_waves_declared_size_is_confirmed_by_the_next_seek() {
|
fn a_waves_declared_size_is_confirmed_by_the_next_seek() {
|
||||||
skip_without_disc!(root);
|
skip_without_disc!(root);
|
||||||
let snd = PakArchive::open(root.join("dat/sound.pak")).expect("sound.pak");
|
let snd = sound(&root);
|
||||||
let mut checked = 0usize;
|
let mut checked = 0usize;
|
||||||
for n in 1u32..400 {
|
for n in 1u32..400 {
|
||||||
for path in [
|
for path in [
|
||||||
@@ -289,7 +309,7 @@ fn a_waves_declared_size_is_confirmed_by_the_next_seek() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_bank_that_states_its_own_header_has_no_leading_segment() {
|
fn a_bank_that_states_its_own_header_has_no_leading_segment() {
|
||||||
skip_without_disc!(root);
|
skip_without_disc!(root);
|
||||||
let snd = PakArchive::open(root.join("dat/sound.pak")).expect("sound.pak");
|
let snd = sound(&root);
|
||||||
let mut with_header = 0usize;
|
let mut with_header = 0usize;
|
||||||
let mut mid_bank = 0usize;
|
let mut mid_bank = 0usize;
|
||||||
// Peek at the 56-byte header through the archive's flat data rather than
|
// Peek at the 56-byte header through the archive's flat data rather than
|
||||||
@@ -327,7 +347,7 @@ fn a_bank_that_states_its_own_header_has_no_leading_segment() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn the_menu_music_bank_is_exactly_two_sub_waves() {
|
fn the_menu_music_bank_is_exactly_two_sub_waves() {
|
||||||
skip_without_disc!(root);
|
skip_without_disc!(root);
|
||||||
let snd = PakArchive::open(root.join("dat/sound.pak")).expect("sound.pak");
|
let snd = sound(&root);
|
||||||
for (name, sizes) in [
|
for (name, sizes) in [
|
||||||
("BGM_103.slb", [3_876_864usize, 3_930_112]),
|
("BGM_103.slb", [3_876_864usize, 3_930_112]),
|
||||||
("BGM_001.slb", [4_466_688, 4_673_536]),
|
("BGM_001.slb", [4_466_688, 4_673_536]),
|
||||||
|
|||||||
@@ -16,10 +16,8 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
let p = PathBuf::from(std::env::var("SYLPHEED_DISC").ok()?);
|
use common::disc_root;
|
||||||
p.join("dat").is_dir().then_some(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(ar: &PakArchive, i: usize) -> (Vec<u8>, ui_layout::UiBuild) {
|
fn build(ar: &PakArchive, i: usize) -> (Vec<u8>, ui_layout::UiBuild) {
|
||||||
let by = ar.read(&ar.entries()[i]).expect("entry");
|
let by = ar.read(&ar.entries()[i]).expect("entry");
|
||||||
|
|||||||
@@ -26,21 +26,8 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
use common::disc_root;
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let default = Path::new(
|
|
||||||
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
|
|
||||||
);
|
|
||||||
if default.join("dat").is_dir() {
|
|
||||||
return Some(default.to_path_buf());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn for_each_build(root: &Path, mut f: impl FnMut(&str, &[u8])) {
|
fn for_each_build(root: &Path, mut f: impl FnMut(&str, &[u8])) {
|
||||||
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
|
let mut paks: Vec<PathBuf> = std::fs::read_dir(root.join("dat"))
|
||||||
|
|||||||
@@ -22,10 +22,8 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
let p = PathBuf::from(std::env::var("SYLPHEED_DISC").ok()?);
|
use common::disc_root;
|
||||||
p.join("dat").is_dir().then_some(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read a nested record's declared length and its largest keyframe time.
|
/// Read a nested record's declared length and its largest keyframe time.
|
||||||
fn record_len_and_maxt(bundle: &[u8], off: usize, size: usize) -> Option<(i64, i64)> {
|
fn record_len_and_maxt(bundle: &[u8], off: usize, size: usize) -> Option<(i64, i64)> {
|
||||||
|
|||||||
@@ -25,15 +25,8 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
use sylpheed_formats::{pak::PakArchive, ratc, ui_layout};
|
||||||
|
|
||||||
fn disc_root() -> Option<PathBuf> {
|
mod common;
|
||||||
if let Ok(p) = std::env::var("SYLPHEED_DISC") {
|
use common::disc_root;
|
||||||
let p = PathBuf::from(p);
|
|
||||||
if p.join("dat").is_dir() {
|
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The case that found the bug, asserted end to end.
|
/// The case that found the bug, asserted end to end.
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -21,23 +21,68 @@ IMAGE="${SYLPH_CI_IMAGE:-sylph-ci:local}"
|
|||||||
CPUS="${SYLPH_CI_CPUS:-6}"
|
CPUS="${SYLPH_CI_CPUS:-6}"
|
||||||
MEM_GB="${SYLPH_CI_MEM_GB:-7}"
|
MEM_GB="${SYLPH_CI_MEM_GB:-7}"
|
||||||
|
|
||||||
|
CARGO_VOL="${SYLPH_CI_CARGO_VOL:-sylph-ci-cargo}"
|
||||||
|
TARGET_VOL="${SYLPH_CI_TARGET_VOL:-sylph-ci-target}"
|
||||||
|
|
||||||
|
# 🔴 Docker on the dev boxes is ROOTFUL, so without `--user` every byte the build
|
||||||
|
# writes into the bind-mounted repo is owned by root — and the user then needs
|
||||||
|
# `sudo` to delete their own artifacts. The regen command in the workspace
|
||||||
|
# `CLAUDE.md` writes `sylpheed.db` straight into /work, so it lands root-owned,
|
||||||
|
# and a stray root-owned file is exactly what survived the last cleanup and had
|
||||||
|
# to be sudo'd away.
|
||||||
|
#
|
||||||
|
# The catch is that the daemon creates a named volume root-owned, so a `--user`
|
||||||
|
# container cannot write /cargo or /target at all. Take ownership once — and
|
||||||
|
# only when it is actually wrong, because a recursive chown across a ~36 GB
|
||||||
|
# target volume is not something to repeat on every invocation.
|
||||||
|
RUN_UID="$(id -u)"
|
||||||
|
RUN_GID="$(id -g)"
|
||||||
|
|
||||||
|
docker volume create "$CARGO_VOL" >/dev/null
|
||||||
|
docker volume create "$TARGET_VOL" >/dev/null
|
||||||
|
|
||||||
|
# Both volumes, not just one: they are chowned together but can drift apart if an
|
||||||
|
# older root-owned run created only one of them.
|
||||||
|
vol_owner="$(docker run --rm -v "$CARGO_VOL:/cargo" -v "$TARGET_VOL:/target" "$IMAGE" \
|
||||||
|
stat -c %u /cargo /target 2>/dev/null | sort -u | tr '\n' ' ' || echo unknown)"
|
||||||
|
if [ "$vol_owner" != "$RUN_UID " ]; then
|
||||||
|
echo "docker/ci/run: chowning the cargo/target volumes to $RUN_UID:$RUN_GID (one-off)" >&2
|
||||||
|
docker run --rm \
|
||||||
|
-v "$CARGO_VOL:/cargo" -v "$TARGET_VOL:/target" \
|
||||||
|
"$IMAGE" chown -R "$RUN_UID:$RUN_GID" /cargo /target
|
||||||
|
fi
|
||||||
|
|
||||||
args=(
|
args=(
|
||||||
--rm
|
--rm
|
||||||
--cpus "$CPUS"
|
--cpus "$CPUS"
|
||||||
--memory "${MEM_GB}g"
|
--memory "${MEM_GB}g"
|
||||||
--memory-swap "${MEM_GB}g"
|
--memory-swap "${MEM_GB}g"
|
||||||
--pids-limit 2048
|
--pids-limit 2048
|
||||||
|
# Run as the invoking user so build output in /work is owned by them, not root.
|
||||||
|
--user "$RUN_UID:$RUN_GID"
|
||||||
-v "$REPO:/work"
|
-v "$REPO:/work"
|
||||||
# Named volumes, not bind mounts: the host tree keeps a 32 GB `target/` from
|
# Named volumes, not bind mounts: the host tree keeps a 32 GB `target/` from
|
||||||
# earlier host-side builds, and mixing the two produces rebuilds that look
|
# earlier host-side builds, and mixing the two produces rebuilds that look
|
||||||
# like cache misses and are actually two toolchains fighting over one directory.
|
# like cache misses and are actually two toolchains fighting over one directory.
|
||||||
-v sylph-ci-cargo:/cargo -e CARGO_HOME=/cargo
|
-v "$CARGO_VOL:/cargo" -e CARGO_HOME=/cargo
|
||||||
-v sylph-ci-target:/target -e CARGO_TARGET_DIR=/target
|
-v "$TARGET_VOL:/target" -e CARGO_TARGET_DIR=/target
|
||||||
-w /work
|
-w /work
|
||||||
)
|
)
|
||||||
|
|
||||||
# The disc, read-only, when a disc-backed test or the exporter needs it.
|
# The corpora, read-only, when a disc-backed test or the exporter needs them.
|
||||||
|
#
|
||||||
|
# All three, not just the disc: a suite whose corpus is absent self-skips and
|
||||||
|
# still counts as passed, so mounting one of three made an in-container run look
|
||||||
|
# like a full one while `res3d` and `iso` suites silently sat out (#16). Each is
|
||||||
|
# mounted only when it exists, and `target/sylpheed-corpus-report.txt` says which
|
||||||
|
# ones the run actually had.
|
||||||
DISC="${SYLPHEED_DISC:-$REPO/../sylph_extract}"
|
DISC="${SYLPHEED_DISC:-$REPO/../sylph_extract}"
|
||||||
[ -d "$DISC" ] && args+=(-v "$DISC:/disc:ro" -e SYLPHEED_DISC=/disc)
|
[ -d "$DISC" ] && args+=(-v "$DISC:/disc:ro" -e SYLPHEED_DISC=/disc)
|
||||||
|
|
||||||
|
RES3D="${SYLPHEED_RES3D:-}"
|
||||||
|
[ -n "$RES3D" ] && [ -d "$RES3D" ] && args+=(-v "$RES3D:/res3d:ro" -e SYLPHEED_RES3D=/res3d)
|
||||||
|
|
||||||
|
ISO="${SYLPHEED_ISO:-}"
|
||||||
|
[ -n "$ISO" ] && [ -f "$ISO" ] && args+=(-v "$ISO:/disc.iso:ro" -e SYLPHEED_ISO=/disc.iso)
|
||||||
|
|
||||||
exec docker run "${args[@]}" "$IMAGE" "$@"
|
exec docker run "${args[@]}" "$IMAGE" "$@"
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ Confidence: ✅ `CONFIRMED` · 🟡 `PROBABLE` · ❔ `HYPOTHESIS`. See [README]
|
|||||||
Also durable, and worth reading before proposing anything:
|
Also durable, and worth reading before proposing anything:
|
||||||
[`REFUTED.md`](REFUTED.md) — what has already been tested and died ·
|
[`REFUTED.md`](REFUTED.md) — what has already been tested and died ·
|
||||||
[`METHOD.md`](METHOD.md) — the traps this corpus has already paid for ·
|
[`METHOD.md`](METHOD.md) — the traps this corpus has already paid for ·
|
||||||
[`BACKLOG.md`](BACKLOG.md) — what is still open.
|
[`BACKLOG.md`](BACKLOG.md) — what is still open ·
|
||||||
|
[`disc-contents.md`](disc-contents.md) — what files the disc actually holds, and where.
|
||||||
|
|
||||||
Formats we've already reversed are, for now, **documented by their parser + disc round-trip
|
Formats we've already reversed are, for now, **documented by their parser + disc round-trip
|
||||||
tests** (the executable spec) rather than a prose file — the "Spec" column points there.
|
tests** (the executable spec) rather than a prose file — the "Spec" column points there.
|
||||||
@@ -72,6 +73,7 @@ files, which is how the same ground got covered twice.
|
|||||||
| [`autopilot-memory-driven.md`](autopilot-memory-driven.md) | Memory-driven autopilot — build log and current state | 🟢 IT FLIES, KILLS AND SURVIVES — but it loses the mission anyway. |
|
| [`autopilot-memory-driven.md`](autopilot-memory-driven.md) | Memory-driven autopilot — build log and current state | 🟢 IT FLIES, KILLS AND SURVIVES — but it loses the mission anyway. |
|
||||||
| [`canary-scripted-input-traps.md`](canary-scripted-input-traps.md) | Getting past the title screen in the container — three traps and one blocker | ✅ CONFIRMED for the three traps (each reproduced, and two of them |
|
| [`canary-scripted-input-traps.md`](canary-scripted-input-traps.md) | Getting past the title screen in the container — three traps and one blocker | ✅ CONFIRMED for the three traps (each reproduced, and two of them |
|
||||||
| [`challenge-mission-gate.md`](challenge-mission-gate.md) | Challenge / EX missions — the stage set, the GamePart graph, and the kind field | ✅ for the static structure (stage set, GamePart ids, the config-section |
|
| [`challenge-mission-gate.md`](challenge-mission-gate.md) | Challenge / EX missions — the stage set, the GamePart graph, and the kind field | ✅ for the static structure (stage set, GamePart ids, the config-section |
|
||||||
|
| [`disc-contents.md`](disc-contents.md) | What is actually on the disc, and where | ✅ CONFIRMED — layout, counts and `media_id` re-measured against the retail extract 2026-09-18; `resource3d/` is in `hidden/`, not `dat/` |
|
||||||
| [`dynamic-re-state-restore.md`](dynamic-re-state-restore.md) | The container's dynamic-RE state is not durable — how to rebuild it | ✅ CONFIRMED by rebuilding it (2026-08-23). Everything the dynamic |
|
| [`dynamic-re-state-restore.md`](dynamic-re-state-restore.md) | The container's dynamic-RE state is not durable — how to rebuild it | ✅ CONFIRMED by rebuilding it (2026-08-23). Everything the dynamic |
|
||||||
| [`flight-controls-runtime.md`](flight-controls-runtime.md) | In-flight control mapping — measured, not assumed | ✅ for the weapon bindings (ammo counters move), 🟡 for the rest (HUD |
|
| [`flight-controls-runtime.md`](flight-controls-runtime.md) | In-flight control mapping — measured, not assumed | ✅ for the weapon bindings (ammo counters move), 🟡 for the rest (HUD |
|
||||||
| [`flight-speed-law.md`](flight-speed-law.md) | The throttle is a TARGET-SPEED selector — measured against the definition (2026-08-13) | ✅ for the shape of the law, 🟡 for the unit scale. |
|
| [`flight-speed-law.md`](flight-speed-law.md) | The throttle is a TARGET-SPEED selector — measured against the definition (2026-08-13) | ✅ for the shape of the law, 🟡 for the unit scale. |
|
||||||
|
|||||||
@@ -18,6 +18,22 @@ A wrong-but-confident note is worse than no note: someone builds on it and the b
|
|||||||
for weeks. Every entry therefore carries an explicit **confidence** and its **evidence**.
|
for weeks. Every entry therefore carries an explicit **confidence** and its **evidence**.
|
||||||
This mirrors the project method — *measure the oracle, never infer; refute before believing.*
|
This mirrors the project method — *measure the oracle, never infer; refute before believing.*
|
||||||
|
|
||||||
|
### Captures are local-only (issue #49)
|
||||||
|
|
||||||
|
The repository carries **code, tooling and docs**. Screenshots and savegame
|
||||||
|
blobs are game-derived, so since 2026-09-19 they live in `docs/re/captures/`
|
||||||
|
on disk and are **gitignored** — the pages' relative links still resolve on a
|
||||||
|
machine that has them, and nothing ships.
|
||||||
|
|
||||||
|
Derived measurements (`csv`, `tsv`, `txt`, `log`, `json`, `jsonl`, `npy`) are
|
||||||
|
our own numbers rather than game content, and stay tracked — they are what most
|
||||||
|
claims here actually rest on.
|
||||||
|
|
||||||
|
`tools/re/check-capture-citations` enforces both halves: a cited capture must be
|
||||||
|
**present**, and a game asset must **not be tracked**. ⚠️ A fresh clone has no
|
||||||
|
captures, so its citations will not resolve until the captures are copied in;
|
||||||
|
that is expected, and the checker is a local gate rather than a CI one.
|
||||||
|
|
||||||
### Clean-room firewall
|
### Clean-room firewall
|
||||||
|
|
||||||
- ✅ Allowed: behaviour descriptions, field offsets/types, formulas, state machines,
|
- ✅ Allowed: behaviour descriptions, field offsets/types, formulas, state machines,
|
||||||
|
|||||||
13
docs/re/captures/.gitignore
vendored
Normal file
13
docs/re/captures/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Game-derived assets stay on disk and ship nowhere (issue #49).
|
||||||
|
#
|
||||||
|
# The pages' relative links still resolve on a machine that has the captures,
|
||||||
|
# so the evidence stays followable where it exists — it is simply not committed.
|
||||||
|
# `tools/re/check-capture-citations` enforces both halves: a cited capture must
|
||||||
|
# be PRESENT here, and an asset must NOT be tracked.
|
||||||
|
#
|
||||||
|
# Derived measurements (csv, tsv, txt, log, json, jsonl, npy) are our own
|
||||||
|
# numbers rather than game content, and remain tracked.
|
||||||
|
*.png
|
||||||
|
*.jpg
|
||||||
|
*.jpeg
|
||||||
|
*.bin
|
||||||
104
docs/re/disc-contents.md
Normal file
104
docs/re/disc-contents.md
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
# What is actually on the disc, and where
|
||||||
|
|
||||||
|
**Status:** ✅ `CONFIRMED` for the layout and the counts — every figure below was
|
||||||
|
re-measured against the retail extract on 2026-09-18, not copied from the source
|
||||||
|
document. ✅ `CONFIRMED` for `media_id`, read from the XEX header.
|
||||||
|
|
||||||
|
This page exists because the corpus never had one: it documents formats in depth
|
||||||
|
(see [INDEX](INDEX.md)) but nowhere said **what files the disc holds and where they sit**.
|
||||||
|
It is adopted from a pre-RE-era `GAME_CONTENTS.md` that lived homeless in the workspace
|
||||||
|
root through the repository consolidation. **Only the measured parts were carried over** —
|
||||||
|
see [What was dropped](#what-was-dropped-and-why) at the end, which matters more than the
|
||||||
|
rest of the page.
|
||||||
|
|
||||||
|
Extracted from the XISO image with [extract-xiso](https://github.com/XboxDev/extract-xiso).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
|
||||||
|
| Field | Value | Source |
|
||||||
|
|---|---|---|
|
||||||
|
| `media_id` | `0x2D2E2EEB` | XEX `execution_info`, via `.xex.json` |
|
||||||
|
| `title_id` | `0x53512D14` (`"SQ"` + `0x2D14`) | XEX `execution_info` |
|
||||||
|
| `disc_number` / `disc_count` | 1 / 1 | XEX `execution_info` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Top-level layout
|
||||||
|
|
||||||
|
```
|
||||||
|
<extract root>/
|
||||||
|
├── default.xex ← the game executable (XEX2, PowerPC BE)
|
||||||
|
├── config.ini ← language table (Shift-JIS comments)
|
||||||
|
├── $SystemUpdate/ ← su20076000_00000000 (dashboard update, not game data)
|
||||||
|
├── dat/ ← 71 entries
|
||||||
|
└── hidden/ ← 5 entries
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ **`resource3d/` is in `hidden/`, not in `dat/`.** So are `DefTables` and `MiscBin`.
|
||||||
|
The source document placed all three under `dat/`; that is wrong, and it is the reason
|
||||||
|
this page re-measured rather than transcribed. `SYLPHEED_RES3D` points at
|
||||||
|
`hidden/resource3d` for exactly this reason.
|
||||||
|
|
||||||
|
### `dat/` — 71 entries
|
||||||
|
|
||||||
|
| Group | Count | Note |
|
||||||
|
|---|---|---|
|
||||||
|
| `*.pak` + `*.p00` pairs | 33 + 33 | the IPFB archives — format ✅ decoded, see [INDEX](INDEX.md) |
|
||||||
|
| `sound.pak` + `sound.p00`–`.p04` | 6 | one archive whose payload is split across five chunks |
|
||||||
|
| `movie/` | 109 entries | |
|
||||||
|
|
||||||
|
`dat/movie/` holds **97 `.wmv`** files plus **six language packs** as `.pak`/`.p00`
|
||||||
|
pairs (`deu eng esp fra ita jpn`). ⚠️ Those packs carry **subtitles and fonts, not voice** —
|
||||||
|
all voice and SFX live in `sound.pak`. That trap is recorded separately; do not go looking
|
||||||
|
for dialogue audio in the movie directory.
|
||||||
|
|
||||||
|
### `hidden/` — 5 entries
|
||||||
|
|
||||||
|
| Entry | Size | Note |
|
||||||
|
|---|---|---|
|
||||||
|
| `resource3d/` | 166 files | `.xpr` texture/model containers (`Base.xpr`, `BG_*.xpr`, stage and ship sets) |
|
||||||
|
| `DefTables.pak` / `.p00` | 17 596 B / 3 058 037 B | balance and definition tables |
|
||||||
|
| `MiscBin.pak` / `.p00` | 496 B / 22 553 238 B | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `config.ini`
|
||||||
|
|
||||||
|
Plain-text INI, **Shift-JIS** comments (they render as mojibake in a UTF-8 reader — the
|
||||||
|
file is not corrupt). `[SYSTEM]` is present but empty; `[LANGUAGE]` maps the Xbox 360
|
||||||
|
locale constants onto the disc's three-letter directory names, with `eng` as the default:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[LANGUAGE]
|
||||||
|
= eng ; default
|
||||||
|
#0x01 = eng ; XC_LANGUAGE_ENGLISH
|
||||||
|
#0x02 = jpn ; XC_LANGUAGE_JAPANESE
|
||||||
|
#0x03 = deu ; XC_LANGUAGE_GERMAN
|
||||||
|
```
|
||||||
|
|
||||||
|
Those keys are why the six-language pack naming above is what it is.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What was dropped, and why
|
||||||
|
|
||||||
|
The source document was written **before** the formats were reversed, and roughly half of
|
||||||
|
it was speculation phrased as status. Carrying that forward would have put claims into the
|
||||||
|
corpus that the corpus itself has already refuted — the precise failure mode
|
||||||
|
[README](README.md) warns about ("a wrong-but-confident note is worse than no note").
|
||||||
|
Dropped:
|
||||||
|
|
||||||
|
- **A "Known File Formats" status table** marking `dat/*.pak` as *"⏳ Unknown — magic bytes
|
||||||
|
TBD"*, and `.XWB`/`.XSB` as *"⏳ TODO"*. The `.pak` container is ✅ decoded disc-wide.
|
||||||
|
[INDEX](INDEX.md) is the authority on format status; a second table would only drift.
|
||||||
|
- **A "PAK Archive Structure (TBD)" section** guessing each archive's contents from its
|
||||||
|
name ("`GP_BUNK.pak` — likely barracks/crew quarters UI"). Those are guesses, and the
|
||||||
|
real contents are known.
|
||||||
|
- **An "RE Entry Points" section** recommending loading `default.xex` into Ghidra to find
|
||||||
|
the loaders. Static analysis now goes through `sylpheed.db` (see the workspace
|
||||||
|
`CLAUDE.md` and `/sylph-dis`).
|
||||||
|
|
||||||
|
Nothing measured was dropped. The layout, the counts, the identity fields and the language
|
||||||
|
table are all re-verified above.
|
||||||
3
tools/re-capture/.gitignore
vendored
Normal file
3
tools/re-capture/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
# Game-derived digit templates for ob_read.py — pixels from the game (issue #49).
|
||||||
|
ob_digits.png
|
||||||
@@ -10,10 +10,17 @@
|
|||||||
largest thing in the repository, and the one place a file can be added, never
|
largest thing in the repository, and the one place a file can be added, never
|
||||||
cited, and never noticed.
|
cited, and never noticed.
|
||||||
|
|
||||||
Two failures, which are opposites and must not be conflated:
|
⚠️ 2026-09-19, issue #49: **game assets are no longer committed.** Screenshots
|
||||||
|
and savegame blobs live in the working tree and are gitignored, so the pages'
|
||||||
|
relative links still resolve on a machine that has them while nothing ships.
|
||||||
|
That inverts half of this check — "is it committed?" became "is it PRESENT?",
|
||||||
|
and a NEW failure appeared: an asset that IS tracked. Both are below.
|
||||||
|
|
||||||
* a page cites a capture that **is not committed** — a reader following it
|
Three failures, which are opposites and must not be conflated:
|
||||||
|
|
||||||
|
* a page cites a capture that **is not present** — a reader following it
|
||||||
gets nothing. That is an error, exactly as in `check-citations`.
|
gets nothing. That is an error, exactly as in `check-citations`.
|
||||||
|
* a game asset that **is tracked by git** — issue #49 says it must not be.
|
||||||
* a capture that **no page cites** — not an error. It may be evidence a page
|
* a capture that **no page cites** — not an error. It may be evidence a page
|
||||||
should have cited, and deleting on that basis would silently ratify the
|
should have cited, and deleting on that basis would silently ratify the
|
||||||
omission. Reported, counted, never failed on.
|
omission. Reported, counted, never failed on.
|
||||||
@@ -65,7 +72,16 @@ def committed() -> tuple[set[str], set[str]]:
|
|||||||
proposed. `tools/port/check-citations` uses the working tree for exactly
|
proposed. `tools/port/check-citations` uses the working tree for exactly
|
||||||
this reason; so does this.
|
this reason; so does this.
|
||||||
"""
|
"""
|
||||||
files = {l for l in git("ls-files", ROOT).splitlines() if l}
|
# 🔴 NOT `git ls-files`: since #49 the assets are deliberately untracked, so
|
||||||
|
# the index no longer knows they exist. Presence is a question about the
|
||||||
|
# working tree, and asking git would report every screenshot as missing and
|
||||||
|
# fail on all 203 citations.
|
||||||
|
files = set()
|
||||||
|
for dirpath, _dirnames, filenames in os.walk(ROOT):
|
||||||
|
for fn in filenames:
|
||||||
|
if fn == ".gitignore":
|
||||||
|
continue
|
||||||
|
files.add(os.path.join(dirpath, fn).replace(os.sep, "/"))
|
||||||
dirs = set()
|
dirs = set()
|
||||||
for f in files:
|
for f in files:
|
||||||
parts = f.split("/")
|
parts = f.split("/")
|
||||||
@@ -74,6 +90,24 @@ def committed() -> tuple[set[str], set[str]]:
|
|||||||
return files, dirs
|
return files, dirs
|
||||||
|
|
||||||
|
|
||||||
|
ASSET_SUFFIXES = (".png", ".jpg", ".jpeg", ".bin")
|
||||||
|
|
||||||
|
|
||||||
|
def tracked_assets() -> list[str]:
|
||||||
|
"""Game assets that are committed — forbidden since issue #49.
|
||||||
|
|
||||||
|
The repo carries code, tooling and docs. A screenshot that sneaks back in
|
||||||
|
is invisible in review (a binary shows as "Bin 0 -> 1234567 bytes") and is
|
||||||
|
permanent once merged, because removing it later needs a history rewrite.
|
||||||
|
So this is the half of the check that has to be loud.
|
||||||
|
"""
|
||||||
|
out = []
|
||||||
|
for l in git("ls-files").splitlines():
|
||||||
|
if l.lower().endswith(ASSET_SUFFIXES):
|
||||||
|
out.append(l)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def cited() -> set[str]:
|
def cited() -> set[str]:
|
||||||
raw = git("grep", "-rhoE", CITE_ERE, "--", *SEARCH, SELF).splitlines()
|
raw = git("grep", "-rhoE", CITE_ERE, "--", *SEARCH, SELF).splitlines()
|
||||||
out = set()
|
out = set()
|
||||||
@@ -141,7 +175,7 @@ def selftest() -> int:
|
|||||||
real_dir = next(iter(dirs), None)
|
real_dir = next(iter(dirs), None)
|
||||||
real_file = next(iter(files), None)
|
real_file = next(iter(files), None)
|
||||||
if not real_dir or not real_file:
|
if not real_dir or not real_file:
|
||||||
print("selftest: 🔴 no captures committed — nothing to test against")
|
print("selftest: 🔴 no captures present — nothing to test against")
|
||||||
return 2
|
return 2
|
||||||
cases = [
|
cases = [
|
||||||
("planted dangling caught", "docs/re/captures/no-such-file-anywhere.png", False),
|
("planted dangling caught", "docs/re/captures/no-such-file-anywhere.png", False),
|
||||||
@@ -168,7 +202,15 @@ def selftest() -> int:
|
|||||||
print(" %-34s %s"
|
print(" %-34s %s"
|
||||||
% ("own fixtures not counted", "🔴 FAILED" if fixture_counted else "ok"))
|
% ("own fixtures not counted", "🔴 FAILED" if fixture_counted else "ok"))
|
||||||
|
|
||||||
ok = gathering_ok and not fixture_counted
|
# 🔴 THE #49 RULE NEEDS ITS OWN TICK. Presence now comes from the working
|
||||||
|
# tree, so a checkout with the captures present looks identical whether or
|
||||||
|
# not they are tracked — only this assertion can tell the difference.
|
||||||
|
stowaways = tracked_assets()
|
||||||
|
print(" %-34s %s%s"
|
||||||
|
% ("no game asset tracked", "ok" if not stowaways else "🔴 FAILED",
|
||||||
|
"" if not stowaways else " (%d tracked)" % len(stowaways)))
|
||||||
|
|
||||||
|
ok = gathering_ok and not fixture_counted and not stowaways
|
||||||
for name, path, want in cases:
|
for name, path, want in cases:
|
||||||
got = resolves(path.rstrip(".,;:)`"), files, dirs)
|
got = resolves(path.rstrip(".,;:)`"), files, dirs)
|
||||||
mark = "ok" if got == want else "🔴 FAILED"
|
mark = "ok" if got == want else "🔴 FAILED"
|
||||||
@@ -188,19 +230,33 @@ def main() -> int:
|
|||||||
print(f)
|
print(f)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
print("captures committed : %d" % len(files))
|
stowaways = tracked_assets()
|
||||||
|
print("captures present on disk : %d" % len(files))
|
||||||
print(" cited by a page or a tool : %d" % (len(files) - len(orphans)))
|
print(" cited by a page or a tool : %d" % (len(files) - len(orphans)))
|
||||||
print(" cited by nothing : %d (reported, not failed —" % len(orphans))
|
print(" cited by nothing : %d (reported, not failed —" % len(orphans))
|
||||||
print(" an orphan may be evidence a page owes)")
|
print(" an orphan may be evidence a page owes)")
|
||||||
|
rc = 0
|
||||||
if dangling:
|
if dangling:
|
||||||
print(" 🔴 cited but NOT committed: %d" % len(dangling))
|
print(" 🔴 cited but NOT present : %d" % len(dangling))
|
||||||
for d in dangling:
|
for d in dangling:
|
||||||
print(" %s" % d)
|
print(" %s" % d)
|
||||||
print("\n🔴 a reader following those gets nothing. Commit the capture, fix the")
|
print("\n🔴 a reader following those gets nothing. Restore the capture, fix the")
|
||||||
print(" path, or drop the citation.")
|
print(" path, or drop the citation.")
|
||||||
return 1
|
rc = 1
|
||||||
print(" 🔴 cited but NOT committed: 0")
|
else:
|
||||||
return 0
|
print(" 🔴 cited but NOT present : 0")
|
||||||
|
|
||||||
|
if stowaways:
|
||||||
|
print(" 🔴 game assets TRACKED : %d (issue #49 — code, tooling, docs only)" % len(stowaways))
|
||||||
|
for a in stowaways[:10]:
|
||||||
|
print(" %s" % a)
|
||||||
|
if len(stowaways) > 10:
|
||||||
|
print(" … and %d more" % (len(stowaways) - 10))
|
||||||
|
print("\n🔴 run `git rm --cached` on those; they stay on disk and stay ignored.")
|
||||||
|
rc = 1
|
||||||
|
else:
|
||||||
|
print(" 🔴 game assets TRACKED : 0")
|
||||||
|
return rc
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user