Fix SteamTracking freshness and hardware signals

This commit is contained in:
seanbetts committed 2026-05-31 11:13:43 +01:00
1 parent c306b67472
commit 98f9a51171
8 files changed
+411 -2

No files matched your search

+3 -1
View File
@@ -91,12 +91,14 @@ Optional:
chmod +x scripts/*.sh chmod +x scripts/*.sh
``` ```
3. Ensure `SteamTracking` is available locally, for example at: 3. Ensure `SteamTracking` is available locally as a git checkout, for example at:
```text ```text
/tmp/SteamTracking-master /tmp/SteamTracking-master
``` ```
`scripts/check_steamtracking.sh` fast-forwards this checkout before scanning and records the before/after commit in each run.
4. Run a watch pass: 4. Run a watch pass:
```sh ```sh
+9
View File
@@ -216,9 +216,12 @@ The helper loads `.local/steamdb-env.sh` automatically and reuses the live brows
### 4. SteamTracking / GameTracking ### 4. SteamTracking / GameTracking
Use [scripts/check_steamtracking.sh](scripts/check_steamtracking.sh). When the tracking directory is a git checkout, the helper updates it with a fast-forward pull before scanning, records before/after commit metadata in `steamtracking-source.txt`, and fails loudly if the checkout is invalid or missing core extracted content.
Look for: Look for:
- onboarding text - onboarding text
- guided-tour and welcome-tour strings, especially `GuidedTour_SteamMachine_*` and `GuidedTour_SteamFrame_*`
- firmware update strings - firmware update strings
- pairing flow strings - pairing flow strings
- device type names or codenames - device type names or codenames
@@ -226,6 +229,12 @@ Look for:
- Steam client ARM64 beta/publicbeta manifest movement, especially `steam_client_beta_linuxarm64` - Steam client ARM64 beta/publicbeta manifest movement, especially `steam_client_beta_linuxarm64`
- support or OOBE changes - support or OOBE changes
Hardware backend signal focus:
- Review `steamtracking-hardware-signals-key-lines.txt` every run before dismissing SteamTracking as unchanged.
- Treat setup-tour/OOBE additions as launch-readiness signals when they name Machine, Frame, SteamOS, codenames, or watched package IDs.
- Compare this report between runs; it is designed to catch semantic client/backend additions that are not visible from manifest hashes alone.
Client manifest focus: Client manifest focus:
- Treat `steam_client_beta_linuxarm64` as a high-signal Steam Machine / Fremont watch target. - Treat `steam_client_beta_linuxarm64` as a high-signal Steam Machine / Fremont watch target.
+47 -1
View File
@@ -17,6 +17,46 @@ if [ ! -d "$TRACKING_DIR" ]; then
exit 1 exit 1
fi fi
SOURCE_REPORT="$OUT_DIR/steamtracking-source.txt"
if [ -d "$TRACKING_DIR/.git" ]; then
if ! git -C "$TRACKING_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "invalid SteamTracking git checkout: $TRACKING_DIR" >&2
exit 1
fi
BEFORE_COMMIT="$(git -C "$TRACKING_DIR" rev-parse HEAD)"
BEFORE_TIME="$(git -C "$TRACKING_DIR" log -1 --format=%cI)"
if [ "${STEAMTRACKING_UPDATE:-1}" = "1" ]; then
BRANCH="$(git -C "$TRACKING_DIR" rev-parse --abbrev-ref HEAD)"
if [ "$BRANCH" = "HEAD" ]; then
git -C "$TRACKING_DIR" fetch --prune --quiet
else
git -C "$TRACKING_DIR" pull --ff-only --quiet
fi
fi
AFTER_COMMIT="$(git -C "$TRACKING_DIR" rev-parse HEAD)"
AFTER_TIME="$(git -C "$TRACKING_DIR" log -1 --format=%cI)"
{
printf 'tracking_dir=%s\n' "$TRACKING_DIR"
printf 'before_commit=%s\n' "$BEFORE_COMMIT"
printf 'before_time=%s\n' "$BEFORE_TIME"
printf 'after_commit=%s\n' "$AFTER_COMMIT"
printf 'after_time=%s\n' "$AFTER_TIME"
printf 'updated=%s\n' "$([ "$BEFORE_COMMIT" = "$AFTER_COMMIT" ] && printf no || printf yes)"
} > "$SOURCE_REPORT"
else
{
printf 'tracking_dir=%s\n' "$TRACKING_DIR"
printf 'git_checkout=no\n'
} > "$SOURCE_REPORT"
fi
if [ ! -d "$TRACKING_DIR/ClientManifest" ] && [ ! -d "$TRACKING_DIR/ClientExtracted" ]; then
echo "SteamTracking checkout has no ClientManifest or ClientExtracted content: $TRACKING_DIR" >&2
exit 1
fi
rg -n -i "Triton|Ibex|Puck|Steam Controller|firmware|pair|dock|dongle" \ rg -n -i "Triton|Ibex|Puck|Steam Controller|firmware|pair|dock|dongle" \
"$TRACKING_DIR" \ "$TRACKING_DIR" \
> "$OUT_DIR/steamtracking-hits.txt" || true > "$OUT_DIR/steamtracking-hits.txt" || true
@@ -29,7 +69,13 @@ python3 "$SCRIPT_DIR/parse_steamtracking_client_manifests.py" \
--run-dir "$RUN_DIR" \ --run-dir "$RUN_DIR" \
--tracking-dir "$TRACKING_DIR" --tracking-dir "$TRACKING_DIR"
python3 "$SCRIPT_DIR/parse_steamtracking_hardware_signals.py" \
--run-dir "$RUN_DIR" \
--tracking-dir "$TRACKING_DIR"
printf '%s\n' \ printf '%s\n' \
"Saved SteamTracking source metadata to $SOURCE_REPORT" \
"Saved SteamTracking grep output to $OUT_DIR/steamtracking-hits.txt" \ "Saved SteamTracking grep output to $OUT_DIR/steamtracking-hits.txt" \
"Saved pairing-focused grep output to $OUT_DIR/steamtracking-pairing-focus.txt" \ "Saved pairing-focused grep output to $OUT_DIR/steamtracking-pairing-focus.txt" \
"Saved client manifest report to $OUT_DIR/steamtracking-client-manifests.md" "Saved client manifest report to $OUT_DIR/steamtracking-client-manifests.md" \
"Saved hardware signal report to $OUT_DIR/steamtracking-hardware-signals.md"
+2
View File
@@ -124,6 +124,8 @@ def build_summary(prev_dir: Path, curr_dir: Path):
deltas.extend(compare_report_sets(prev_dir, curr_dir, "Komodo frame media", "komodo-frame-media.tsv")) deltas.extend(compare_report_sets(prev_dir, curr_dir, "Komodo frame media", "komodo-frame-media.tsv"))
deltas.extend(compare_text_lines(prev_dir, curr_dir, "SteamKit/PICS", "steamkit-pics-key-lines.txt")) deltas.extend(compare_text_lines(prev_dir, curr_dir, "SteamKit/PICS", "steamkit-pics-key-lines.txt"))
deltas.extend(compare_text_lines(prev_dir, curr_dir, "SteamDB", "steamdb-key-lines.txt")) deltas.extend(compare_text_lines(prev_dir, curr_dir, "SteamDB", "steamdb-key-lines.txt"))
deltas.extend(compare_text_lines(prev_dir, curr_dir, "SteamTracking source", "steamtracking-source.txt"))
deltas.extend(compare_text_lines(prev_dir, curr_dir, "SteamTracking hardware signals", "steamtracking-hardware-signals-key-lines.txt"))
deltas.extend(compare_text_lines(prev_dir, curr_dir, "Steam client manifests", "steamtracking-client-manifests-key-lines.txt")) deltas.extend(compare_text_lines(prev_dir, curr_dir, "Steam client manifests", "steamtracking-client-manifests-key-lines.txt"))
deltas.extend(compare_text_lines(prev_dir, curr_dir, "SteamVR depots", "steamvr-depots-key-lines.txt")) deltas.extend(compare_text_lines(prev_dir, curr_dir, "SteamVR depots", "steamvr-depots-key-lines.txt"))
deltas.extend(compare_text_lines(prev_dir, curr_dir, "SteamOS mirror", "steamos-mirror-key-lines.txt")) deltas.extend(compare_text_lines(prev_dir, curr_dir, "SteamOS mirror", "steamos-mirror-key-lines.txt"))
+6
View File
@@ -50,6 +50,9 @@ def build_draft(run_dir: Path):
steamdb_lines = first_n_nonempty(reports / "steamdb-key-lines.txt", 8) steamdb_lines = first_n_nonempty(reports / "steamdb-key-lines.txt", 8)
valve_lines = first_n_nonempty(reports / "valve-key-lines.txt", 8) valve_lines = first_n_nonempty(reports / "valve-key-lines.txt", 8)
tracking_lines = first_n_nonempty(reports / "steamtracking-pairing-focus.txt", 8) tracking_lines = first_n_nonempty(reports / "steamtracking-pairing-focus.txt", 8)
hardware_signal_lines = first_n_nonempty(
reports / "steamtracking-hardware-signals-key-lines.txt", 8
)
client_manifest_lines = first_n_nonempty( client_manifest_lines = first_n_nonempty(
reports / "steamtracking-client-manifests-key-lines.txt", 8 reports / "steamtracking-client-manifests-key-lines.txt", 8
) )
@@ -130,6 +133,9 @@ def build_draft(run_dir: Path):
lines.extend([f"- `{line}`" for line in tracking_lines]) lines.extend([f"- `{line}`" for line in tracking_lines])
else: else:
lines.append("- No focused SteamTracking report found.") lines.append("- No focused SteamTracking report found.")
if hardware_signal_lines:
lines.extend(["", "Steam hardware backend signal focus:"])
lines.extend([f"- `{line}`" for line in hardware_signal_lines])
if client_manifest_lines: if client_manifest_lines:
lines.extend(["", "Steam client manifest focus:"]) lines.extend(["", "Steam client manifest focus:"])
lines.extend([f"- `{line}`" for line in client_manifest_lines]) lines.extend([f"- `{line}`" for line in client_manifest_lines])
+155
View File
@@ -0,0 +1,155 @@
#!/usr/bin/env python3
import argparse
import csv
import re
from pathlib import Path
SEARCH_ROOTS = (
"ClientExtracted",
"ClientManifest",
"Protobufs",
"ProtobufsWebui",
"Random",
"Strings",
"c/ClientExtracted",
)
SKIP_SUFFIXES = {
".avif",
".bmp",
".gif",
".gz",
".jpg",
".jpeg",
".mp4",
".png",
".webm",
".zip",
}
HARDWARE_RE = re.compile(
r"Steam\s*Machine|SteamMachine|k_ESteamMachine|Steam\s*Frame|SteamFrame|k_ESteamFrame|"
r"Steam\s*Controller|Triton|Ibex|Puck|Fremont|Deckard|Roy|"
r"4165910|4165890|1629446|1629447|1629458|1629460|1629484|1629486",
re.IGNORECASE,
)
SIGNAL_RE = re.compile(
r"GuidedTour|WelcomeTour|Welcome|Tour|OOBE|onboarding|first.run|reservation|reserve|"
r"preorder|pre-order|purchase|availability|microSD|SDCard|compatibility|verified|"
r"pair|firmware|dongle|adapter|package|manifest",
re.IGNORECASE,
)
def iter_files(tracking_dir: Path):
for root in SEARCH_ROOTS:
base = tracking_dir / root
if not base.exists():
continue
if base.is_file():
yield base
continue
for path in base.rglob("*"):
if not path.is_file():
continue
if path.suffix.lower() in SKIP_SUFFIXES:
continue
yield path
def classify(line: str):
hardware = HARDWARE_RE.search(line)
signal = SIGNAL_RE.search(line)
if not hardware or not signal:
return ""
if re.search(r"GuidedTour|Welcome|Tour|OOBE|onboarding", line, re.IGNORECASE):
return "setup-tour"
if re.search(r"reservation|reserve|preorder|pre-order|purchase|availability|package", line, re.IGNORECASE):
return "commerce"
if re.search(r"pair|firmware|dongle|adapter|Triton|Ibex|Puck", line, re.IGNORECASE):
return "controller"
if re.search(r"compatibility|verified", line, re.IGNORECASE):
return "compatibility"
return "hardware"
def build_rows(tracking_dir: Path):
rows = []
for path in iter_files(tracking_dir):
try:
text = path.read_text(encoding="utf-8", errors="ignore")
except OSError:
continue
rel_path = path.relative_to(tracking_dir).as_posix()
for line_number, line in enumerate(text.splitlines(), start=1):
category = classify(line)
if not category:
continue
rows.append(
{
"category": category,
"path": rel_path,
"line": str(line_number),
"text": line.strip(),
}
)
return rows
def write_tsv(rows, path: Path):
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8", newline="") as handle:
writer = csv.DictWriter(handle, fieldnames=("category", "path", "line", "text"), delimiter="\t")
writer.writeheader()
writer.writerows(rows)
def write_key_lines(rows, path: Path):
lines = []
seen = set()
for row in rows:
line = f"{row['category']}\t{row['path']}:{row['line']}\t{row['text']}"
if line in seen:
continue
seen.add(line)
lines.append(line)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("\n".join(lines) + ("\n" if lines else ""), encoding="utf-8")
def write_markdown(rows, path: Path):
lines = [
"# SteamTracking Hardware Signals",
"",
"Focused SteamTracking scan for Valve hardware setup, commerce, compatibility, and controller backend signals.",
"",
"| Category | Source | Text |",
"| --- | --- | --- |",
]
for row in rows[:200]:
text = row["text"].replace("|", "\\|")
lines.append(f"| {row['category']} | `{row['path']}:{row['line']}` | `{text}` |")
if len(rows) > 200:
lines.extend(["", f"_Truncated markdown table to first 200 of {len(rows)} matches._"])
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--run-dir", required=True, type=Path)
parser.add_argument("--tracking-dir", required=True, type=Path)
args = parser.parse_args()
reports = args.run_dir / "reports"
rows = build_rows(args.tracking_dir)
rows.sort(key=lambda row: (row["category"], row["path"], int(row["line"]), row["text"]))
write_tsv(rows, reports / "steamtracking-hardware-signals.tsv")
write_key_lines(rows, reports / "steamtracking-hardware-signals-key-lines.txt")
write_markdown(rows, reports / "steamtracking-hardware-signals.md")
if __name__ == "__main__":
main()
+7
View File
@@ -22,6 +22,12 @@ FILTER_TERMS = (
"xr", "xr",
"vr", "vr",
"dongle", "dongle",
"guidedtour",
"welcome",
"tour",
"oobe",
"sdcard",
"microsd",
) )
REJECT_TERMS = ( REJECT_TERMS = (
"application_config", "application_config",
@@ -153,6 +159,7 @@ def build(run_dir: Path):
notable.extend([f"- SteamKit/PICS: `{line}`" for line in filtered_first(reports / "steamkit-pics-key-lines.txt", 8)]) notable.extend([f"- SteamKit/PICS: `{line}`" for line in filtered_first(reports / "steamkit-pics-key-lines.txt", 8)])
notable.extend([f"- SteamDB: `{line}`" for line in filtered_first(reports / "steamdb-key-lines.txt", 5)]) notable.extend([f"- SteamDB: `{line}`" for line in filtered_first(reports / "steamdb-key-lines.txt", 5)])
notable.extend([f"- SteamTracking: `{line}`" for line in filtered_first(reports / "steamtracking-pairing-focus.txt", 5)]) notable.extend([f"- SteamTracking: `{line}`" for line in filtered_first(reports / "steamtracking-pairing-focus.txt", 5)])
notable.extend([f"- SteamTracking hardware: `{line}`" for line in filtered_first(reports / "steamtracking-hardware-signals-key-lines.txt", 8)])
notable.extend([f"- Steam client manifests: `{line}`" for line in filtered_first(reports / "steamtracking-client-manifests-key-lines.txt", 8)]) notable.extend([f"- Steam client manifests: `{line}`" for line in filtered_first(reports / "steamtracking-client-manifests-key-lines.txt", 8)])
notable.extend([f"- SteamVR depots: `{line}`" for line in filtered_first(reports / "steamvr-depots-key-lines.txt", 5)]) notable.extend([f"- SteamVR depots: `{line}`" for line in filtered_first(reports / "steamvr-depots-key-lines.txt", 5)])
notable.extend([f"- SteamOS mirror: `{line}`" for line in filtered_first(reports / "steamos-mirror-key-lines.txt", 5)]) notable.extend([f"- SteamOS mirror: `{line}`" for line in filtered_first(reports / "steamos-mirror-key-lines.txt", 5)])
@@ -151,6 +151,109 @@ class SteamTrackingClientManifestTests(unittest.TestCase):
) )
self.assertIn("steam_client_beta_linuxarm64", key_lines) self.assertIn("steam_client_beta_linuxarm64", key_lines)
def test_check_steamtracking_updates_git_checkout_before_scanning(self):
with TemporaryDirectory() as tmp:
tmp_path = Path(tmp)
source = tmp_path / "source"
tracking = tmp_path / "tracking"
subprocess.run(["git", "init", str(source)], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
subprocess.run(["git", "-C", str(source), "config", "user.email", "test@example.com"], check=True)
subprocess.run(["git", "-C", str(source), "config", "user.name", "Test User"], check=True)
(source / "ClientManifest").mkdir()
(source / "ClientManifest" / "steam_client_beta_linuxarm64").write_text(
manifest("1779139477"),
encoding="utf-8",
)
(source / "ClientExtracted" / "steamui" / "localization").mkdir(parents=True)
(source / "ClientExtracted" / "steamui" / "localization" / "steamui_english.json").write_text(
"{}\n",
encoding="utf-8",
)
subprocess.run(["git", "-C", str(source), "add", "."], check=True)
subprocess.run(["git", "-C", str(source), "commit", "-m", "old"], check=True, stdout=subprocess.PIPE)
subprocess.run(["git", "clone", str(source), str(tracking)], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(source / "ClientExtracted" / "steamui" / "localization" / "steamui_english.json").write_text(
'{"GuidedTour_SteamMachine_Welcome_Title": "Welcome to Steam Machine"}\n',
encoding="utf-8",
)
subprocess.run(["git", "-C", str(source), "add", "."], check=True)
subprocess.run(["git", "-C", str(source), "commit", "-m", "new"], check=True, stdout=subprocess.PIPE)
latest = subprocess.run(
["git", "-C", str(source), "rev-parse", "HEAD"],
check=True,
text=True,
stdout=subprocess.PIPE,
).stdout.strip()
run_dir = tmp_path / "runs" / "2026-05-31"
result = subprocess.run(
[str(ROOT / "scripts" / "check_steamtracking.sh"), str(run_dir), str(tracking)],
cwd=ROOT,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
self.assertEqual("", result.stderr)
self.assertEqual(0, result.returncode)
checked = subprocess.run(
["git", "-C", str(tracking), "rev-parse", "HEAD"],
check=True,
text=True,
stdout=subprocess.PIPE,
).stdout.strip()
self.assertEqual(latest, checked)
key_lines = (run_dir / "reports" / "steamtracking-hardware-signals-key-lines.txt").read_text(
encoding="utf-8"
)
self.assertIn("GuidedTour_SteamMachine_Welcome_Title", key_lines)
def test_hardware_signal_report_catches_steam_machine_guided_tour(self):
with TemporaryDirectory() as tmp:
tmp_path = Path(tmp)
tracking = tmp_path / "tracking"
localization = tracking / "ClientExtracted" / "steamui" / "localization"
localization.mkdir(parents=True)
(localization / "steamui_english.json").write_text(
textwrap.dedent(
"""
{
"GuidedTour_SteamMachine_Welcome_Title": "Welcome to Steam Machine",
"GuidedTour_SDCard_Title_SteamMachine": "Last of all, Steam Machine is equipped with a microSD card slot"
}
"""
).strip()
+ "\n",
encoding="utf-8",
)
run_dir = tmp_path / "runs" / "2026-05-31"
result = subprocess.run(
[
"python3",
str(ROOT / "scripts" / "parse_steamtracking_hardware_signals.py"),
"--run-dir",
str(run_dir),
"--tracking-dir",
str(tracking),
],
cwd=ROOT,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
self.assertEqual("", result.stderr)
self.assertEqual(0, result.returncode)
key_lines = (run_dir / "reports" / "steamtracking-hardware-signals-key-lines.txt").read_text(
encoding="utf-8"
)
self.assertIn("GuidedTour_SteamMachine_Welcome_Title", key_lines)
self.assertIn("GuidedTour_SDCard_Title_SteamMachine", key_lines)
def test_compare_runs_includes_client_manifest_key_line_deltas(self): def test_compare_runs_includes_client_manifest_key_line_deltas(self):
with TemporaryDirectory() as tmp: with TemporaryDirectory() as tmp:
tmp_path = Path(tmp) tmp_path = Path(tmp)
@@ -192,6 +295,85 @@ class SteamTrackingClientManifestTests(unittest.TestCase):
self.assertIn("Steam client manifests added line", compare) self.assertIn("Steam client manifests added line", compare)
self.assertIn("1779139477", compare) self.assertIn("1779139477", compare)
def test_compare_runs_includes_hardware_signal_key_line_deltas(self):
with TemporaryDirectory() as tmp:
tmp_path = Path(tmp)
previous = tmp_path / "2026-05-30"
current = tmp_path / "2026-05-31"
(previous / "reports").mkdir(parents=True)
(current / "reports").mkdir(parents=True)
(previous / "reports" / "steamtracking-hardware-signals-key-lines.txt").write_text("", encoding="utf-8")
(current / "reports" / "steamtracking-hardware-signals-key-lines.txt").write_text(
"ClientExtracted/steamui/localization/steamui_english.json:GuidedTour_SteamMachine_Welcome_Title=Welcome to Steam Machine\n",
encoding="utf-8",
)
output = tmp_path / "compare.md"
result = subprocess.run(
[
"python3",
str(ROOT / "scripts" / "compare_runs.py"),
"--previous",
str(previous),
"--current",
str(current),
"--output",
str(output),
],
cwd=ROOT,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
self.assertEqual("", result.stderr)
self.assertEqual(0, result.returncode)
compare = output.read_text(encoding="utf-8")
self.assertIn("SteamTracking hardware signals added line", compare)
self.assertIn("GuidedTour_SteamMachine_Welcome_Title", compare)
def test_status_draft_and_run_summary_include_hardware_signals(self):
with TemporaryDirectory() as tmp:
run_dir = Path(tmp) / "2026-05-31"
reports = run_dir / "reports"
reports.mkdir(parents=True)
(reports / "steamtracking-hardware-signals-key-lines.txt").write_text(
"setup-tour\tClientExtracted/steamui/localization/steamui_english.json:8417\t"
'"GuidedTour_SteamMachine_Welcome_Title": "Welcome to Steam Machine",\n',
encoding="utf-8",
)
summary = subprocess.run(
["python3", str(ROOT / "scripts" / "write_run_summary.py"), "--run-dir", str(run_dir)],
cwd=ROOT,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
draft = subprocess.run(
["python3", str(ROOT / "scripts" / "draft_status_update.py"), "--run-dir", str(run_dir)],
cwd=ROOT,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
self.assertEqual("", summary.stderr)
self.assertEqual(0, summary.returncode)
self.assertEqual("", draft.stderr)
self.assertEqual(0, draft.returncode)
self.assertIn(
"GuidedTour_SteamMachine_Welcome_Title",
(reports / "run-summary.md").read_text(encoding="utf-8"),
)
self.assertIn(
"GuidedTour_SteamMachine_Welcome_Title",
(reports / "status-draft.md").read_text(encoding="utf-8"),
)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()