From 1ea5d664a3ca97ac4e9a4e28650ca62430838bf0 Mon Sep 17 00:00:00 2001 From: flashls1 Date: Fri, 11 Sep 2026 01:13:34 -0500 Subject: [PATCH 01/12] Plan four-hour incremental TFT optimization sweep --- .../IMPLEMENTATION_PLAN.md | 67 ++++++++++++ .../PREFLIGHT.md | 102 ++++++++++++++++++ .../ZENGATE_RECEIPT.json | 27 +++++ .../ZENMC_RECEIPT.json | 36 +++++++ 4 files changed, 232 insertions(+) create mode 100644 .clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/IMPLEMENTATION_PLAN.md create mode 100644 .clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/PREFLIGHT.md create mode 100644 .clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENGATE_RECEIPT.json create mode 100644 .clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_RECEIPT.json diff --git a/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/IMPLEMENTATION_PLAN.md b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000..641e5e7 --- /dev/null +++ b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/IMPLEMENTATION_PLAN.md @@ -0,0 +1,67 @@ +# Implementation Plan — four-hour evidence-grounded incremental optimization sweep + +## Execution contract + +1. Converge only the proven current DEVHighPerf resource authority into this change: copy exact current `DeviceProfiles.ini`, `profile-transaction.sh`, and matching README from preserved `bddd2d6c...` resource state. Do not import its stale facts/project docs or unrelated login work. Update source hashes/tests only where the repository's own verifier requires it. +2. Add an incremental-candidate manifest separate from historical/PBE candidate inventories. Initial queue is `pso-precompile-threads-2`, `shader-background-batch-4`, `animation-budget-5ms`; `animation-budget-4ms` is conditional on a confirmed 5ms win. +3. Generalize OvernightLab's proven DeviceProfiles bind-overlay implementation from direct-Vulkan-only to exact one-CVar profile overlays. Candidate generation must diff parsed section/key/value maps and fail unless the only semantic differences are every expected occurrence of the one declared CVar. +4. Preserve existing direct-Vulkan helper behavior as historical code, but the new incremental queue never selects it. +5. Add incremental campaign state containing: base merged authority SHA, installed DEV resource hashes, current campaign working-profile SHA, cumulative accepted deltas, candidate queue index, matched control/candidate/confirmation run IDs, exact native capture IDs, deadline, and rollback state. Atomic checkpoint writes only. +6. Add matched comparison logic using same-stage native performance windows and current v4 doctrine. Material vetoes: correctness/usability failure, >=5% weighted-FPS regression, >=10% 1%-low regression, >=10% p95/p99 regression, client/RHI/config identity mismatch, telemetry invalidity, or unproven rollback. Any valid directional gain without a veto is PROMISING and requires confirmation. +7. Use one current campaign control before each new candidate. Generate candidate from the current campaign working profile. A confirmed positive candidate becomes the campaign working profile for subsequent tests; reject/inconclusive restores the prior working profile. +8. Keep the screen/navigation workload fixed. Use the smallest repeatable Tocker workload that still exercises the heavy path and yields same-stage native windows; prefer the already-comparable 1-5 stage with a small bounded window count. Do not generate unbounded traces/profilers. +9. Efficiency rules: no Xcode/app rebuild during candidate runs; no SDK/emulator/AVD copy; no root-only cache inventory unless explicitly required; no repeated research/discovery during the run; one full DEV launch only when needed for a control, candidate, or confirmation. Reuse existing classifier and installed app. +10. Before the live run, run static/self/fault tests plus repository validation once. The one validation build is a safety gate, not a per-candidate rebuild. +11. Install/update only the small source-controlled OvernightLab controller/manifest into `/Volumes/MAC MINI M4/TFTMAC/OvernightLab` using the existing install seam. Preserve generated database/campaign outputs. +12. Start the local campaign under `caffeinate` with an exact four-hour deadline. Store raw generated campaign data outside Git. Attach a durable Clara operation watcher so browser/context boundaries do not abandon the run. +13. Runtime gates before every candidate: protected Control not running; exact current installed DEV integrity; official TFT version; 1920x1080/320/60; effective 8/6144; `OPENGL_ES_ANGLE`; latest campaign working profile mounted/effective; native telemetry available. +14. After every run, import/seal native SQLite/capture identity, write exact candidate/profile delta, metrics, decision, and rollback evidence. Generate/update the human/CSV report before queue advance. +15. If candidate is PROMISING, run one cold confirmation on the same candidate against the same campaign working baseline. Confirmed -> keep in cumulative working stack. Confirmation loss/inconclusive -> do not keep; restore baseline and continue. +16. Conditional animation-budget 4ms is legal only if 5ms confirmed positive. It is generated from the already-accepted 5ms working profile, preserving one-factor semantics. +17. Stop admitting new candidates when the four-hour deadline is reached. Let the currently active run finish or fail safely, then restore the latest verified campaign working profile / host baseline and close the campaign as `DEADLINE_COMPLETE` or `COMPLETE`. +18. On interruption/restart, reconcile actual DEV/qemu/ADB/profile mount/AVD journal state before resuming. Never replay an UNKNOWN external effect; restore first, then resume from the checkpoint's next unapplied candidate. +19. On any rollback failure, unknown profile mount, source/client/RHI mismatch, protected-Control activity, or unowned runtime process, fail closed and stop the queue. +20. At campaign end, produce the final report with candidate results, matched controls, confirmations, cumulative accepted stack, exact capture IDs/DB hashes, rejected/inconclusive reasons, final working profile hash, remaining gap to continuous 60 FPS, and recommended next evidence-backed candidate family. +21. Reconcile final confirmed combined result into `CHANGELOG.md` / `project.md` and `facts.md` only if it changes current hard authority. Do not invent a WIN identity without confirmation evidence. +22. Validate/review/publish the source/workflow change, require exact-SHA GitHub CI, merge it, and independently verify the local OvernightLab source/install still matches merged authority. Source-only project has no remote deployment target. + +## Candidate queue and rationale + +A. `pso-precompile-threads-2` +- baseline: `r.pso.PrecompileThreadPoolSize=4` +- candidate: `2` +- expected mechanism: reduce CPU contention from concurrent PSO precompile workers during interactive/heavy gameplay. +- risk: slower background shader precompile / more later misses; native tail/jank and shader/PSO signals decide. + +B. `shader-background-batch-4` +- baseline: `r.ShaderPipelineCache.BackgroundBatchSize=20` +- candidate: `4` +- expected mechanism: reduce burst CPU/driver work in interactive background cache compilation. +- risk: slower cache fill; reject if stalls/misses worsen despite reduced CPU contention. + +C. `animation-budget-5ms` +- baseline: `a.Budget.BudgetMs=6.0` +- candidate: `5.0` +- expected mechanism: earlier animation-budget throttling under unit-dense combat to protect game-thread frame time. +- risk: visible animation degradation; any correctness/usability regression vetoes promotion. + +D. `animation-budget-4ms` conditional +- baseline: campaign-confirmed 5.0ms stack +- candidate: `4.0` +- only runs after C confirms positive. +- same correctness/tail vetoes. + +## Explicit non-goals + +No PBE evidence. No custom Riot APK/shader modification. No Control/LKG mutation. No app/SDK/emulator/AVD clone. No per-candidate rebuild. No direct Vulkan. No transport experiments. No resolution/quality downgrade as the first route. No broad source-runtime research project. No speculative observability framework. No cleanup of unrelated worktrees. + +## Acceptance + +- Governance artifacts checkpointed before product/source mutation. +- ZenGate PASS + ZenMC PASS for exact lifecycle design. +- source/profile authority reconciled to proven installed current profile. +- static/self/fault and full repository validation PASS. +- live 4h campaign starts on current installed DEV with no protected-Control mutation. +- every advanced queue transition has proven rollback and durable report/checkpoint. +- campaign ends/restores safely at deadline or completion. +- final source delivery/CI/merge and post-merge local install verification PASS. diff --git a/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/PREFLIGHT.md b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/PREFLIGHT.md new file mode 100644 index 0000000..86189d0 --- /dev/null +++ b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/PREFLIGHT.md @@ -0,0 +1,102 @@ +# Preflight — four-hour evidence-grounded incremental TFT optimization sweep + +Date: 2026-09-11 America/Chicago +Change: `25f21f7e-ba86-4e3d-832d-9984522811c0` +Completion class: IMPLEMENT_SHIP +Base: merged `master` SHA `73761eaa7b7d7c965eb443bb7af5f96060ebffec` + +## Requested outcome + +Run a bounded four-hour local TFTMAC DEV optimization sweep that consumes the current authoritative specs and the surviving native telemetry, tests only research-backed adjustable settings with plausible performance upside, keeps repeatable net gains even when small, logs/reverts losers, and advances to the next candidate. Minimize time spent rebuilding or relaunching. Protected Control/LKG must remain untouched. + +## Current governing authority + +Read/reconciled current `facts.md`, `project.md`, `CHANGELOG.md`, `benchmark.md`, `dev.md`, `settings.md`, current OvernightLab authority/manifest/controller, current DEVHighPerf profile assets, current 60-FPS/fast-wins plans, research docs, Sept. 10 result logs, and surviving native SQLite captures. + +Current working authority remains: +- DEV winner `DEV-B8-WIN-01`. +- `/Applications/TFTMAC DEV.app`, advanced_diagnostics / StockShadow. +- 1920x1080 / 320 DPI / 60 Hz. +- effective 8 vCPU / 6144 MiB. +- official TFT `18.1-5423749` / `8423749`. +- selected game RHI `OPENGL_ES_ANGLE`. +- multifile cache ON, `preferSubmitAtFBOBoundary` disabled, `syncMonolithicPipelinesToBlobCache` removed. +- continuous useful 60 FPS is the cumulative target; there is no fixed positive-gain floor for a verified repeatable net improvement. + +## Logging audit + +The ignored derived OvernightLab database/screenshots from the closed prior worktree are unavailable and will not be reconstructed. Primary raw evidence survives in `~/Library/Application Support/TFTMAC/Modes/advanced_diagnostics/Captures/*/TFTMAC_NATIVE_RUNTIME.sqlite`. + +Read-only Sept. 10 SQL audit confirmed a wide performance envelope on the current line, including: +- a long near-60 run with 823,403 exact intervals and 8,974 available one-second windows; mean effective FPS about 59.834, median about 59.980, while 1% low/tails still expose nonzero pressure; +- heavier captures around 51.8–53.9 mean FPS with materially worse 1% lows and p95/p99; +- a later 49.7 mean-FPS run with poor tail behavior; +- queue-submit-inline produced no usable frame evidence and remains rejected. + +Conclusion: the current line is often close to 60 in light/planning states but still loses useful cadence under heavier CPU/RHI pressure. Candidate selection should target contention/tail work, not pixel-count shortcuts or already-rejected transport paths. + +## Host readiness + +- No current TFTMAC DEV core, owned StockShadow qemu/5586 process, or OvernightLab campaign was running at preflight. +- Internal free space approximately 21 GiB; external free space approximately 140 GiB. Both exceed the prior 8 GiB / 20 GiB campaign safety floors. +- Current installed DEV profile is signed/installed and available; no rebuild is required to begin profile-overlay experiments. + +## Material authority reconciliation discovered + +Merged master source is stale for exactly the DEVHighPerf profile currently installed and used by the valid native logs: +- installed/current DeviceProfiles SHA-256: `45d6465040ef8b7298cd8da000208e4ee0be924c65a20326900267f25adf5c9c`; +- merged-source DeviceProfiles SHA-256: `aa9672cd730e5e3c32e6c9a793a4ef98bef90d80124c7e22b196d2e174af07e1`; +- only effective profile delta: `r.MobileContentScaleFactor=0.0` installed/current versus `1.0` merged source, in both base sections; +- installed/current profile transaction SHA-256: `7d7d89df8db6927b09d4e2866aac932f9924b729584797e8364077102eeb5835` versus stale merged `5718319c...`; its only semantic delta is the expected current profile hash. + +The exact current profile/transaction/README already exist in preserved change `bddd2d6c-a2ed-46fb-9e9e-6674bfdf3541`. That change's old facts/project docs are stale and are NOT authority. Only the exact proven installed-resource bytes are admitted. This new change will converge only those three profile resource files before the new workflow runs. No unrelated bddd login work is imported. + +## Historical/research exclusions + +Do not automatically rerun: buffer retention, direct Vulkan, queue-submit-inline, virtual-queue-off, fence-contexts-off, historical ASG 400us/alternate rings/write steps/buffers, pipe transport, MoltenVK command-buffer/synchronous-submit variants, VirtioGpuNativeSync/Next, descriptor batching off, more RAM/vCPU as an assumed optimization, Home Run A / Riot Performance Mode, MSAA2, material-quality-1, OneFrameThreadLag=0, forced half-rate animation, blind 50% scale, broad shader prewarm, or prior rejected PBE candidates. + +Do not admit `r.MobileContentScaleFactor=1.0` in this first sweep: current accepted profile uses `0.0` native resolution; 1.0 intentionally lowers internal render scale and would trade image quality for FPS rather than test an efficiency unlock. + +Do not re-enable `preferSubmitAtFBOBoundary`: current ANGLE source history explicitly disables this for real vsynced applications because early submit did not benefit that workload class and could increase power; current WIN-01 remains aligned with that direction. + +## Evidence-grounded first queue + +1. `pso-precompile-threads-2`: `r.pso.PrecompileThreadPoolSize` 4 -> 2. Epic PSO precache guidance identifies this as the exact precompile-thread count and warns that many concurrent compile threads can be heavy during gameplay. Current guest has 8 vCPUs and heavy captures show CPU/RHI pressure. +2. `shader-background-batch-4`: `r.ShaderPipelineCache.BackgroundBatchSize` 20 -> 4. Epic FShaderPipelineCache documents this as background-mode PSO batch size and distinguishes background interactive use from fast/load-screen batches. This is a direct contention/burst-control hypothesis. +3. `animation-budget-5ms`: `a.Budget.BudgetMs` 6.0 -> 5.0. Epic Animation Budget Allocator documents this as the game-thread skeletal-animation budget; current heavy TFT battles are unit/animation dense, so a modest reduction may protect frame cadence while retaining visual correctness. +4. `animation-budget-4ms`: 5.0 -> 4.0 ONLY if the 5ms parent is positive and confirms. This is an adaptive refinement, not an unconditional queue item. + +Potential later candidates such as shader foreground BatchSize/BatchTime remain lower-confidence and are not added merely to fill time. + +## Simplest viable workflow + +Reuse OvernightLab's existing proven primitives: official GUI/session launch, current guest-property application before TFT, exact runtime/client/RHI verification, Tocker navigation, native SQLite frame measurement, bind-mounted private DeviceProfiles overlay, exact hash/readback, normal AppKit quit, AVD restoration, native capture import, rollback proof, checkpoint/resume, failure quarantine, and report generation. + +Do NOT create another app, SDK, emulator, AVD, VM, database service, profiler, or build farm. Do NOT rebuild TFTMAC per candidate. The campaign uses the already-installed DEV app and external exact-one-CVar profile overlays. + +Although a persistent one-emulator hot loop could save launch time, it adds new mount/session/configuration-identity/recovery states. The current robust full candidate lifecycle is already proven. For this four-hour pass the efficient safe baseline is ZERO app rebuilds and one necessary cold DEV lifecycle per control/candidate/confirmation, with short same-stage native measurements. Add a hot-loop state machine only if measured campaign timing later proves launch overhead is the limiting factor. + +## Measurement / promotion discipline + +- Each candidate is generated on the latest campaign working profile, not the frozen LKG. +- Each candidate differs from its campaign working profile by exactly one declared CVar; all repeated occurrences of that CVar are changed consistently and no other profile key may drift. +- Use current official client/RHI and native SurfaceFlinger-derived frame windows only. +- Compare the candidate to a current matched campaign control at the same Tocker stage using the v4 cumulative doctrine. +- Any directional gain may be `PROMISING` when no material regression/correctness/usability veto fires. +- PROMISING requires one cold confirmation before entering the campaign working stack. +- A confirmed campaign win advances the local working-profile stack. Formal `DEV-B8-WIN-##` project authority promotion is reconciled from the final combined confirmed stack, so the 4h automation does not churn source authority between every screening pair. +- Reject/inconclusive -> preserve result, restore current campaign working profile, move on. +- Unproven rollback -> stop immediately; never advance the queue. + +## ZenGate + +PASS. +- Every admitted candidate maps to a current exposed CVar, current runtime evidence, and an engine/runtime source describing the relevant performance mechanism. +- Already-resolved/stale/PBE/quality-sacrifice candidates are excluded. +- Existing OvernightLab lifecycle/rollback/native telemetry is reused. +- No per-candidate rebuild is required. +- New code is limited to generalizing the already-proven profile-overlay primitive, matched comparison/promotion logic, a bounded incremental manifest, and durable 4h orchestration. +- The protected Control/LKG and Riot-signed package remain outside mutation scope. +- Complexity removal test passes: no proposed new service/store/runtime/identifier is necessary beyond the campaign-local profile/result identities already required for exact comparison and rollback. + +ZenMC qualification: `ZENMC_REQUIRED` because the four-hour workflow includes durable asynchronous execution, restart/recovery, checkpoint/resume, candidate promotion, conditional follow-up, rollback, deadline, and failure combinations. diff --git a/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENGATE_RECEIPT.json b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENGATE_RECEIPT.json new file mode 100644 index 0000000..f71cf76 --- /dev/null +++ b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENGATE_RECEIPT.json @@ -0,0 +1,27 @@ +{ + "schema": 1, + "change_id": "25f21f7e-ba86-4e3d-832d-9984522811c0", + "observed_date": "2026-09-11 America/Chicago", + "result": "PASS", + "zenmc_qualification": "ZENMC_REQUIRED", + "simplest_viable_mechanism": "reuse installed TFTMAC DEV + existing OvernightLab lifecycle/native telemetry/profile bind overlay; zero per-candidate rebuilds", + "scope": [ + "reconcile exact current DEVHighPerf source resources", + "generalize one-CVar profile overlay", + "incremental candidate manifest and matched comparison", + "four-hour durable local campaign", + "logging, confirmation, cumulative campaign stack, rollback", + "source delivery after campaign" + ], + "excluded": [ + "PBE/historical candidate factory", + "protected Control/LKG mutation", + "new app/SDK/emulator/AVD/VM", + "direct Vulkan and rejected transport candidates", + "MSAA2/material-quality/half-rate animation/blind resolution downgrade", + "hot-loop state machine unless later evidence proves launch overhead is dominant", + "unbounded profiling or speculative observability" + ], + "complexity_economics": "PASS", + "removal_test": "Every retained addition is required for exact one-factor generation, durable four-hour continuation, matched decision/confirmation, or verified rollback. No new service/store/runtime layer is introduced." +} diff --git a/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_RECEIPT.json b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_RECEIPT.json new file mode 100644 index 0000000..8c59fe7 --- /dev/null +++ b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_RECEIPT.json @@ -0,0 +1,36 @@ +{ + "schema": 1, + "model": "TFTMAC_INCREMENTAL_4H_LIFECYCLE_V1", + "seed": 20260911, + "trajectories": 100000, + "queue": [ + "pso_threads_2", + "pso_background_batch_4", + "anim_budget_5ms", + "anim_budget_4ms_if_parent_wins" + ], + "invariants": { + "deadline_prevents_new_work": true, + "frozen_lkg_never_mutated": true, + "no_queue_advance_after_unproven_rollback": true, + "promotion_requires_positive_confirmation": true, + "verified_restore_before_next_candidate": true + }, + "stats": { + "candidate_promising": 51997, + "candidate_reject": 237459, + "complete": 90064, + "confirm_fail": 23120, + "deadline": 0, + "launch_fail": 7144, + "promoted": 28665, + "recovered_interrupt": 1781, + "rollback_fail": 2531, + "telemetry_invalid": 7647 + }, + "violation_count": 0, + "violations": [], + "result": "PASS", + "receipt_sha256": "d7d26be584b26853fc3e05d554764c8d8f0dae2fc67b09e2045f52da121ca03a", + "source_operation_id": "tftmac-4h-zenmc-20260911-v1" +} From a0621e8d96d31971494bfd740d5b3a96800160bf Mon Sep 17 00:00:00 2001 From: flashls1 Date: Fri, 11 Sep 2026 01:23:28 -0500 Subject: [PATCH 02/12] Add evidence-grounded four-hour incremental optimization workflow --- OvernightLab/incremental_lab.py | 490 ++++++++++++++++++ OvernightLab/install.command | 2 + .../manifests/incremental-candidates.json | 42 ++ tftmac/Assets/DEVHighPerf/DeviceProfiles.ini | 4 +- tftmac/Assets/DEVHighPerf/README.md | 2 +- .../Assets/DEVHighPerf/profile-transaction.sh | 2 +- tftmac/Runtime/TFTMACRuntime.swift | 4 +- 7 files changed, 540 insertions(+), 6 deletions(-) create mode 100755 OvernightLab/incremental_lab.py create mode 100644 OvernightLab/manifests/incremental-candidates.json mode change 100644 => 100755 tftmac/Assets/DEVHighPerf/profile-transaction.sh diff --git a/OvernightLab/incremental_lab.py b/OvernightLab/incremental_lab.py new file mode 100755 index 0000000..30e638f --- /dev/null +++ b/OvernightLab/incremental_lab.py @@ -0,0 +1,490 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import math +import os +import re +import shutil +import sqlite3 +import statistics +import time +import uuid +from datetime import datetime, timezone +from pathlib import Path +from typing import Any, Optional + +import overnight_lab as base + +ROOT = Path(__file__).resolve().parent +MANIFEST_PATH = ROOT / "manifests" / "incremental-candidates.json" +STATE_NAME = "incremental-state.json" + + +def utc_now() -> str: + return datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z") + + +def canonical_json(value: Any) -> str: + return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + + +class IncrementalLab(base.OvernightLab): + def __init__(self): + super().__init__() + self.incremental_manifest = base.read_json(MANIFEST_PATH) + self.installed_profile = self.dev_app / "Contents/Resources/DEVHighPerf/DeviceProfiles.ini" + self.installed_profile_sha = base.sha256_file(self.installed_profile) + expected = self.authority["current_dev_hashes"]["Contents/Resources/DEVHighPerf/DeviceProfiles.ini"] + if self.installed_profile_sha != expected: + raise base.LabError( + f"installed DEV profile is not current authority: {self.installed_profile_sha} != {expected}", + error_class="PROFILE_BASELINE_MISMATCH", component="profile", phase="preflight") + + def candidate_definition(self, spec: dict[str, Any]) -> dict[str, Any]: + return { + "id": spec["id"], + "family": spec["family"], + "kind": "profile_cvar", + "build_scope": "NO_BUILD", + "restart_class": "TFT_PROCESS_COLD", + "enabled": True, + "baseline": self.authority["working_version"], + "target_stages": list(self.incremental_manifest["target_stages"]), + "maximum_seconds": int(self.incremental_manifest["maximum_seconds_per_run"]), + "cvar": spec["cvar"], + "from": str(spec["from"]), + "to": str(spec["to"]), + "reason": spec["reason"], + "expected_rhi": self.authority["expected_normal_rhi"], + "decision_requires": ["native_combat_windows", "native_capture"], + } + + def ensure_incremental_candidates(self) -> None: + for spec in self.incremental_manifest["candidates"]: + c = self.candidate_definition(spec) + definition = canonical_json(c) + self.db.execute( + "INSERT OR REPLACE INTO candidates(candidate_id,family,kind,build_scope,restart_class,definition_json,definition_sha256,enabled) VALUES(?,?,?,?,?,?,?,1)", + (c["id"], c["family"], c["kind"], c["build_scope"], c["restart_class"], definition, base.sha256_bytes(definition.encode()))) + + def state_path(self, cid: str) -> Path: + return self.campaign_dir(cid) / STATE_NAME + + def read_state(self, cid: str) -> dict[str, Any]: + return base.read_json(self.state_path(cid)) + + def write_state(self, cid: str, state: dict[str, Any]) -> None: + state = dict(state) + state["updated_utc"] = utc_now() + base.atomic_json(self.state_path(cid), state) + + def create_incremental_campaign(self, deadline_seconds: int) -> str: + self.verify_static_authority() + self.ensure_incremental_candidates() + cid = "incremental-" + datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + "-" + uuid.uuid4().hex[:8] + cdir = self.campaign_dir(cid) + cdir.mkdir(parents=True, exist_ok=False) + working = cdir / "working-profile.ini" + shutil.copy2(self.installed_profile, working) + self.db.execute( + "INSERT INTO campaigns(campaign_id,started_utc,state,authority_sha256,candidate_manifest_sha256,lkg_integrity_passed,pbe_evidence_admitted) VALUES(?,?,?,?,?,1,0)", + (cid, utc_now(), "RUNNING", base.sha256_file(base.AUTHORITY_PATH), base.sha256_file(MANIFEST_PATH))) + base.ACTIVE_PATH.write_text(cid + "\n", encoding="utf-8") + self.write_checkpoint(cid, queue_index=0, state="RUNNING", phase="INCREMENTAL_CAMPAIGN_START", current_candidate=None, current_run=None, failure=None) + self.write_state(cid, { + "schema": 1, + "campaign": "TFTMAC_INCREMENTAL_SMALL_GAINS_V1", + "campaign_id": cid, + "started_utc": utc_now(), + "deadline_monotonic_ns": time.monotonic_ns() + deadline_seconds * 1_000_000_000, + "base_working_version": self.authority["working_version"], + "installed_profile_sha256": self.installed_profile_sha, + "working_profile_path": str(working), + "working_profile_sha256": base.sha256_file(working), + "accepted": [], + "results": [], + "queue_index": 0, + "rollback_verified": True, + }) + return cid + + def profile_occurrences(self, text: str, cvar: str) -> list[tuple[int, str, str]]: + section = "" + found: list[tuple[int, str, str]] = [] + for i, raw in enumerate(text.splitlines()): + line = raw.strip() + if line.startswith("[") and line.endswith("]"): + section = line + elif line.startswith("CVars="): + key, sep, value = line[6:].partition("=") + if sep and key == cvar: + found.append((i, section, value)) + return found + + def build_profile_candidate(self, base_profile: Path, spec: dict[str, Any], out: Path) -> dict[str, Any]: + text = base_profile.read_text(encoding="utf-8") + occurrences = self.profile_occurrences(text, spec["cvar"]) + if not occurrences: + raise base.LabError(f"candidate CVar is not present: {spec['cvar']}", error_class="CANDIDATE_CONFIG_MISSING", component="profile", phase="candidate_apply") + observed = sorted({value for _, _, value in occurrences}) + if observed != [str(spec["from"])]: + raise base.LabError( + f"candidate baseline mismatch for {spec['cvar']}: observed={observed} expected={spec['from']}", + error_class="CANDIDATE_CONFIG_CONTAMINATED", component="profile", phase="candidate_apply") + lines = text.splitlines() + changed = [] + for i, section, value in occurrences: + lines[i] = f"CVars={spec['cvar']}={spec['to']}" + changed.append({"line": i + 1, "section": section, "cvar": spec["cvar"], "from": value, "to": str(spec["to"])}) + out.write_text("\n".join(lines) + ("\n" if text.endswith("\n") else ""), encoding="utf-8") + # Semantic guard: every non-target line must remain byte-identical. + candidate_lines = out.read_text(encoding="utf-8").splitlines() + if len(candidate_lines) != len(text.splitlines()): + raise base.LabError("profile line count changed", error_class="CANDIDATE_CONFIG_CONTAMINATED", component="profile", phase="candidate_apply") + target_lines = {item["line"] - 1 for item in changed} + for i, (a, b) in enumerate(zip(text.splitlines(), candidate_lines)): + if i not in target_lines and a != b: + raise base.LabError(f"unapproved profile drift at line {i+1}", error_class="CANDIDATE_CONFIG_CONTAMINATED", component="profile", phase="candidate_apply") + return {"changes": changed, "base_sha256": base.sha256_file(base_profile), "candidate_sha256": base.sha256_file(out)} + + def apply_profile_overlay(self, ctx: base.RunContext, profile: Path) -> None: + target = self.authority["device_profile_target"] + original_stage = self.authority["highperf_stage_profile"] + expected = self.installed_profile_sha + self.adb("shell", "am", "force-stop", self.package, timeout=15) + self.adb_root() + try: + current_sha = self.adb("shell", "sha256sum", target).stdout.split()[0] + original_sha = self.adb("shell", "sha256sum", original_stage).stdout.split()[0] + if current_sha != expected or original_sha != expected: + raise base.LabError( + f"current native profile mount is not the accepted installed baseline: target={current_sha} stage={original_sha}", + error_class="PROFILE_BASELINE_MISMATCH", component="profile", phase="candidate_apply") + stage = f"/data/local/tmp/tftmac-incremental-{ctx.run_id[-12:]}" + self.adb("shell", "mkdir", "-p", stage) + self.adb("push", str(profile), stage + "/DeviceProfiles.ini", timeout=20) + self.adb("shell", "chmod", "444", stage + "/DeviceProfiles.ini") + context = self.adb("shell", "ls", "-Zd", target).stdout.split()[0] + self.adb("shell", "chcon", context, stage + "/DeviceProfiles.ini") + self.adb("shell", "umount", target) + self.adb("shell", "mount", "-o", "bind", stage + "/DeviceProfiles.ini", target) + observed = self.adb("shell", "sha256sum", target).stdout.split()[0] + wanted = base.sha256_file(profile) + if observed != wanted: + raise base.LabError("incremental profile overlay hash mismatch", error_class="PROFILE_OVERLAY_FAILED", component="profile", phase="candidate_apply") + ctx.profile_overlay_applied = True + finally: + self.adb_unroot() + self.adb("shell", "am", "start", "-n", f"{self.package}/{self.game_activity}", timeout=20) + + def restore_profile_overlay(self, ctx: base.RunContext) -> bool: + if not ctx.profile_overlay_applied: + return True + target = self.authority["device_profile_target"] + original_stage = self.authority["highperf_stage_profile"] + try: + self.adb("shell", "am", "force-stop", self.package, timeout=10, check=False) + self.adb_root() + try: + self.adb("shell", "umount", target, timeout=10) + self.adb("shell", "mount", "-o", "bind", original_stage, target, timeout=10) + ok = self.adb("shell", "sha256sum", target).stdout.split()[0] == self.installed_profile_sha + finally: + self.adb_unroot() + ctx.profile_overlay_applied = False + return ok + except Exception: + try: + self.adb_unroot() + except Exception: + pass + return False + + def wait_profile_value(self, ctx: base.RunContext, cvar: str, expected: str, timeout: int = 45) -> bool: + rx = re.compile(rf"Pushing Device Profile CVar: \[\[{re.escape(cvar)}:.*? -> {re.escape(expected)}\]\]") + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + if ctx.capture: + for path in list(ctx.capture.glob("*engine*.log")) + list(ctx.capture.glob("*logcat*.txt")) + list(ctx.capture.glob("*.log")): + if rx.search(base.safe_text(path)): + return True + if not self.dev_core_running() or not self.owned_emulator_pids(): + break + time.sleep(1) + return False + + def register_run_candidate(self, spec: dict[str, Any], role: str, base_profile: Path, profile: Path) -> dict[str, Any]: + c = self.candidate_definition(spec) + c = dict(c) + c["id"] = f"{spec['id']}--{role}" + c["family"] = spec["family"] + c["profile_role"] = role + c["profile_sha256"] = base.sha256_file(profile) + definition = canonical_json(c) + self.db.execute( + "INSERT OR REPLACE INTO candidates(candidate_id,family,kind,build_scope,restart_class,definition_json,definition_sha256,enabled) VALUES(?,?,?,?,?,?,?,1)", + (c["id"], c["family"], c["kind"], c["build_scope"], c["restart_class"], definition, base.sha256_bytes(definition.encode()))) + return c + + def run_profile(self, cid: str, spec: dict[str, Any], role: str, profile: Path, expected_cvar_value: Optional[str]) -> tuple[str, str]: + c = self.register_run_candidate(spec, role, profile, profile) + ctx = self.new_run(cid, c) + navigation = None + error: Optional[BaseException] = None + restore_profile = True + rollback: dict[str, Any] = {} + try: + self.verify_static_authority() + self.launch_dev(ctx) + self.wait_for_device(ctx) + self.apply_session_properties_before_tft(ctx) + self.wait_for_shell_and_package(ctx) + if base.sha256_file(profile) != self.installed_profile_sha: + self.apply_profile_overlay(ctx, profile) + deadline = time.monotonic() + 120 + while time.monotonic() < deadline: + pid = self.adb("shell", "pidof", self.package, check=False).stdout.strip() + if pid: + break + time.sleep(1) + self.verify_runtime(ctx) + if expected_cvar_value is not None: + if not self.wait_profile_value(ctx, spec["cvar"], expected_cvar_value): + raise base.LabError( + f"engine log did not prove effective CVar {spec['cvar']}={expected_cvar_value}", + error_class="CVAR_NOT_EFFECTIVE", component="profile", phase="identity") + self.upsert_variable(ctx, "device_profile_candidate", spec["cvar"], spec["from"], expected_cvar_value, expected_cvar_value, "engine log Pushing Device Profile CVar", "VERIFIED") + navigation = self.navigate_tocker(ctx) + except BaseException as exc: + error = exc + self.failure(ctx, exc, getattr(exc, "phase", "run")) + finally: + if ctx.profile_overlay_applied: + restore_profile = self.restore_profile_overlay(ctx) + self.request_dev_quit() + try: + self.import_native_capture(ctx) + except Exception: + pass + rollback = self.wait_cleanup(ctx) + rollback["profile_restored"] = restore_profile + rollback["cache_properties_restored"] = True + rollback["angle_restored"] = True + rollback["verified"] = bool(rollback.get("verified") and restore_profile) + self.db.execute( + "INSERT INTO rollbacks(run_id,observed_utc,dev_core_stopped,emulator_stopped,angle_restored,profile_restored,cache_properties_restored,installed_app_integrity,lkg_integrity,verified,detail_json) VALUES(?,?,?,?,?,?,?,?,?,?,?)", + (ctx.run_id, utc_now(), int(rollback.get("dev_core_stopped", False)), int(rollback.get("emulator_stopped", False)), 1, + int(restore_profile), 1, int(rollback.get("installed_app_integrity", False)), int(rollback.get("lkg_integrity", False)), + int(rollback["verified"]), canonical_json(rollback))) + perf = self.db.rows("SELECT weighted_fps FROM performance_windows WHERE run_id=? AND semantic_gate_passed=1", (ctx.run_id,)) + classification = "CONTROL_VALID" if role.startswith("control") and perf and error is None and rollback.get("verified") else ( + "MECHANISM_WORKING" if perf and error is None and rollback.get("verified") else "INCONCLUSIVE") + if not ctx.decision_admissible: + classification = "DATA_ONLY_NONCOMPARABLE" + if not rollback.get("verified"): + classification = "INCONCLUSIVE" + if error is None: + error = base.LabError("rollback could not be proven", error_class="ROLLBACK_FAILURE", phase="rollback") + self.failure(ctx, error, "rollback") + result = { + "candidate": spec["id"], "role": role, "classification": classification, "navigation": navigation, + "error": base.normalize_error(str(error)) if error else None, "rollback": rollback, + "profile_sha256": base.sha256_file(profile), "working_profile_sha256": base.sha256_file(profile), + "selected_game_rhi": ctx.selected_rhi, "decision_admissible": ctx.decision_admissible, + } + base.atomic_json(ctx.run_dir / "result.json", result) + self.db.execute( + "UPDATE runs SET ended_utc=?,state='COMPLETE',classification=?,rollback_verified=?,selected_game_rhi=?,raw_native_classifier_value=?,result_json=? WHERE run_id=?", + (utc_now(), classification, int(rollback.get("verified", False)), ctx.selected_rhi, ctx.raw_native_rhi, canonical_json(result), ctx.run_id)) + self.db.execute( + "INSERT INTO decisions(decision_id,campaign_id,candidate_id,run_id,created_utc,classification,reason,evidence_json) VALUES(?,?,?,?,?,?,?,?)", + (str(uuid.uuid4()), cid, c["id"], ctx.run_id, utc_now(), classification, base.normalize_error(str(error)) if error else role, canonical_json(result))) + return ctx.run_id, classification + + def metrics(self, run_id: str) -> dict[str, float]: + rows = self.db.rows( + "SELECT frame_count,weighted_fps,one_percent_low_fps,p95_ms,p99_ms,over_25ms,over_50ms FROM performance_windows WHERE run_id=? AND semantic_gate_passed=1", + (run_id,)) + if not rows: + return {} + total_frames = sum(int(r["frame_count"] or 0) for r in rows) + return { + "fps": statistics.fmean(float(r["weighted_fps"]) for r in rows), + "low1": statistics.fmean(float(r["one_percent_low_fps"] or 0) for r in rows), + "p95": statistics.fmean(float(r["p95_ms"] or 0) for r in rows), + "p99": statistics.fmean(float(r["p99_ms"] or 0) for r in rows), + "jank_rate": sum(int(r["over_25ms"] or 0) for r in rows) / max(1, total_frames), + "severe_rate": sum(int(r["over_50ms"] or 0) for r in rows) / max(1, total_frames), + "windows": float(len(rows)), + } + + @staticmethod + def pct(candidate: float, control: float) -> float: + if control == 0: + return 0.0 if candidate == 0 else math.inf + return (candidate - control) / control * 100.0 + + def compare(self, control_id: str, candidate_id: str) -> dict[str, Any]: + control = self.metrics(control_id) + candidate = self.metrics(candidate_id) + if not control or not candidate: + return {"decision": "INCONCLUSIVE", "reason": "missing matched performance windows", "control": control, "candidate": candidate} + delta = { + "weighted_fps_percent": self.pct(candidate["fps"], control["fps"]), + "one_percent_low_fps_percent": self.pct(candidate["low1"], control["low1"]), + "p95_interval_percent": self.pct(candidate["p95"], control["p95"]), + "p99_interval_percent": self.pct(candidate["p99"], control["p99"]), + "jank_rate_delta": candidate["jank_rate"] - control["jank_rate"], + "severe_rate_delta": candidate["severe_rate"] - control["severe_rate"], + } + veto = ( + delta["weighted_fps_percent"] <= -5.0 or + delta["one_percent_low_fps_percent"] <= -10.0 or + delta["p95_interval_percent"] >= 10.0 or + delta["p99_interval_percent"] >= 10.0 + ) + directional = ( + delta["weighted_fps_percent"] > 0 or + delta["one_percent_low_fps_percent"] > 0 or + (delta["p95_interval_percent"] < 0 and delta["p99_interval_percent"] < 0) or + delta["jank_rate_delta"] < 0 or delta["severe_rate_delta"] < 0 + ) + home = ( + delta["one_percent_low_fps_percent"] >= 20 and + control["jank_rate"] > 0 and candidate["jank_rate"] <= control["jank_rate"] * .7 and + control["severe_rate"] > 0 and candidate["severe_rate"] <= control["severe_rate"] * .7 and + (delta["weighted_fps_percent"] >= 10 or delta["p95_interval_percent"] <= -15) + ) + decision = "REJECT" if veto else ("HOME_RUN" if home else ("PROMISING" if directional else "INCONCLUSIVE")) + return {"decision": decision, "control": control, "candidate": candidate, "delta": delta} + + def incremental_report(self, cid: str) -> Path: + state = self.read_state(cid) + out = self.campaign_dir(cid) / "incremental-report.md" + lines = [ + f"# TFTMAC Incremental Sweep — {cid}", "", + f"- Base winner: `{state['base_working_version']}`", + f"- Working profile SHA-256: `{state['working_profile_sha256']}`", + f"- Accepted cumulative candidates: {', '.join(x['id'] for x in state['accepted']) or 'none'}", + f"- Rollback verified: `{state['rollback_verified']}`", "", "## Results", "" + ] + for item in state["results"]: + lines.append(f"- `{item['candidate']}`: initial={item.get('initial_decision')}, confirmation={item.get('confirmation_decision')}, kept={item.get('kept')}, control={item.get('control_run')}, candidateRun={item.get('candidate_run')}") + out.write_text("\n".join(lines) + "\n", encoding="utf-8") + return out + + def run_incremental_campaign(self, duration_seconds: int, resume: bool) -> str: + if base.LOCK_PATH.exists(): + try: + owner = int(base.LOCK_PATH.read_text().strip()); os.kill(owner, 0) + raise base.LabError(f"campaign already running under pid {owner}", error_class="CAMPAIGN_LOCKED", phase="campaign") + except (ValueError, ProcessLookupError): + base.LOCK_PATH.unlink(missing_ok=True) + base.LOCK_PATH.write_text(str(os.getpid()) + "\n", encoding="utf-8") + try: + cid = self.current_campaign() if resume else None + if cid and self.state_path(cid).exists(): + self.reconcile_resume(cid) + state = self.read_state(cid) + else: + cid = self.create_incremental_campaign(duration_seconds) + state = self.read_state(cid) + specs = list(self.incremental_manifest["candidates"]) + while state["queue_index"] < len(specs): + if time.monotonic_ns() >= int(state["deadline_monotonic_ns"]): + break + spec = specs[state["queue_index"]] + parent = spec.get("requires_parent") + accepted_ids = {x["id"] for x in state["accepted"]} + if parent and parent not in accepted_ids: + state["results"].append({"candidate": spec["id"], "initial_decision": "SKIPPED_PARENT_NOT_ACCEPTED", "kept": False}) + state["queue_index"] += 1; self.write_state(cid, state); continue + working = Path(state["working_profile_path"]) + candidate_profile = self.campaign_dir(cid) / f"candidate-{spec['id']}.ini" + delta_receipt = self.build_profile_candidate(working, spec, candidate_profile) + base.atomic_json(self.campaign_dir(cid) / f"candidate-{spec['id']}.delta.json", delta_receipt) + # Fresh matched control then candidate. + control_id, control_class = self.run_profile(cid, spec, "control-initial", working, str(spec["from"]) if base.sha256_file(working) != self.installed_profile_sha else None) + if control_class != "CONTROL_VALID": + state["rollback_verified"] = False if control_class == "INCONCLUSIVE" else state["rollback_verified"] + state["results"].append({"candidate": spec["id"], "initial_decision": "CONTROL_NOT_GREEN", "control_run": control_id, "kept": False}) + self.write_state(cid, state); break + candidate_id, candidate_class = self.run_profile(cid, spec, "candidate-initial", candidate_profile, str(spec["to"])) + initial = self.compare(control_id, candidate_id) if candidate_class == "MECHANISM_WORKING" else {"decision": "INCONCLUSIVE", "reason": candidate_class} + item: dict[str, Any] = {"candidate": spec["id"], "control_run": control_id, "candidate_run": candidate_id, "initial_decision": initial["decision"], "initial_comparison": initial, "kept": False} + if initial["decision"] in {"PROMISING", "HOME_RUN"} and time.monotonic_ns() < int(state["deadline_monotonic_ns"]): + confirm_control, cc = self.run_profile(cid, spec, "control-confirm", working, str(spec["from"]) if base.sha256_file(working) != self.installed_profile_sha else None) + if cc != "CONTROL_VALID": + confirmation = {"decision": "INCONCLUSIVE", "reason": f"confirmation control={cc}"} + confirm_candidate, kc = None, "SKIPPED_CONTROL_NOT_GREEN" + else: + confirm_candidate, kc = self.run_profile(cid, spec, "candidate-confirm", candidate_profile, str(spec["to"])) + confirmation = self.compare(confirm_control, confirm_candidate) if kc == "MECHANISM_WORKING" else {"decision": "INCONCLUSIVE", "reason": f"candidate={kc}"} + item.update({"confirmation_control_run": confirm_control, "confirmation_candidate_run": confirm_candidate, "confirmation_decision": confirmation["decision"], "confirmation_comparison": confirmation}) + if confirmation["decision"] in {"PROMISING", "HOME_RUN"}: + shutil.copy2(candidate_profile, working) + state["working_profile_sha256"] = base.sha256_file(working) + accepted = {"id": spec["id"], "cvar": spec["cvar"], "from": str(spec["from"]), "to": str(spec["to"]), "profile_sha256": state["working_profile_sha256"], "initial": initial, "confirmation": confirmation} + state["accepted"].append(accepted) + item["kept"] = True + state["results"].append(item) + state["queue_index"] += 1 + self.write_state(cid, state) + self.incremental_report(cid) + # Hard rollback guard: latest two runs must prove rollback before queue advance. + latest = self.db.rows("SELECT rollback_verified FROM runs WHERE campaign_id=? ORDER BY started_utc DESC LIMIT 2", (cid,)) + if latest and any(not bool(r["rollback_verified"]) for r in latest): + state["rollback_verified"] = False; self.write_state(cid, state) + raise base.LabError("incremental campaign stopped because rollback is unproven", error_class="ROLLBACK_FAILURE", phase="campaign") + end_state = "COMPLETE" if state["queue_index"] >= len(specs) else "DEADLINE_COMPLETE" + state["campaign_state"] = end_state + self.write_state(cid, state) + self.db.execute("UPDATE campaigns SET state=?,ended_utc=? WHERE campaign_id=?", (end_state, utc_now(), cid)) + self.write_checkpoint(cid, queue_index=int(state["queue_index"]), state=end_state, phase="COMPLETE", current_candidate=None, current_run=None, failure=None) + self.incremental_report(cid) + self.generate_report(cid) + return cid + finally: + base.LOCK_PATH.unlink(missing_ok=True) + + +def parse_duration(value: str) -> int: + return base.parse_duration(value) + + +def main() -> int: + p = argparse.ArgumentParser(description="TFTMAC evidence-grounded incremental small-gains sweep") + sub = p.add_subparsers(dest="command", required=True) + sub.add_parser("self-test") + c = sub.add_parser("campaign"); c.add_argument("--duration", type=parse_duration, default=parse_duration("4h")); c.add_argument("--resume", action="store_true") + sub.add_parser("status") + args = p.parse_args() + lab = IncrementalLab() + try: + if args.command == "self-test": + static = lab.verify_static_authority() + manifest = lab.incremental_manifest + assert manifest["baseline"] == lab.authority["working_version"] + assert [x["id"] for x in manifest["candidates"]] == ["pso-precompile-threads-2","shader-background-batch-4","animation-budget-5ms","animation-budget-4ms"] + # prove exact candidate generation against the current installed profile + import tempfile + receipts=[] + current=lab.installed_profile + with tempfile.TemporaryDirectory() as td: + for spec in manifest["candidates"][:3]: + out=Path(td)/f"{spec['id']}.ini"; receipts.append(lab.build_profile_candidate(current,spec,out)); current=lab.installed_profile + print(json.dumps({"static": static, "incremental_manifest": "PASS", "exact_one_cvar_generation": "PASS", "candidate_receipts": receipts}, indent=2)); return 0 + if args.command == "campaign": + print(lab.run_incremental_campaign(args.duration, args.resume)); return 0 + if args.command == "status": + cid=lab.current_campaign(); print(json.dumps(lab.read_state(cid) if cid and lab.state_path(cid).exists() else {"campaign":None}, indent=2)); return 0 + return 2 + except base.LabError as exc: + print(f"TFTMAC IncrementalLab: {exc.error_class}: {exc}", file=__import__('sys').stderr); return 3 + finally: + lab.close() + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/OvernightLab/install.command b/OvernightLab/install.command index 6e528e7..574464f 100755 --- a/OvernightLab/install.command +++ b/OvernightLab/install.command @@ -26,6 +26,7 @@ copy_file() { # Source/config only. Generated campaigns/database/reports/bin remain in place. copy_file 755 "$SOURCE_LAB/overnight_lab.py" "$LIVE_LAB/overnight_lab.py" +copy_file 755 "$SOURCE_LAB/incremental_lab.py" "$LIVE_LAB/incremental_lab.py" copy_file 755 "$SOURCE_LAB/install.command" "$LIVE_LAB/install.command" copy_file 755 "$SOURCE_LAB/run-overnight-campaign.command" "$LIVE_LAB/run-overnight-campaign.command" copy_file 644 "$SOURCE_LAB/README.md" "$LIVE_LAB/README.md" @@ -33,6 +34,7 @@ copy_file 644 "$SOURCE_LAB/schema.sql" "$LIVE_LAB/schema.sql" copy_file 644 "$SOURCE_LAB/authority/official-client-runtime.json" "$LIVE_LAB/authority/official-client-runtime.json" copy_file 644 "$SOURCE_LAB/authority/native-capture-recovery-2026-09-11.json" "$LIVE_LAB/authority/native-capture-recovery-2026-09-11.json" copy_file 644 "$SOURCE_LAB/manifests/official-candidates.json" "$LIVE_LAB/manifests/official-candidates.json" +copy_file 644 "$SOURCE_LAB/manifests/incremental-candidates.json" "$LIVE_LAB/manifests/incremental-candidates.json" # Existing controller code resolves these two classifier inputs from ROOT.parent. copy_file 644 "$PROJECT_ROOT/tools/tft-screen-classifier.swift" "$LIVE_ROOT/tools/tft-screen-classifier.swift" diff --git a/OvernightLab/manifests/incremental-candidates.json b/OvernightLab/manifests/incremental-candidates.json new file mode 100644 index 0000000..179fd2b --- /dev/null +++ b/OvernightLab/manifests/incremental-candidates.json @@ -0,0 +1,42 @@ +{ + "schema": 1, + "campaign": "TFTMAC_INCREMENTAL_SMALL_GAINS_V1", + "baseline": "DEV-B8-WIN-01", + "target_stages": ["1-5"], + "maximum_seconds_per_run": 720, + "candidates": [ + { + "id": "pso-precompile-threads-2", + "family": "unreal_cpu_contention", + "cvar": "r.pso.PrecompileThreadPoolSize", + "from": "4", + "to": "2", + "reason": "Reduce concurrent PSO precompile CPU contention on the 8-vCPU guest during interactive/heavy gameplay." + }, + { + "id": "shader-background-batch-4", + "family": "unreal_shader_cache_contention", + "cvar": "r.ShaderPipelineCache.BackgroundBatchSize", + "from": "20", + "to": "4", + "reason": "Reduce burst background PSO cache compile work during interactive gameplay." + }, + { + "id": "animation-budget-5ms", + "family": "unreal_animation_budget", + "cvar": "a.Budget.BudgetMs", + "from": "6.0", + "to": "5.0", + "reason": "Apply earlier skeletal-animation budgeting during unit-dense combat to protect game-thread frame time." + }, + { + "id": "animation-budget-4ms", + "family": "unreal_animation_budget", + "cvar": "a.Budget.BudgetMs", + "from": "5.0", + "to": "4.0", + "requires_parent": "animation-budget-5ms", + "reason": "Conditional refinement only after a confirmed 5ms animation-budget win." + } + ] +} diff --git a/tftmac/Assets/DEVHighPerf/DeviceProfiles.ini b/tftmac/Assets/DEVHighPerf/DeviceProfiles.ini index 0e24d54..7c00ef2 100644 --- a/tftmac/Assets/DEVHighPerf/DeviceProfiles.ini +++ b/tftmac/Assets/DEVHighPerf/DeviceProfiles.ini @@ -24,7 +24,7 @@ CVars=r.MSAACount=4 CVars=r.MaterialQualityLevel=0 CVars=r.MeshDrawCommands.AllowOnDemandShaderCreation=1 CVars=r.Mobile.CustomDepthForTranslucency=1 -CVars=r.MobileContentScaleFactor=1.0 +CVars=r.MobileContentScaleFactor=0.0 CVars=r.OpenGL.DeferTextureCreation=0 CVars=r.OpenGL.DeferTextureCreationKeepLowerMipCount=16 CVars=r.OpenGL.EnableProgramLRUCache=1 @@ -106,7 +106,7 @@ CVars=r.MSAACount=4 CVars=r.MaterialQualityLevel=0 CVars=r.MeshDrawCommands.AllowOnDemandShaderCreation=1 CVars=r.Mobile.CustomDepthForTranslucency=1 -CVars=r.MobileContentScaleFactor=1.0 +CVars=r.MobileContentScaleFactor=0.0 CVars=r.OpenGL.DeferTextureCreation=0 CVars=r.OpenGL.DeferTextureCreationKeepLowerMipCount=16 CVars=r.OpenGL.EnableProgramLRUCache=1 diff --git a/tftmac/Assets/DEVHighPerf/README.md b/tftmac/Assets/DEVHighPerf/README.md index 7b20d2b..48e9d52 100644 --- a/tftmac/Assets/DEVHighPerf/README.md +++ b/tftmac/Assets/DEVHighPerf/README.md @@ -18,7 +18,7 @@ Source mechanism: AOSP Android16 `system/core/init/{first_stage_init.cpp,selinux (allow domain su (process (sigchld))) ``` -`DeviceProfiles.ini` contains17 target CVars plus61 preserved non-target values. `baseline-device-cvars.json` records all76 explicit baseline assignments from the same official APK and6GB guest before the override. Generated arrays replace inherited arrays, so preserving the baseline is mandatory. In particular, `r.Android.DisableVulkanSupport=1` retains TFT's validated OpenGL-through-ANGLE route. The first override omitted that flag, selected direct Vulkan and caused Metal vertex-descriptor failures during Trials loading; it was rejected. Generated Saved config uses repeated **CVars=** assignments and **DeviceProfileFragment** sections. The target is the game's private `files/UnrealGame/TFT/TFT/Saved/Config/Android/DeviceProfiles.ini`. The native transaction verifies the file hash, root ownership,0444 mode, app SELinux context and zygote mount namespace before unrooting. A fresh engine log must confirm every target before readiness is claimed. +`DeviceProfiles.ini` contains18 target CVars plus60 preserved non-target values. `baseline-device-cvars.json` records all76 explicit baseline assignments from the same official APK and6GB guest before the override. Generated arrays replace inherited arrays, so preserving the baseline is mandatory. In particular, `r.Android.DisableVulkanSupport=1` retains TFT's validated OpenGL-through-ANGLE route. The first override omitted that flag, selected direct Vulkan and caused Metal vertex-descriptor failures during Trials loading; it was rejected. Generated Saved config uses repeated **CVars=** assignments and **DeviceProfileFragment** sections. The native-resolution experiment changes `r.MobileContentScaleFactor` from `1.0` to `0.0` in both base sections; all other values remain unchanged. Actual 1920x1080 game buffers must be verified after launch; profile acceptance alone is insufficient. The target is the game's private `files/UnrealGame/TFT/TFT/Saved/Config/Android/DeviceProfiles.ini`. The native transaction verifies the file hash, root ownership,0444 mode, app SELinux context and zygote mount namespace before unrooting. A fresh engine log must confirm every target before readiness is claimed. The Android helper journals original presence/hash/metadata/context before touching the target. Shutdown unmounts the known profile and verifies the original content or absence. A cold boot recovers a previous prepared transaction using the same checks. Unknown changes retain the journal and fail; they are never overwritten. Archives remain in DEV `/data/local/tmp/tftmac-native-highperf.rolled-back.*` for diagnosis. diff --git a/tftmac/Assets/DEVHighPerf/profile-transaction.sh b/tftmac/Assets/DEVHighPerf/profile-transaction.sh old mode 100644 new mode 100755 index a5e80dc..c83b229 --- a/tftmac/Assets/DEVHighPerf/profile-transaction.sh +++ b/tftmac/Assets/DEVHighPerf/profile-transaction.sh @@ -3,7 +3,7 @@ set -eu mode="$1" session="$2" -expected=aa9672cd730e5e3c32e6c9a793a4ef98bef90d80124c7e22b196d2e174af07e1 +expected=45d6465040ef8b7298cd8da000208e4ee0be924c65a20326900267f25adf5c9c package=com.riotgames.league.teamfighttactics target=/data/user/0/$package/files/UnrealGame/TFT/TFT/Saved/Config/Android/DeviceProfiles.ini parent=${target%/*} diff --git a/tftmac/Runtime/TFTMACRuntime.swift b/tftmac/Runtime/TFTMACRuntime.swift index c6e04bf..5f850f8 100644 --- a/tftmac/Runtime/TFTMACRuntime.swift +++ b/tftmac/Runtime/TFTMACRuntime.swift @@ -2483,8 +2483,8 @@ actor TFTMACRuntimeService { private static let highPerfHashes = [ "debug-ramdisk.img": "35e03aec0f5faea16db88d852ac80ad9691c44e7b4f1d1305a6b7ad6984de956", - "DeviceProfiles.ini": "aa9672cd730e5e3c32e6c9a793a4ef98bef90d80124c7e22b196d2e174af07e1", - "profile-transaction.sh": "5718319c56995d00613b930acb5bc0a8e7b107d165fb819e748b0fcb050bff7f" + "DeviceProfiles.ini": "45d6465040ef8b7298cd8da000208e4ee0be924c65a20326900267f25adf5c9c", + "profile-transaction.sh": "7d7d89df8db6927b09d4e2866aac932f9924b729584797e8364077102eeb5835" ] private func highPerfReceipt(_ base: RuntimeExperimentConfigurationReceipt) -> RuntimeExperimentConfigurationReceipt { From f7da8dfc071a2fde6952a8619ec054bde0bce69c Mon Sep 17 00:00:00 2001 From: flashls1 Date: Fri, 11 Sep 2026 03:19:14 -0500 Subject: [PATCH 03/12] Approve continuous four-hour stability tail --- .../AMENDMENT_001_CONTINUOUS_4H.md | 40 +++++++++++++++++++ .../ZENMC_V2_RECEIPT.json | 31 ++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 .clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_001_CONTINUOUS_4H.md create mode 100644 .clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V2_RECEIPT.json diff --git a/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_001_CONTINUOUS_4H.md b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_001_CONTINUOUS_4H.md new file mode 100644 index 0000000..e0bfc70 --- /dev/null +++ b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_001_CONTINUOUS_4H.md @@ -0,0 +1,40 @@ +# Amendment 001 — Continuous Four-Hour Tail + +## Why this amendment exists +The approved candidate queue can finish before the exact four-hour campaign deadline. The current runner exits immediately when the queue is exhausted. That does not satisfy the requested continuous four-hour evidence window. + +## Scope change +After the candidate queue is exhausted, do **not** invent or admit another tuning candidate. Repeatedly execute a `stability-control` run using the exact current campaign working profile and the existing matched native workload until the deadline prevents admission of the next run. + +A stability-control run: +- changes no CVar and cannot promote a candidate; +- uses the current working-profile bytes unchanged; +- uses the same DEV app, StockShadow runtime, Tocker navigation, native SurfaceFlinger/SQLite evidence, and rollback path already approved; +- records run/capture/profile identity, metrics, result classification, and rollback evidence in campaign state/report; +- stops the campaign immediately if runtime identity, telemetry, correctness, or rollback is not green. + +Deadline rule: check the monotonic deadline before each new stability-control admission. A run already admitted before the deadline may finish and must complete rollback/evidence sealing; no subsequent run may start after the deadline. + +## Complexity Economics Gate +PASS. The smallest correct mechanism is one bounded tail loop in `OvernightLab/incremental_lab.py` reusing `run_profile()` with a synthetic no-mutation stability specification. No new runtime, scheduler, service, VM, AVD, profiler, store, or mutation state machine is introduced. + +## ZenGate +PASS. ZenMC V2 completed with 100,000 trajectories, zero invariant violations, result `PASS`, receipt `857b5abdff538ca5903473a9ca7c7e58fdccd0dab39eacae96ba24de93b7300e`. This amendment changes only queue-exhaustion behavior. All original protected boundaries, one-factor candidate rules, rollback gates, evidence rules, and final delivery requirements remain unchanged. + +## ZenMC V2 qualification +ZENMC_REQUIRED because the amendment changes deadline, recovery, rollback, and repeated asynchronous run transitions. Model identity: `TFTMAC_INCREMENTAL_4H_LIFECYCLE_V2`. + +Required invariants: +1. Frozen LKG is never mutated. +2. No tuning candidate is admitted after candidate-queue exhaustion. +3. Every post-queue run is `stability-control` on the current working-profile identity. +4. Any rollback failure terminates future run admission. +5. Deadline prevents admission of the next candidate or stability run. +6. Candidate promotion still requires positive confirmation. +7. A run admitted before deadline may finish, but must seal evidence and rollback before terminal campaign state. + +## Source delta authorized after V2 PASS + checkpoint +- Add the stability-control tail loop and state/report ledger fields only. +- Extend offline self-test to prove the tail-loop policy is enabled without executing the emulator. +- Re-run governed validation and live-install hash-parity/self-test. +- Then start the durable four-hour campaign exactly as the approved plan requires. diff --git a/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V2_RECEIPT.json b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V2_RECEIPT.json new file mode 100644 index 0000000..ce396cb --- /dev/null +++ b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V2_RECEIPT.json @@ -0,0 +1,31 @@ +{ + "schema": 1, + "model": "TFTMAC_INCREMENTAL_4H_LIFECYCLE_V2", + "seed": 20260911, + "trajectories": 100000, + "invariants": { + "admitted_run_finishes_and_rolls_back_before_terminal": true, + "deadline_prevents_next_run_admission": true, + "frozen_lkg_never_mutated": true, + "no_candidate_after_queue_exhaustion": true, + "promotion_requires_positive_confirmation": true, + "rollback_failure_stops_future_admission": true, + "stability_uses_current_working_profile_only": true + }, + "stats": { + "complete_queue": 84721, + "deadline_during_queue": 0, + "inconclusive": 66501, + "parent_skipped": 71492, + "promoted": 46363, + "queue_exhausted_before_deadline": 84721, + "rejected": 170858, + "rollback_fail": 62015, + "stability_runs": 1564611 + }, + "violation_count": 0, + "violations": [], + "result": "PASS", + "receipt_sha256": "857b5abdff538ca5903473a9ca7c7e58fdccd0dab39eacae96ba24de93b7300e", + "source_operation_id": "god-dc1f03dd-5205-4f56-bf95-830d66f53be0" +} From c24567ee67db367ed4988dcda282426bfbec0b41 Mon Sep 17 00:00:00 2001 From: flashls1 Date: Fri, 11 Sep 2026 03:21:25 -0500 Subject: [PATCH 04/12] Run stability controls through four-hour deadline --- OvernightLab/incremental_lab.py | 50 +++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/OvernightLab/incremental_lab.py b/OvernightLab/incremental_lab.py index 30e638f..91bb53b 100755 --- a/OvernightLab/incremental_lab.py +++ b/OvernightLab/incremental_lab.py @@ -105,6 +105,7 @@ def create_incremental_campaign(self, deadline_seconds: int) -> str: "working_profile_sha256": base.sha256_file(working), "accepted": [], "results": [], + "stability_runs": [], "queue_index": 0, "rollback_verified": True, }) @@ -368,13 +369,30 @@ def incremental_report(self, cid: str) -> Path: f"- Base winner: `{state['base_working_version']}`", f"- Working profile SHA-256: `{state['working_profile_sha256']}`", f"- Accepted cumulative candidates: {', '.join(x['id'] for x in state['accepted']) or 'none'}", + f"- Stability-control runs: {len(state.get('stability_runs', []))}", f"- Rollback verified: `{state['rollback_verified']}`", "", "## Results", "" ] for item in state["results"]: lines.append(f"- `{item['candidate']}`: initial={item.get('initial_decision')}, confirmation={item.get('confirmation_decision')}, kept={item.get('kept')}, control={item.get('control_run')}, candidateRun={item.get('candidate_run')}") + if state.get("stability_runs"): + lines.extend(["", "## Stability controls", ""]) + for item in state["stability_runs"]: + m = item.get("metrics", {}) + lines.append(f"- `{item['run_id']}`: classification={item['classification']}, profile={item['profile_sha256']}, fps={m.get('fps')}, low1={m.get('low1')}, p95={m.get('p95')}, p99={m.get('p99')}, rollback={item.get('rollback_verified')}") out.write_text("\n".join(lines) + "\n", encoding="utf-8") return out + @staticmethod + def stability_spec() -> dict[str, Any]: + return { + "id": "stability-control", + "family": "campaign_stability", + "cvar": "", + "from": "", + "to": "", + "reason": "Queue-exhaustion stability control using the current working profile without changing any CVar.", + } + def run_incremental_campaign(self, duration_seconds: int, resume: bool) -> str: if base.LOCK_PATH.exists(): try: @@ -438,7 +456,33 @@ def run_incremental_campaign(self, duration_seconds: int, resume: bool) -> str: if latest and any(not bool(r["rollback_verified"]) for r in latest): state["rollback_verified"] = False; self.write_state(cid, state) raise base.LabError("incremental campaign stopped because rollback is unproven", error_class="ROLLBACK_FAILURE", phase="campaign") - end_state = "COMPLETE" if state["queue_index"] >= len(specs) else "DEADLINE_COMPLETE" + + # Queue exhausted before the four-hour deadline: accumulate stability evidence only. + # No new tuning candidate is invented or admitted in this tail loop. + stability = self.stability_spec() + while state["queue_index"] >= len(specs) and time.monotonic_ns() < int(state["deadline_monotonic_ns"]): + working = Path(state["working_profile_path"]) + run_id, classification = self.run_profile(cid, stability, "stability-control", working, None) + rollback_row = self.db.rows("SELECT rollback_verified FROM runs WHERE run_id=?", (run_id,)) + rollback_verified = bool(rollback_row and rollback_row[0]["rollback_verified"]) + state.setdefault("stability_runs", []).append({ + "run_id": run_id, + "classification": classification, + "profile_sha256": base.sha256_file(working), + "metrics": self.metrics(run_id), + "rollback_verified": rollback_verified, + }) + if not rollback_verified: + state["rollback_verified"] = False + self.write_state(cid, state) + self.incremental_report(cid) + if classification != "MECHANISM_WORKING" or not rollback_verified: + raise base.LabError( + f"stability-control not green: classification={classification} rollback={rollback_verified}", + error_class="STABILITY_CONTROL_FAILED" if rollback_verified else "ROLLBACK_FAILURE", + phase="campaign") + + end_state = "DEADLINE_COMPLETE" if time.monotonic_ns() >= int(state["deadline_monotonic_ns"]) else ("COMPLETE" if state["queue_index"] >= len(specs) else "DEADLINE_COMPLETE") state["campaign_state"] = end_state self.write_state(cid, state) self.db.execute("UPDATE campaigns SET state=?,ended_utc=? WHERE campaign_id=?", (end_state, utc_now(), cid)) @@ -475,7 +519,9 @@ def main() -> int: with tempfile.TemporaryDirectory() as td: for spec in manifest["candidates"][:3]: out=Path(td)/f"{spec['id']}.ini"; receipts.append(lab.build_profile_candidate(current,spec,out)); current=lab.installed_profile - print(json.dumps({"static": static, "incremental_manifest": "PASS", "exact_one_cvar_generation": "PASS", "candidate_receipts": receipts}, indent=2)); return 0 + stability = lab.stability_spec() + assert stability["id"] == "stability-control" and stability["cvar"] == "" and stability["from"] == stability["to"] == "" + print(json.dumps({"static": static, "incremental_manifest": "PASS", "exact_one_cvar_generation": "PASS", "stability_tail_policy": "PASS", "candidate_receipts": receipts}, indent=2)); return 0 if args.command == "campaign": print(lab.run_incremental_campaign(args.duration, args.resume)); return 0 if args.command == "status": From 4e5f764b9aa6d7d4fcb4c2943fbe12904ddc091e Mon Sep 17 00:00:00 2001 From: flashls1 Date: Fri, 11 Sep 2026 03:29:22 -0500 Subject: [PATCH 05/12] Approve classifier blocker repair --- .../AMENDMENT_002_CLASSIFIER_BLOCKER.md | 42 +++++++++++++++++++ .../ZENMC_V3_RECEIPT.json | 38 +++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 .clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_002_CLASSIFIER_BLOCKER.md create mode 100644 .clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V3_RECEIPT.json diff --git a/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_002_CLASSIFIER_BLOCKER.md b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_002_CLASSIFIER_BLOCKER.md new file mode 100644 index 0000000..607f199 --- /dev/null +++ b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_002_CLASSIFIER_BLOCKER.md @@ -0,0 +1,42 @@ +# Amendment 002 — Blocking Classifier False Positive + Truthful Control Terminal State + +## Discovery classification +`BLOCKING DEPENDENCY` discovered by the first live control of campaign `incremental-20260911T082151Z-1f32770a`. + +The control reached real Tocker battle stages 1-1 and 1-2, then the screen classifier labeled the normal stage-1-3 shop frame as `error_reject_marker`. Independent Vision OCR of the saved frame contained no `ERROR`, `FAILED`, `UNAVAILABLE`, `DECLINED READY CHECK`, or `RETURNED TO THE LOBBY` text. + +Debugging the existing classifier proved the exact root cause. `EvidenceMatcher.has()` concatenates every OCR line into `compactCorpus` to permit phrases split across OCR lines. On the saved normal frame, three independent OCR lines `Brawler`, `r`, and `Ornn` concatenate to `BRAWLERRORNN`, which contains the synthetic substring `ERROR`. The current generic `matcher.has("ERROR")` therefore creates a false fail-closed state from cross-line text that never existed on screen. + +A second in-scope bookkeeping defect was exposed at the same time: the run's rollback receipt proves DEV stopped, emulator stopped, profile restored, installed DEV integrity true, frozen LKG integrity true, and `verified=true`, but `incremental_lab.py` sets campaign-level `rollback_verified=false` merely because the control classification is `INCONCLUSIVE`. It then labels the early pre-deadline stop `DEADLINE_COMPLETE`. Both statements are factually false. + +## Smallest authorized repair +1. Keep cross-line `EvidenceMatcher.has()` unchanged for existing phrase matching. +2. Add a line-local matcher and use it only for the strong error markers plus `ERROR`. Error rejection may match inside one OCR line, but never by concatenating unrelated OCR lines. +3. Add a regression self-test reproducing the exact `Brawler` + `r` + `Ornn` boundary so it cannot synthesize `ERROR` again. +4. In `incremental_lab.py`, derive campaign rollback truth from the actual run rollback receipt, never from `INCONCLUSIVE` classification alone. +5. If a control is not green before the deadline but rollback is proven, stop that attempt as `BLOCKED_INCONCLUSIVE` with the exact blocker instead of falsely claiming deadline completion. Keep queue index unchanged. +6. On a governed resume after the blocking dependency is repaired, clear only the transient blocker marker, reconcile actual runtime/rollback through the existing resume path, and reattempt the same current control before the original deadline. Do not erase the prior run or its evidence. +7. Preserve the original campaign deadline. Recovery downtime is an interruption/recovery interval; do not create a second overlapping campaign. + +## Complexity Economics Gate +PASS. This is one matcher method, one targeted call-site change, one regression test, and truthful state bookkeeping in the existing runner. No new classifier, OCR framework, retry service, scheduler, runtime, or recovery subsystem is added. + +## ZenGate +PASS. ZenMC V3 completed with 100,000 trajectories, zero invariant violations, result `PASS`, receipt `052d36aeda6fc63d8a67b07e312166c1e4106e246be5485fa856c76d9573ea30`. The repair is limited to the proven blocker and the false completion/rollback statements it exposed. No tuning candidate, game setting, runtime identity, protected Control/LKG boundary, or comparison doctrine changes. + +## ZenMC V3 qualification +ZENMC_REQUIRED because the repair changes blocked/resume/completion semantics in the four-hour lifecycle. + +Required invariants: +- unrelated OCR lines can never synthesize a strong error marker; +- a non-green control never advances the candidate queue; +- `INCONCLUSIVE` does not imply rollback failure; +- actual rollback failure still blocks all future run admission; +- a verified-rollback blocked control may resume the same queue index before the original deadline after the blocker is repaired; +- deadline still prevents admission of the next run; +- frozen LKG remains immutable; +- candidate promotion still requires positive confirmation; +- post-queue stability controls remain no-mutation working-profile runs only. + +## Resume target after repair +Resume `incremental-20260911T082151Z-1f32770a` from queue index 0 under its original deadline after source validation, live classifier/runner install, classifier regression proof against `state-0051-error.png`, and protected-state verification. diff --git a/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V3_RECEIPT.json b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V3_RECEIPT.json new file mode 100644 index 0000000..cf3a816 --- /dev/null +++ b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V3_RECEIPT.json @@ -0,0 +1,38 @@ +{ + "schema": 1, + "model": "TFTMAC_INCREMENTAL_4H_LIFECYCLE_V3", + "seed": 20260911, + "trajectories": 100000, + "exact_classifier_regression": { + "ocr_lines": ["Brawler", "r", "Ornn"], + "cross_line_compact": "BRAWLERRORNN", + "cross_line_contains_ERROR": true, + "line_local_contains_ERROR": false + }, + "invariants": { + "deadline_prevents_next_run_admission": true, + "frozen_lkg_never_mutated": true, + "inconclusive_does_not_imply_rollback_failure": true, + "line_local_error_markers_only": true, + "non_green_control_never_advances_queue": true, + "original_deadline_preserved": true, + "promotion_requires_positive_confirmation": true, + "rollback_failure_stops_future_admission": true, + "stability_tail_no_mutation": true, + "verified_blocked_control_resumes_same_index": true + }, + "stats": { + "blocked_controls": 55895, + "deadline_blocks": 0, + "promotions": 59392, + "resumed_same_index": 55895, + "rollback_fail_blocks": 82380, + "stability_runs": 1622197, + "verified_rollback_blocks": 55895 + }, + "violation_count": 0, + "violations": [], + "result": "PASS", + "receipt_sha256": "052d36aeda6fc63d8a67b07e312166c1e4106e246be5485fa856c76d9573ea30", + "source_operation_id": "god-a4e54a38-a063-45a4-a96c-fb521a1825db" +} From f3311532f444b6fa945e1d83de2ebb998dd23bba Mon Sep 17 00:00:00 2001 From: flashls1 Date: Fri, 11 Sep 2026 03:33:29 -0500 Subject: [PATCH 06/12] Repair classifier false error and campaign resume truth --- OvernightLab/incremental_lab.py | 43 +++++++++++++++++++++++++++---- tools/tft-screen-classifier.swift | 21 ++++++++++++++- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/OvernightLab/incremental_lab.py b/OvernightLab/incremental_lab.py index 91bb53b..f5477d1 100755 --- a/OvernightLab/incremental_lab.py +++ b/OvernightLab/incremental_lab.py @@ -406,6 +406,14 @@ def run_incremental_campaign(self, duration_seconds: int, resume: bool) -> str: if cid and self.state_path(cid).exists(): self.reconcile_resume(cid) state = self.read_state(cid) + state.pop("blocked_reason", None) + state["campaign_state"] = "RUNNING" + last_rollback = self.db.one( + "SELECT rollback_verified FROM runs WHERE campaign_id=? ORDER BY started_utc DESC LIMIT 1", (cid,)) + if last_rollback is not None: + state["rollback_verified"] = bool(last_rollback["rollback_verified"]) + self.write_state(cid, state) + self.db.execute("UPDATE campaigns SET state='RUNNING',ended_utc=NULL WHERE campaign_id=?", (cid,)) else: cid = self.create_incremental_campaign(duration_seconds) state = self.read_state(cid) @@ -425,10 +433,22 @@ def run_incremental_campaign(self, duration_seconds: int, resume: bool) -> str: base.atomic_json(self.campaign_dir(cid) / f"candidate-{spec['id']}.delta.json", delta_receipt) # Fresh matched control then candidate. control_id, control_class = self.run_profile(cid, spec, "control-initial", working, str(spec["from"]) if base.sha256_file(working) != self.installed_profile_sha else None) + control_rollback_row = self.db.one("SELECT rollback_verified FROM runs WHERE run_id=?", (control_id,)) + control_rollback_verified = bool(control_rollback_row and control_rollback_row["rollback_verified"]) + state["rollback_verified"] = control_rollback_verified if control_class != "CONTROL_VALID": - state["rollback_verified"] = False if control_class == "INCONCLUSIVE" else state["rollback_verified"] - state["results"].append({"candidate": spec["id"], "initial_decision": "CONTROL_NOT_GREEN", "control_run": control_id, "kept": False}) - self.write_state(cid, state); break + state["blocked_reason"] = { + "kind": "CONTROL_NOT_GREEN", + "run_id": control_id, + "classification": control_class, + "rollback_verified": control_rollback_verified, + } + state["results"].append({ + "candidate": spec["id"], "initial_decision": "CONTROL_NOT_GREEN", + "control_run": control_id, "rollback_verified": control_rollback_verified, "kept": False}) + self.write_state(cid, state) + self.incremental_report(cid) + break candidate_id, candidate_class = self.run_profile(cid, spec, "candidate-initial", candidate_profile, str(spec["to"])) initial = self.compare(control_id, candidate_id) if candidate_class == "MECHANISM_WORKING" else {"decision": "INCONCLUSIVE", "reason": candidate_class} item: dict[str, Any] = {"candidate": spec["id"], "control_run": control_id, "candidate_run": candidate_id, "initial_decision": initial["decision"], "initial_comparison": initial, "kept": False} @@ -482,11 +502,24 @@ def run_incremental_campaign(self, duration_seconds: int, resume: bool) -> str: error_class="STABILITY_CONTROL_FAILED" if rollback_verified else "ROLLBACK_FAILURE", phase="campaign") - end_state = "DEADLINE_COMPLETE" if time.monotonic_ns() >= int(state["deadline_monotonic_ns"]) else ("COMPLETE" if state["queue_index"] >= len(specs) else "DEADLINE_COMPLETE") + deadline_reached = time.monotonic_ns() >= int(state["deadline_monotonic_ns"]) + if state.get("blocked_reason"): + end_state = "BLOCKED_INCONCLUSIVE" + elif deadline_reached: + end_state = "DEADLINE_COMPLETE" + elif state["queue_index"] >= len(specs): + end_state = "COMPLETE" + else: + end_state = "BLOCKED_INCONCLUSIVE" state["campaign_state"] = end_state self.write_state(cid, state) self.db.execute("UPDATE campaigns SET state=?,ended_utc=? WHERE campaign_id=?", (end_state, utc_now(), cid)) - self.write_checkpoint(cid, queue_index=int(state["queue_index"]), state=end_state, phase="COMPLETE", current_candidate=None, current_run=None, failure=None) + blocked = state.get("blocked_reason") + self.write_checkpoint( + cid, queue_index=int(state["queue_index"]), state=end_state, + phase="BLOCKED" if end_state == "BLOCKED_INCONCLUSIVE" else "COMPLETE", + current_candidate=None, current_run=None, + failure=canonical_json(blocked) if blocked else None) self.incremental_report(cid) self.generate_report(cid) return cid diff --git a/tools/tft-screen-classifier.swift b/tools/tft-screen-classifier.swift index 70acf91..24bdc84 100644 --- a/tools/tft-screen-classifier.swift +++ b/tools/tft-screen-classifier.swift @@ -457,6 +457,11 @@ private func classifierSelfTest() -> Bool { imageWidth: referenceWidth, imageHeight: referenceHeight ) + let crossLineErrorTrap = EvidenceMatcher(lines: [ + OCRLine(text: "Brawler", normalized: "BRAWLER", compact: "BRAWLER", confidence: 1, boundingBox: .zero), + OCRLine(text: "r", normalized: "R", compact: "R", confidence: 1, boundingBox: .zero), + OCRLine(text: "Ornn", normalized: "ORNN", compact: "ORNN", confidence: 1, boundingBox: .zero), + ]) return shopCost(red: 37, green: 51, blue: 65) == 1 && shopCost(red: 19, green: 53, blue: 44) == 2 && shopCost(red: 28, green: 32, blue: 72) == 3 @@ -466,6 +471,8 @@ private func classifierSelfTest() -> Bool { && boardOccupancy(in: [noisyOccupancy])?.capacity == 4 && postCombatPhase == "post_combat" && planningPhase == "planning" + && crossLineErrorTrap.has("ERROR") + && !crossLineErrorTrap.hasLineLocal("ERROR") } private func emitDebugLines(_ lines: [OCRLine]) { @@ -504,6 +511,18 @@ private final class EvidenceMatcher { phrases.contains(where: has) } + func hasLineLocal(_ phrase: String) -> Bool { + let needle = compactText(normalizedText(phrase)) + guard !needle.isEmpty else { + return false + } + return lines.contains { $0.compact.contains(needle) } + } + + func hasAnyLineLocal(_ phrases: [String]) -> Bool { + phrases.contains { hasLineLocal($0) } + } + func matchedText(for phrases: [String], limit: Int = 4) -> [String] { let needles = phrases.map { compactText(normalizedText($0)) }.filter { !$0.isEmpty } var result: [String] = [] @@ -679,7 +698,7 @@ private func classify(lines: [OCRLine]) -> Classification { }) && lines.contains(where: { $0.normalized == "+" && $0.boundingBox.minX >= 0.75 && $0.boundingBox.midY >= 0.90 }) - if matcher.hasAny(strongErrorMarkers) || (matcher.has("ERROR") && !hasTopBarErrorCurrency) { + if matcher.hasAnyLineLocal(strongErrorMarkers) || (matcher.hasLineLocal("ERROR") && !hasTopBarErrorCurrency) { return Classification( state: .error, stage: nil, From eb6cd5448c1009b828510df02ad4b00146b02630 Mon Sep 17 00:00:00 2001 From: flashls1 Date: Fri, 11 Sep 2026 03:36:54 -0500 Subject: [PATCH 07/12] Approve durable four-hour deadline recovery --- .../AMENDMENT_003_DURABLE_DEADLINE.md | 49 +++++++++++++++++++ .../ZENMC_V4_RECEIPT.json | 36 ++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 .clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_003_DURABLE_DEADLINE.md create mode 100644 .clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V4_RECEIPT.json diff --git a/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_003_DURABLE_DEADLINE.md b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_003_DURABLE_DEADLINE.md new file mode 100644 index 0000000..eb9cd0e --- /dev/null +++ b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_003_DURABLE_DEADLINE.md @@ -0,0 +1,49 @@ +# Amendment 003 — Durable Four-Hour Deadline Across Host-Operation Resume + +## Discovery classification +`BLOCKING DEPENDENCY` discovered during the first governed resume of campaign `incremental-20260911T082151Z-1f32770a`. + +The runner persisted `deadline_monotonic_ns`. The original campaign process stored `14400097332625`, while the next Clara host-operation process observed a current monotonic value near `49626079892666`. The resulting resume calculation falsely reported zero seconds remaining even though the durable wall-clock campaign start was `2026-09-11T08:21:51Z` and the approved four-hour deadline is therefore `2026-09-11T12:21:51Z`. + +A process-local monotonic origin is valid only within one process/clock namespace. It is not a durable checkpoint value across Clara host-operation recovery. Persisting it violated the plan's restart/recovery requirement and could truncate the four-hour campaign without running the queue. + +## Smallest authorized repair +1. Persist the campaign deadline as an absolute UTC timestamp (`deadline_utc`) derived once when the campaign is created. +2. Persist `duration_seconds` for provenance. +3. Use UTC comparison for all candidate admission, confirmation admission, stability-control admission, and final deadline classification. +4. Never reconstruct an absent durable deadline from a fresh process-local monotonic value. +5. For a legacy/in-progress campaign missing `deadline_utc`, fail closed with `CAMPAIGN_DEADLINE_MIGRATION_REQUIRED` until an exact evidence-backed one-time state migration provides it. +6. Migrate the current campaign only from its already-persisted `started_utc=2026-09-11T08:21:51Z` plus the user-approved `4h` duration, yielding `deadline_utc=2026-09-11T12:21:51Z`. Preserve the old monotonic value under a forensic legacy field rather than treating it as authority. +7. Resume the same campaign ID and queue index. Do not create a replacement campaign, do not discard the prior false-classifier run, and do not extend the deadline to compensate for repair time. + +## Complexity Economics Gate +PASS. The canonical durable value is one absolute UTC deadline already supported by Python's standard library. No scheduler, timer service, clock daemon, lease, or new persistence subsystem is required. + +## ZenGate +PASS. ZenMC V4 completed with 100,000 trajectories, zero invariant violations, result `PASS`, receipt `77576f5db814d315d11a8af9992340a778f416239780182dc46fea4646440766`. This amendment changes only the persisted deadline representation and admission checks required for restart-safe execution. Candidate semantics, measurement workload, promotion logic, protected Control/LKG boundaries, and the four-hour wall-clock duration are unchanged. + +## ZenMC V4 qualification +ZENMC_REQUIRED because the defect is specifically in restart/recovery and deadline transitions. + +Required invariants: +- one campaign has one immutable absolute UTC deadline; +- restart/host-operation changes never reset or extend that deadline; +- legacy state without a durable deadline fails closed until explicit evidence-backed migration; +- current campaign migration equals `started_utc + 14400 seconds` exactly; +- no candidate/confirmation/stability run is admitted at or after `deadline_utc`; +- a run admitted before the deadline may finish and must seal rollback/evidence before terminal state; +- queue index and accepted stack survive restart unchanged; +- rollback failure still stops all future admissions; +- frozen LKG remains immutable; +- post-queue runs remain stability-control only. + +## Current campaign migration authority +Campaign: `incremental-20260911T082151Z-1f32770a` + +Persisted start: `2026-09-11T08:21:51Z` + +Approved duration: `14400` seconds (4 hours) + +Exact durable deadline: `2026-09-11T12:21:51Z` + +The migration may occur only after the currently-running old resume process is terminal and the repaired source is validated/installed, so no two processes can race on campaign state. diff --git a/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V4_RECEIPT.json b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V4_RECEIPT.json new file mode 100644 index 0000000..64a9e9c --- /dev/null +++ b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V4_RECEIPT.json @@ -0,0 +1,36 @@ +{ + "schema": 1, + "model": "TFTMAC_INCREMENTAL_4H_LIFECYCLE_V4", + "seed": 20260911, + "trajectories": 100000, + "current_campaign_migration": { + "started_utc": "2026-09-11T08:21:51Z", + "duration_seconds": 14400, + "deadline_utc": "2026-09-11T12:21:51Z" + }, + "invariants": { + "absolute_deadline_immutable": true, + "admitted_run_may_finish_then_seal": true, + "current_migration_is_start_plus_14400": true, + "frozen_lkg_never_mutated": true, + "legacy_missing_deadline_fails_closed": true, + "no_run_admission_at_or_after_deadline": true, + "post_queue_stability_only": true, + "queue_and_stack_survive_restart": true, + "restart_never_resets_or_extends_deadline": true, + "rollback_failure_stops_admission": true + }, + "stats": { + "admitted_before_deadline": 573481, + "deadline_blocks": 7206, + "legacy_migrations": 14286, + "restart_events": 201632, + "rollback_blocks": 19987, + "stability_runs": 231875 + }, + "violation_count": 0, + "violations": [], + "result": "PASS", + "receipt_sha256": "77576f5db814d315d11a8af9992340a778f416239780182dc46fea4646440766", + "source_operation_id": "god-3dd0cd23-502e-4d8d-a81f-79f6b2245fe0" +} From 6d1ed1b5431fe579d077e8642ba0e18e58e0225f Mon Sep 17 00:00:00 2001 From: flashls1 Date: Fri, 11 Sep 2026 03:40:38 -0500 Subject: [PATCH 08/12] Persist restart-safe four-hour deadline --- OvernightLab/incremental_lab.py | 50 +++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/OvernightLab/incremental_lab.py b/OvernightLab/incremental_lab.py index f5477d1..4c788a6 100755 --- a/OvernightLab/incremental_lab.py +++ b/OvernightLab/incremental_lab.py @@ -11,7 +11,7 @@ import statistics import time import uuid -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Any, Optional @@ -30,6 +30,22 @@ def canonical_json(value: Any) -> str: return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True) +def parse_utc(value: str) -> datetime: + parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) + if parsed.tzinfo is None: + raise ValueError("UTC deadline must include a timezone") + return parsed.astimezone(timezone.utc) + + +def deadline_reached(state: dict[str, Any], *, now: Optional[datetime] = None) -> bool: + if "deadline_utc" not in state: + raise base.LabError( + "campaign checkpoint is missing durable deadline_utc", + error_class="CAMPAIGN_DEADLINE_MIGRATION_REQUIRED", component="campaign", phase="resume") + current = (now or datetime.now(timezone.utc)).astimezone(timezone.utc) + return current >= parse_utc(str(state["deadline_utc"])) + + class IncrementalLab(base.OvernightLab): def __init__(self): super().__init__() @@ -83,22 +99,26 @@ def write_state(self, cid: str, state: dict[str, Any]) -> None: def create_incremental_campaign(self, deadline_seconds: int) -> str: self.verify_static_authority() self.ensure_incremental_candidates() - cid = "incremental-" + datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + "-" + uuid.uuid4().hex[:8] + started = datetime.now(timezone.utc).replace(microsecond=0) + started_utc = started.isoformat().replace("+00:00", "Z") + deadline_utc = (started + timedelta(seconds=deadline_seconds)).isoformat().replace("+00:00", "Z") + cid = "incremental-" + started.strftime("%Y%m%dT%H%M%SZ") + "-" + uuid.uuid4().hex[:8] cdir = self.campaign_dir(cid) cdir.mkdir(parents=True, exist_ok=False) working = cdir / "working-profile.ini" shutil.copy2(self.installed_profile, working) self.db.execute( "INSERT INTO campaigns(campaign_id,started_utc,state,authority_sha256,candidate_manifest_sha256,lkg_integrity_passed,pbe_evidence_admitted) VALUES(?,?,?,?,?,1,0)", - (cid, utc_now(), "RUNNING", base.sha256_file(base.AUTHORITY_PATH), base.sha256_file(MANIFEST_PATH))) + (cid, started_utc, "RUNNING", base.sha256_file(base.AUTHORITY_PATH), base.sha256_file(MANIFEST_PATH))) base.ACTIVE_PATH.write_text(cid + "\n", encoding="utf-8") self.write_checkpoint(cid, queue_index=0, state="RUNNING", phase="INCREMENTAL_CAMPAIGN_START", current_candidate=None, current_run=None, failure=None) self.write_state(cid, { "schema": 1, "campaign": "TFTMAC_INCREMENTAL_SMALL_GAINS_V1", "campaign_id": cid, - "started_utc": utc_now(), - "deadline_monotonic_ns": time.monotonic_ns() + deadline_seconds * 1_000_000_000, + "started_utc": started_utc, + "duration_seconds": int(deadline_seconds), + "deadline_utc": deadline_utc, "base_working_version": self.authority["working_version"], "installed_profile_sha256": self.installed_profile_sha, "working_profile_path": str(working), @@ -404,6 +424,11 @@ def run_incremental_campaign(self, duration_seconds: int, resume: bool) -> str: try: cid = self.current_campaign() if resume else None if cid and self.state_path(cid).exists(): + state = self.read_state(cid) + if "deadline_utc" not in state: + raise base.LabError( + "existing campaign requires an evidence-backed durable deadline migration", + error_class="CAMPAIGN_DEADLINE_MIGRATION_REQUIRED", component="campaign", phase="resume") self.reconcile_resume(cid) state = self.read_state(cid) state.pop("blocked_reason", None) @@ -419,7 +444,7 @@ def run_incremental_campaign(self, duration_seconds: int, resume: bool) -> str: state = self.read_state(cid) specs = list(self.incremental_manifest["candidates"]) while state["queue_index"] < len(specs): - if time.monotonic_ns() >= int(state["deadline_monotonic_ns"]): + if deadline_reached(state): break spec = specs[state["queue_index"]] parent = spec.get("requires_parent") @@ -452,7 +477,7 @@ def run_incremental_campaign(self, duration_seconds: int, resume: bool) -> str: candidate_id, candidate_class = self.run_profile(cid, spec, "candidate-initial", candidate_profile, str(spec["to"])) initial = self.compare(control_id, candidate_id) if candidate_class == "MECHANISM_WORKING" else {"decision": "INCONCLUSIVE", "reason": candidate_class} item: dict[str, Any] = {"candidate": spec["id"], "control_run": control_id, "candidate_run": candidate_id, "initial_decision": initial["decision"], "initial_comparison": initial, "kept": False} - if initial["decision"] in {"PROMISING", "HOME_RUN"} and time.monotonic_ns() < int(state["deadline_monotonic_ns"]): + if initial["decision"] in {"PROMISING", "HOME_RUN"} and not deadline_reached(state): confirm_control, cc = self.run_profile(cid, spec, "control-confirm", working, str(spec["from"]) if base.sha256_file(working) != self.installed_profile_sha else None) if cc != "CONTROL_VALID": confirmation = {"decision": "INCONCLUSIVE", "reason": f"confirmation control={cc}"} @@ -480,7 +505,7 @@ def run_incremental_campaign(self, duration_seconds: int, resume: bool) -> str: # Queue exhausted before the four-hour deadline: accumulate stability evidence only. # No new tuning candidate is invented or admitted in this tail loop. stability = self.stability_spec() - while state["queue_index"] >= len(specs) and time.monotonic_ns() < int(state["deadline_monotonic_ns"]): + while state["queue_index"] >= len(specs) and not deadline_reached(state): working = Path(state["working_profile_path"]) run_id, classification = self.run_profile(cid, stability, "stability-control", working, None) rollback_row = self.db.rows("SELECT rollback_verified FROM runs WHERE run_id=?", (run_id,)) @@ -502,10 +527,10 @@ def run_incremental_campaign(self, duration_seconds: int, resume: bool) -> str: error_class="STABILITY_CONTROL_FAILED" if rollback_verified else "ROLLBACK_FAILURE", phase="campaign") - deadline_reached = time.monotonic_ns() >= int(state["deadline_monotonic_ns"]) + deadline_has_elapsed = deadline_reached(state) if state.get("blocked_reason"): end_state = "BLOCKED_INCONCLUSIVE" - elif deadline_reached: + elif deadline_has_elapsed: end_state = "DEADLINE_COMPLETE" elif state["queue_index"] >= len(specs): end_state = "COMPLETE" @@ -554,7 +579,10 @@ def main() -> int: out=Path(td)/f"{spec['id']}.ini"; receipts.append(lab.build_profile_candidate(current,spec,out)); current=lab.installed_profile stability = lab.stability_spec() assert stability["id"] == "stability-control" and stability["cvar"] == "" and stability["from"] == stability["to"] == "" - print(json.dumps({"static": static, "incremental_manifest": "PASS", "exact_one_cvar_generation": "PASS", "stability_tail_policy": "PASS", "candidate_receipts": receipts}, indent=2)); return 0 + deadline_fixture = {"deadline_utc": "2026-09-11T12:21:51Z"} + assert not deadline_reached(deadline_fixture, now=datetime(2026, 9, 11, 8, 21, 51, tzinfo=timezone.utc)) + assert deadline_reached(deadline_fixture, now=datetime(2026, 9, 11, 12, 21, 51, tzinfo=timezone.utc)) + print(json.dumps({"static": static, "incremental_manifest": "PASS", "exact_one_cvar_generation": "PASS", "stability_tail_policy": "PASS", "durable_deadline_policy": "PASS", "candidate_receipts": receipts}, indent=2)); return 0 if args.command == "campaign": print(lab.run_incremental_campaign(args.duration, args.resume)); return 0 if args.command == "status": From 8a7460310191795bbbd12f76875c60999773c7a9 Mon Sep 17 00:00:00 2001 From: flashls1 Date: Fri, 11 Sep 2026 03:59:41 -0500 Subject: [PATCH 09/12] Approve Tocker 1-5 phase classifier repair --- .../AMENDMENT_004_STAGE15_PHASE.md | 53 +++++++++++++++++++ .../ZENMC_V5_RECEIPT.json | 34 ++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 .clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_004_STAGE15_PHASE.md create mode 100644 .clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V5_RECEIPT.json diff --git a/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_004_STAGE15_PHASE.md b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_004_STAGE15_PHASE.md new file mode 100644 index 0000000..b358257 --- /dev/null +++ b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/AMENDMENT_004_STAGE15_PHASE.md @@ -0,0 +1,53 @@ +# Amendment 004 — Tocker 1-5 phase classification blocker + +## Classification + +BLOCKING DEPENDENCY discovered by the live governed campaign. This amendment does not admit a new optimization candidate or widen the performance experiment. It repairs the existing screen-classifier semantics required to reach the already-approved 1-5 measurement gate. + +## Current evidence + +Campaign `incremental-20260911T082151Z-1f32770a` remains at queue index 0 with no accepted performance candidate and verified rollback. + +Latest control `run-747b11debe5a47a09dc96a24abdc8213` reached Tocker stage 1-5 and remained there until the navigation watchdog expired. The final screenshot is a normal battle HUD with OCR including stage `1-5`, `Score`, score value `36,550`, traits, gold, and board occupancy. It contains no reject/error/login marker. The classifier returns `state=battle`, `stage=1-5`, but `phase=null` because the stage-1-5 combat presentation does not expose the cyan combat timer or a literal `COMBAT` marker. + +A read-only audit of all 371 current-campaign battle classifications found: +- 315 phase-null rows: `SCORE`, no shop, no FIGHT. +- 46 known combat rows: `SCORE`, no shop; 12 also expose FIGHT late in combat. +- 2 known planning rows with SCORE both expose FIGHT. +- 6 known planning rows without SCORE expose shop/planning controls. +- 1 observed post-combat SCORE row exposes shop/reward state and is already recognized through the `TIME BONUS` path. + +Therefore `SCORE` alone is not a legal new phase signal. The admissible missing semantic is: a battle frame with SCORE after existing explicit combat/planning/post-combat checks, where no shop is open and no FIGHT control is visible. + +## Purified repair + +1. Compute `shopOpen` and `fightButtonVisible` before `battlePhase` is called. +2. Pass those two already-existing observed UI facts into `battlePhase`. +3. Preserve precedence exactly: + - explicit `COMBAT` / cyan timer -> combat; + - explicit PLANNING/PREPARE/FIGHT/REROLL -> planning; + - TIME BONUS -> post_combat; + - only then, if battle HUD contains line-local SCORE and both `shopOpen == false` and `fightButtonVisible == false`, classify combat. +4. Keep all reject/login/disconnect/trial-choice classification behavior unchanged. +5. Add self-test fixtures proving: + - SCORE + no shop + no FIGHT -> combat; + - SCORE + FIGHT -> planning; + - TIME BONUS remains post_combat. +6. Replay the exact failed 1-5 screenshot and require `state=battle`, `stage=1-5`, `phase=combat`. +7. Production validate, checkpoint/publish, install the classifier through the existing OvernightLab install seam, prove source/live parity, then resume the same campaign at queue index 0 and immutable deadline `2026-09-11T12:21:51Z`. + +## Non-goals + +- No candidate/CVar change. +- No new workload. +- No timeout increase. +- No blind click. +- No Control/LKG mutation. +- No app/emulator/AVD rebuild. +- No change to promotion thresholds. + +## ZenGate + +PASS. ZenMC V5 (`TFTMAC_INCREMENTAL_4H_LIFECYCLE_V5_STAGE15_PHASE`) completed 100,000 trajectories with zero invariant violations. Receipt SHA-256: `d5731f249d5921d102216af7324e8ee72b02b4491e3e0db0ba21a647a1588d5a`. + +The simplest safe mechanism is extending the existing classifier with two already-computed UI facts; no new state machine, service, store, or instrumentation is required. diff --git a/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V5_RECEIPT.json b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V5_RECEIPT.json new file mode 100644 index 0000000..56f90ae --- /dev/null +++ b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/ZENMC_V5_RECEIPT.json @@ -0,0 +1,34 @@ +{ + "invariants": { + "deadline_stops_new_admission": true, + "explicit_combat_precedence": true, + "frozen_lkg_never_mutated": true, + "non_green_control_resumes_same_queue_index": true, + "planning_or_fight_precedes_score_fallback": true, + "promotion_still_requires_positive_confirmation": true, + "rollback_failure_stops_advance": true, + "score_fallback_requires_no_shop_and_no_fight": true, + "time_bonus_post_combat_precedes_score_fallback": true + }, + "model": "TFTMAC_INCREMENTAL_4H_LIFECYCLE_V5_STAGE15_PHASE", + "receipt_sha256": "d5731f249d5921d102216af7324e8ee72b02b4491e3e0db0ba21a647a1588d5a", + "result": "PASS", + "schema": 1, + "seed": 20260911, + "source_operation_id": "tftmac-4h-zenmc-v5-stage15-phase-20260911-v1", + "stats": { + "deadline_blocks": 7629, + "explicit_combat": 17940, + "explicit_planning": 25035, + "explicit_post": 4569, + "promotions": 9911, + "resumed_same_index": 10636, + "rollback_blocks": 3521, + "score_fallback_combat": 31256, + "score_with_fight_planning": 11239, + "score_with_shop_not_fallback": 6312 + }, + "trajectories": 100000, + "violation_count": 0, + "violations": [] +} From c744e9d81f7bf2364e642cb58478e0f326737c7f Mon Sep 17 00:00:00 2001 From: flashls1 Date: Fri, 11 Sep 2026 04:01:21 -0500 Subject: [PATCH 10/12] Classify Tocker 1-5 score-only battle phase --- tools/tft-screen-classifier.swift | 62 +++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/tools/tft-screen-classifier.swift b/tools/tft-screen-classifier.swift index 24bdc84..318c2f2 100644 --- a/tools/tft-screen-classifier.swift +++ b/tools/tft-screen-classifier.swift @@ -441,21 +441,52 @@ private func classifierSelfTest() -> Bool { confidence: 1, boundingBox: CGRect(x: 0.8, y: 0.1, width: 0.1, height: 0.04) ) + let scoreOnly = OCRLine( + text: "Score", + normalized: "SCORE", + compact: "SCORE", + confidence: 1, + boundingBox: CGRect(x: 0.85, y: 0.78, width: 0.08, height: 0.04) + ) let postCombatPhase = battlePhase( for: battle, - lines: [timeBonus], + lines: [timeBonus, scoreOnly], combatCyanPixels: 0, combatCyanLongestRun: 0, imageWidth: referenceWidth, - imageHeight: referenceHeight + imageHeight: referenceHeight, + shopOpen: true, + fightButtonVisible: false ) let planningPhase = battlePhase( for: battle, - lines: [timeBonus, planning], + lines: [scoreOnly, planning], combatCyanPixels: 0, combatCyanLongestRun: 0, imageWidth: referenceWidth, - imageHeight: referenceHeight + imageHeight: referenceHeight, + shopOpen: false, + fightButtonVisible: true + ) + let scoreCombatPhase = battlePhase( + for: battle, + lines: [scoreOnly], + combatCyanPixels: 0, + combatCyanLongestRun: 0, + imageWidth: referenceWidth, + imageHeight: referenceHeight, + shopOpen: false, + fightButtonVisible: false + ) + let scoreShopBlockedPhase = battlePhase( + for: battle, + lines: [scoreOnly], + combatCyanPixels: 0, + combatCyanLongestRun: 0, + imageWidth: referenceWidth, + imageHeight: referenceHeight, + shopOpen: true, + fightButtonVisible: false ) let crossLineErrorTrap = EvidenceMatcher(lines: [ OCRLine(text: "Brawler", normalized: "BRAWLER", compact: "BRAWLER", confidence: 1, boundingBox: .zero), @@ -471,6 +502,8 @@ private func classifierSelfTest() -> Bool { && boardOccupancy(in: [noisyOccupancy])?.capacity == 4 && postCombatPhase == "post_combat" && planningPhase == "planning" + && scoreCombatPhase == "combat" + && scoreShopBlockedPhase == nil && crossLineErrorTrap.has("ERROR") && !crossLineErrorTrap.hasLineLocal("ERROR") } @@ -812,7 +845,9 @@ private func battlePhase( combatCyanPixels: Int, combatCyanLongestRun: Int, imageWidth: Int, - imageHeight: Int + imageHeight: Int, + shopOpen: Bool, + fightButtonVisible: Bool ) -> String? { guard classification.state == .battle || classification.state == .trialChoice else { return nil @@ -852,9 +887,17 @@ private func battlePhase( // the stable semantic marker on that completed-combat screen. Exposing a // separate phase lets the harness open each reward chooser without ever // guessing from a generic battle frame. - if EvidenceMatcher(lines: lines).has("TIME BONUS") { + let matcher = EvidenceMatcher(lines: lines) + if matcher.has("TIME BONUS") { return "post_combat" } + // Tocker stage 1-5 can omit the cyan combat timer while retaining the + // battle HUD and SCORE. Treat that bounded shape as combat only after all + // stronger phase signals above have failed, and only when neither the shop + // nor the FIGHT control is visible. + if matcher.hasLineLocal("SCORE") && !shopOpen && !fightButtonVisible { + return "combat" + } return nil } @@ -927,6 +970,7 @@ private func main() -> Int32 { let combatCyanMetrics = combatCyanMetrics(in: image) let interfaceMatcher = EvidenceMatcher(lines: lines) let shopOpen = interfaceMatcher.has("REROLL") + let fightButtonVisible = interfaceMatcher.has("FIGHT") let occupancy = boardOccupancy(in: lines) try emit( classification, @@ -936,7 +980,9 @@ private func main() -> Int32 { combatCyanPixels: combatCyanMetrics.pixels, combatCyanLongestRun: combatCyanMetrics.longestRun, imageWidth: image.width, - imageHeight: image.height + imageHeight: image.height, + shopOpen: shopOpen, + fightButtonVisible: fightButtonVisible ), ocrCount: lines.count, combatCyanPixels: combatCyanMetrics.pixels, @@ -947,7 +993,7 @@ private func main() -> Int32 { shopCosts: shopCosts(in: image, shopOpen: shopOpen), boardUnits: occupancy?.units, boardCapacity: occupancy?.capacity, - fightButtonVisible: interfaceMatcher.has("FIGHT"), + fightButtonVisible: fightButtonVisible, combatBannerVisible: interfaceMatcher.has("COMBAT") ) return 0 From f4e92dcc966ff46bd7d575361182d259af22c2b4 Mon Sep 17 00:00:00 2001 From: flashls1 Date: Fri, 11 Sep 2026 07:52:43 -0500 Subject: [PATCH 11/12] Log inconclusive stability soaks without aborting --- OvernightLab/incremental_lab.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/OvernightLab/incremental_lab.py b/OvernightLab/incremental_lab.py index 4c788a6..897d509 100755 --- a/OvernightLab/incremental_lab.py +++ b/OvernightLab/incremental_lab.py @@ -521,10 +521,13 @@ def run_incremental_campaign(self, duration_seconds: int, resume: bool) -> str: state["rollback_verified"] = False self.write_state(cid, state) self.incremental_report(cid) - if classification != "MECHANISM_WORKING" or not rollback_verified: + # Stability controls are evidence-only after the candidate queue is exhausted. + # An inconclusive soak reduces confidence but must not abort the campaign when + # rollback is proven; only an unproven rollback may stop future admission. + if not rollback_verified: raise base.LabError( - f"stability-control not green: classification={classification} rollback={rollback_verified}", - error_class="STABILITY_CONTROL_FAILED" if rollback_verified else "ROLLBACK_FAILURE", + f"stability-control rollback is unproven: classification={classification}", + error_class="ROLLBACK_FAILURE", phase="campaign") deadline_has_elapsed = deadline_reached(state) From 2d0d6f3ad9cbd45e1551bb5f6c8afb454e3d2a14 Mon Sep 17 00:00:00 2001 From: flashls1 Date: Fri, 11 Sep 2026 07:59:30 -0500 Subject: [PATCH 12/12] Record terminal four-hour optimization sweep --- .../FINAL_RESULTS.md | 76 +++++++++++++++++++ CHANGELOG.md | 18 +++++ facts.md | 2 +- project.md | 9 ++- ssot/AUTHORITY_INPUTS.sha256 | 4 +- ssot/STACK.lock.yaml | 2 +- 6 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 .clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/FINAL_RESULTS.md diff --git a/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/FINAL_RESULTS.md b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/FINAL_RESULTS.md new file mode 100644 index 0000000..2db32cd --- /dev/null +++ b/.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/FINAL_RESULTS.md @@ -0,0 +1,76 @@ +# TFTMAC four-hour incremental sweep — final results + +Campaign: `incremental-20260911T082151Z-1f32770a` +Governed change: `25f21f7e-ba86-4e3d-832d-9984522811c0` +Start: `2026-09-11T08:21:51Z` +Immutable deadline: `2026-09-11T12:21:51Z` +Terminal state: `DEADLINE_COMPLETE` +Base/final winner: `DEV-B8-WIN-01` +Final working DeviceProfiles SHA-256: `45d6465040ef8b7298cd8da000208e4ee0be924c65a20326900267f25adf5c9c` +Accepted cumulative candidates: none +Rollback/integrity: PASS for every recorded run; protected Control/LKG remained unchanged. + +## Candidate decisions + +| Candidate | Exact intended delta | Result | Integration | +| --- | --- | --- | --- | +| `pso-precompile-threads-2` | `r.pso.PrecompileThreadPoolSize` `4 -> 2` | `INCONCLUSIVE` — matched control became valid, but candidate apply failed because ADB shell privilege was not restored after root use; no admissible candidate performance windows | NO | +| `shader-background-batch-4` | `r.ShaderPipelineCache.BackgroundBatchSize` `20 -> 4` | `INCONCLUSIVE` — matched control became valid, but candidate apply failed because ADB shell privilege was not restored; no admissible candidate performance windows | NO | +| `animation-budget-5ms` | `a.Budget.BudgetMs` `6.0 -> 5.0` | `INCONCLUSIVE` — matched control became valid, but candidate apply failed because ADB root did not become effective; no admissible candidate performance windows | NO | +| `animation-budget-4ms` | `a.Budget.BudgetMs` `5.0 -> 4.0` | `SKIPPED_PARENT_NOT_ACCEPTED` as required because the 5 ms parent did not confirm | NO | + +No directional candidate gain can be claimed because none of the candidate profiles produced valid measured candidate windows. These settings are therefore unresolved, not rejected, and must not be entered on the no-recycle list solely from this campaign. + +## Matched current-winner controls + +The final valid 1-5 control runs on `DEV-B8-WIN-01` produced: + +- PSO matched control `run-382fa917a73e49d2b60732d120c93199`: mean 53.954 FPS, mean 1% low 23.251 FPS, mean p95 25.210 ms, mean p99 36.506 ms across 3 native windows. +- Shader-background matched control `run-6f0521267dad47e5af07e0b07d59dccf`: mean 58.144 FPS, mean 1% low 41.095 FPS, mean p95 20.766 ms, mean p99 24.578 ms across 3 native windows. +- Animation-budget matched control `run-02cb6209b65345fa8f45073bf9d72262`: mean 59.718 FPS, mean 1% low 35.424 FPS, mean p95 20.110 ms, mean p99 27.801 ms across 3 native windows. + +These are baseline observations only; they do not establish a candidate improvement. + +## Stability evidence after queue exhaustion + +The working stack never changed from `DEV-B8-WIN-01`. Three stability-control runs completed with valid native 1-5 windows: + +- `run-1aacb80614c841cdb92f6afacb924b76`: 57.687 FPS, 1% low 28.563 FPS, p95 20.677 ms, p99 34.411 ms, jank rate 0.0240, severe rate 0. +- `run-5156270168b54271981aef45d388704a`: 58.657 FPS, 1% low 29.441 FPS, p95 19.716 ms, p99 29.954 ms, jank rate 0.0176, severe rate 0.00293. +- `run-6ecaaf8ba33144699a89fc662cbda88b`: 59.789 FPS, 1% low 40.990 FPS, p95 19.092 ms, p99 23.170 ms, jank rate 0.00867, severe rate 0. + +A fourth stability run, `run-b218cc8ef58e4aafa56ac1686ade965c`, was `INCONCLUSIVE` because the expected ADB device disappeared before measurement. Its rollback still passed. This lowers additional soak confidence but does not invalidate the three green stability runs or change the working profile. + +## Native capture authority + +Key current-client native SQLite evidence proving the final decisions: + +- PSO control: `2026-09-11T09-02-10.398Z-bcb9464d-f37d-478a-9ee0-4406c3ce2597`, SHA-256 `def22f2c57cb14addbec982c6c7bc1f2096221e5ffd1952ef8cf0722e1174fbd`. +- PSO candidate attempt: `2026-09-11T09-05-57.862Z-f4f37053-86f8-40c8-8542-cce32461e597`, SHA-256 `9a38380dbb836ec82de4d2526d73e3cb5e4b701ef101558d8dbd0bcd24eba2b1`. +- Shader control: `2026-09-11T09-06-45.013Z-c86c72af-4603-499e-8c92-58f6054edbb5`, SHA-256 `9353535b58bbb370618611d8bc7db91f7bbf0e16a07f7a373277e74c66971f84`. +- Shader candidate attempt: `2026-09-11T09-07-59.360Z-cf54aecb-82ec-41c5-b577-8abce7f28a57`, SHA-256 `7d66f8266183254c62f5fa480f0e84c4f08d1515e4605c8bd6d04e2a0c29c765`. +- Animation control: `2026-09-11T09-08-45.780Z-042b0701-5250-445f-93d1-cad0d023b870`, SHA-256 `767969a81806f2ab74a026e28d85b631a26005929a56a5a49b4518f3967ce615`. +- Animation candidate attempt: `2026-09-11T09-10-00.670Z-90d45bde-3fa0-4f6d-83b2-22c936658fa3`, SHA-256 `a209ba85773882b79b8147779c406fbb67a12dcb723ba7c4ea7ca3168e651cc8`. +- Green stability captures: `2026-09-11T09-10-46.345Z-3a64aae6-53c5-43e0-8ad1-64ebd974c7ce` (`0d69c3c09b78ab2c0e4e6f7a22a9d3050f73c23a47f48e2de0abc69d3985692f`), `2026-09-11T09-12-04.231Z-b0c427f0-620a-442b-91d7-e9da3089e8d9` (`5bec5fd6302246a243ab8999a3dfdbe2a4d14299b0c4a2f3138faf8333456f7d`), and `2026-09-11T09-13-19.773Z-1d0c6b04-fa79-4251-8f0e-5493a219646e` (`fa5a0fac8cd0ca422d31041d99dda366907224895fb8ba9a415e9b7c589d1096`). +- Inconclusive stability capture: `2026-09-11T09-14-35.174Z-a3310613-2619-4e5d-b0df-2523903d6306`, SHA-256 `c77d0ae1d850413964715c216a098be65ea9e87a521c107e08c22adbc90b877e`. + +All evidence used the official live package `com.riotgames.league.teamfighttactics` `18.1-5423749` / `8423749` and current DEV route. Raw capture remains authoritative. + +## Correctness, rollback, and infrastructure findings + +- Every campaign run has verified DEV stop, emulator stop, profile restore, installed DEV integrity, and frozen LKG integrity. +- No candidate setting was retained; final working profile bytes are identical to the campaign start profile. +- Campaign execution exposed and repaired three controller/classifier blockers without changing protected Control/LKG: false cross-line `ERROR` OCR matching; missing Tocker 1-5 score-only combat-phase classification; and non-durable process-local deadline persistence. The stability tail was also corrected so an inconclusive evidence-only soak is logged rather than aborting when rollback is verified. +- Candidate application remains unable to provide a reliable root/unroot bind-overlay transaction on this runtime. That is the prerequisite for a future valid CVar campaign, not evidence that any of the three candidate CVars are bad. + +## Distance to continuous useful 60 FPS + +The sweep produced **zero verified configuration gain** because the accepted stack did not change. It therefore does not move the formal winner beyond `DEV-B8-WIN-01`. It did add current 1-5 stability evidence showing the existing winner can run near the 60 FPS target for bounded windows, including a 59.789 FPS three-window mean on the strongest soak, while lower 1% lows and run-to-run variance confirm continuous useful 60 FPS is not yet proven. + +## Strongest evidence-backed next test + +No new setting family earned comparative evidence in this campaign. After the DeviceProfiles bind-overlay root/unroot transaction is made reliable in a separately governed future run, the first unresolved evidence-backed setting to retest remains `r.pso.PrecompileThreadPoolSize=2` because it was first in the approved queue and never received a valid candidate measurement. The shader-background and 5 ms animation-budget candidates likewise remain unresolved. Do not run the conditional 4 ms animation budget unless 5 ms first confirms positive. + +## Do-not-repeat boundary + +This campaign adds **no new candidate** to the historical do-not-recycle list. Continue to honor the existing rejected/no-recycle families in project authority, including direct Vulkan, queue-submit-inline, virtual-queue-off, fence-contexts-off, prior ASG variants, blind quality cuts, and PBE-derived candidates. Do not mistake the three ADB-invalid candidate attempts in this campaign for performance rejections. diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ed3fa7..110bcb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -187,6 +187,24 @@ This is a verified **frame-pacing / tail-latency win**, not a claim that mean FP - Recovery therefore uses surviving native session evidence plus the synchronized record books. A fresh live OvernightLab may be initialized from current authority, but historical derived campaign rows/results must not be synthesized merely to replace deleted local output. - This continuity repair does not reopen buffer retention, direct Vulkan, queue-submit-inline, virtual-queue-off, fence-contexts-off, historical global-sync, or any other resolved fast-pass candidate. +#### Four-hour incremental optimization sweep — 2026-09-11 + +**Campaign:** `incremental-20260911T082151Z-1f32770a` +**Outcome:** `DEADLINE_COMPLETE / NO NEW WINNER` +**Integration:** `NONE` — `DEV-B8-WIN-01` remains the verified working winner. +**Final working profile SHA-256:** `45d6465040ef8b7298cd8da000208e4ee0be924c65a20326900267f25adf5c9c`. + +- `r.pso.PrecompileThreadPoolSize 4 -> 2`: **INCONCLUSIVE / NOT INTEGRATED**. A valid matched 1-5 control completed, but candidate apply failed because ADB shell privilege was not restored after the root profile-overlay transaction. No admissible candidate performance windows exist. +- `r.ShaderPipelineCache.BackgroundBatchSize 20 -> 4`: **INCONCLUSIVE / NOT INTEGRATED**. The matched control completed; candidate apply again failed on ADB unroot. No candidate performance conclusion is allowed. +- `a.Budget.BudgetMs 6.0 -> 5.0`: **INCONCLUSIVE / NOT INTEGRATED**. The matched control completed; candidate apply failed because ADB root did not become effective. +- `a.Budget.BudgetMs 5.0 -> 4.0`: **SKIPPED** exactly as planned because the 5 ms parent was not accepted. +- No candidate entered the cumulative working stack, so no `DEV-B8-WIN-02` identity is created. +- Three valid post-queue `DEV-B8-WIN-01` stability soaks at stage 1-5 averaged **57.687**, **58.657**, and **59.789 FPS** respectively. Their mean p95 values were **20.677**, **19.716**, and **19.092 ms**; mean p99 values were **34.411**, **29.954**, and **23.170 ms**. A fourth soak was inconclusive after the expected ADB device disappeared, but rollback still passed. +- Every recorded run verified DEV/emulator shutdown, profile restoration, installed DEV integrity, and frozen LKG integrity. Protected Control/LKG was not modified. +- The campaign also repaired blocking controller/classifier defects encountered while obtaining valid evidence: cross-line OCR falsely forming `ERROR`, missing Tocker 1-5 score-only combat-phase recognition, process-local monotonic deadline persistence, and abort-on-inconclusive stability soak behavior. These are tooling/controller repairs, not performance wins. +- The three candidate settings remain **unresolved, not rejected**. A future campaign must first make the DeviceProfiles bind-overlay root/unroot transaction reliable, then may retest the same evidence-backed queue. Do not add these settings to the historical no-recycle list from this run. +- Full terminal decisions, native capture IDs/hashes, stability evidence, and next-test boundary are recorded in `.clara/plans/25f21f7e-ba86-4e3d-832d-9984522811c0/FINAL_RESULTS.md`. + ### Documentation - Reconciled current Build 8 runtime, automatic-logging, and graphics-causality diff --git a/facts.md b/facts.md index 5ff9c5c..aa01792 100644 --- a/facts.md +++ b/facts.md @@ -3,7 +3,7 @@ > **CURRENT DEV AUTHORITY — 2026-09-11 America/Chicago.** The DEV optimization/versioning records in `project.md` and `CHANGELOG.md` supersede older mutable DEV build, campaign, performance, and readiness claims below. Historical measurements retain their original dates and remain evidence for their recorded configuration only. **Authority date:** 2026-09-11 America/Chicago -**Current DEV evidence through:** 2026-09-11 incremental-gains optimization doctrine; latest performance winner remains `DEV-B8-WIN-01` +**Current DEV evidence through:** 2026-09-11 terminal four-hour evidence-grounded incremental sweep; no candidate was promoted and latest performance winner remains `DEV-B8-WIN-01` **Purpose:** preserve current facts and hard boundaries that future TFTMAC work must not casually reinterpret. ## Mandatory current-authority reading and record-book rule diff --git a/project.md b/project.md index f743eb4..d705ef9 100644 --- a/project.md +++ b/project.md @@ -3,13 +3,13 @@ > **LIVING WIKI / CURRENT DEV STATE — updated 2026-09-11 America/Chicago.** This top section is the current mutable project state. Older dated sections below are preserved as project history and must not override this section when they conflict with newer verified evidence. **Project:** native macOS TFT client experience using the official Android TFT package -**Current development line:** `master` remains merged repository authority. OvernightLab authority reconciliation change `ff2f318b-245d-418a-b86f-e07d55b19826` merged through PR #9 at `bf61c21a723f2f132834acedda860efbb2223d42` and is closed. Current post-merge continuity/live-layer recovery is isolated in Clara change `b42fb30e-2f22-45f0-9d27-00d9e67a58bf`; it does not admit a new performance candidate. +**Current development line:** `master` remains merged repository authority. The evidence-grounded four-hour incremental sweep is recorded in Clara change `25f21f7e-ba86-4e3d-832d-9984522811c0`; campaign `incremental-20260911T082151Z-1f32770a` reached `DEADLINE_COMPLETE` with no promoted candidate and no change to the verified winner. **Protected release/LKG:** TFTMAC 2.3.0 build 8 Control/LKG remains separate, frozen, and available as historical rollback/comparison authority. **Current DEV application identity:** TFTMAC 2.3.0 build 8 DEV (`/Applications/TFTMAC DEV.app`, bundle `com.flashls1.tftmac.dev`). Test-ledger versions do not change the product release number. **Current official client:** `com.riotgames.league.teamfighttactics` `18.1-5423749`, versionCode `8423749`. **Current test series:** `DEV-B8-2026-09-10-A`. **Current verified working winner:** **`DEV-B8-WIN-01`**. -**Project record current through:** 2026-09-11 incremental-gains doctrine and post-PR-#9 continuity; performance winner remains `DEV-B8-WIN-01`. +**Project record current through:** 2026-09-11 terminal four-hour incremental sweep; performance winner remains `DEV-B8-WIN-01`, with no `DEV-B8-WIN-02` created. ## 0. Living wiki contract @@ -65,6 +65,11 @@ Mandatory companion records: - Fence Contexts Off — **NO WIN / NOT INTEGRATED** from prior official-client combat result. - ASG draw flush 400 µs — historical/experimental; **not reopened in this pass**. - Inverse `preferSubmitAtFBOBoundary` check — **INCONCLUSIVE / NOT ACTUALLY APPLIED / NOT INTEGRATED**. +- `r.pso.PrecompileThreadPoolSize 4 -> 2` — **INCONCLUSIVE / NOT INTEGRATED**; valid control, candidate overlay failed on ADB unroot before admissible candidate measurement. +- `r.ShaderPipelineCache.BackgroundBatchSize 20 -> 4` — **INCONCLUSIVE / NOT INTEGRATED**; valid control, candidate overlay failed on ADB unroot before admissible candidate measurement. +- `a.Budget.BudgetMs 6.0 -> 5.0` — **INCONCLUSIVE / NOT INTEGRATED**; valid control, candidate overlay failed because ADB root did not become effective. +- `a.Budget.BudgetMs 5.0 -> 4.0` — **SKIPPED / NOT INTEGRATED** because its 5 ms parent did not confirm. +- Four-hour sweep stability tail — three valid 1-5 `DEV-B8-WIN-01` soaks at 57.687 / 58.657 / 59.789 FPS mean, plus one rollback-clean inconclusive soak. No configuration promotion resulted. The exact measurements and reasoning for each entry live in `CHANGELOG.md`; this wiki carries only the current state needed to choose the next action correctly. diff --git a/ssot/AUTHORITY_INPUTS.sha256 b/ssot/AUTHORITY_INPUTS.sha256 index f03fbd3..f5a47bf 100644 --- a/ssot/AUTHORITY_INPUTS.sha256 +++ b/ssot/AUTHORITY_INPUTS.sha256 @@ -1,8 +1,8 @@ # TFTMAC Build 8 documentation and machine-readable authority inputs # Regenerate only after reviewing the complete authority change set. -3ba1fd9c4a946a4bc89a9f4994af1a27cd3fe061c79a3f28669b35dde2586fec facts.md -8d8cf83fea6d756fd069a8ffe5e9b4649c31503b7e73bea75b4fae39191a7e79 project.md +371a0d743548cd6349779352b9d11d0d4179547da4e0d2946fe6ce151c27656e facts.md +2bcf20562cd8355e03d7d325bf880bf0d81d170fe162a06ccaaf9e9653e1f3ed project.md a91b5af9a15da1a519d336d5b78c6f7fb5d59de9970f99afb448c3a26d8ed54c dev.md 87eb1d0585e7c244a9f0a0eca74078f005ee2c8e7c6ab1ea737fc6c6be897720 benchmark.md 143af322d05e3ac8ccae92baf279ed8e6e8f62ce5bd2f757e045868e1f20c2c8 TFTMAC_GPU_RUNTIME_SSOT.md diff --git a/ssot/STACK.lock.yaml b/ssot/STACK.lock.yaml index 980de0e..2814958 100644 --- a/ssot/STACK.lock.yaml +++ b/ssot/STACK.lock.yaml @@ -10,7 +10,7 @@ authority: documentation_authority: "facts.md > project.md > CHANGELOG.md > benchmark.md + dev.md + supporting references" authority_inputs: "ssot/AUTHORITY_INPUTS.sha256" runtime_authority_sha256: "3607fef47c7ca0b7ebd3ced416e0b3eb9e7dd37143624b97da2670ee283c7169" - authority_inputs_sha256: "a32d76a14a956aa0523b5c65f57654beab8a5b3107ac8eea372d034913948a63" + authority_inputs_sha256: "7af3be76ef482b262c81baa0d4a2019daf822bfc26e911333ccc448740358548" zengate_version: "2.3" zengate_score: 95.8 zengate_result: "PASS"