import type { RendererObservation, SettleObservation, StateSnapshot, TouchControlsObservation, VerifyObservations, VerifyResult } from './classify.js'; import type { EventsObservation } from './events.js'; import type { MobileParityGap } from './mobile-parity.js'; import type { VerifyPlatform } from './platform.js'; export declare const VERIFY_RESULT_FILENAME = "result.json"; /** * What `bitmagic verify` observed, written so `bitmagic publish` can gate on it. * * `fingerprint` is what makes the record trustworthy later: it names the exact project state * that was verified, so publish can tell "verified and unchanged" from "verified, then edited". */ export interface VerifyRunRecord { ok: boolean; failures: string[]; warnings: string[]; fingerprint: string; /** ISO 8601. Reported to the creator; never used to decide staleness. */ at: string; /** * Which renderer was requested and what actually drove the canvas. Optional — `isRecord` must * not require it, so records written by older CLI versions keep parsing (publish gates on this * file across versions). */ renderer?: RendererObservation; /** The run failed on a lost WebGL context and was automatically re-run once on software GL; * the recorded verdict is the retry's. Optional for the same cross-version reason. */ retriedWithSoftwareGl?: boolean; /** How the settle ended: early on provable stability, or at the `--timeout` cap. */ settle?: SettleObservation; /** Live engine state at the end of the run — what the agent reads instead of pixels. */ snapshot?: StateSnapshot | null; /** Semantic gameplay events (deaths, pickups, match-end) from the engine's `?eventlog=1` * session; null when the engine predates it. Optional for the same cross-version reason. */ events?: EventsObservation | null; /** * Which platform the fields above describe — the LEAD platform, `platforms[0]` of the run. * Absent on records written before `--platform` existed, which were all desktop. */ platform?: VerifyPlatform; /** * Touch affordances the engine rendered — the direct evidence that the mobile branch ran. * Absent on a desktop lead run, where the engine never builds MobileControls at all, so * absent is not the same as zero. */ touchControls?: TouchControlsObservation | null; /** * The engine's mobile-parity gaps, as it reported them at game start. Null means it reported * none: a clean game, an engine too old to have the check, or a genre whose controller does * not implement it. A property of the game's CODE rather than of the emulated device, so it is * reported once whatever the run covered. */ mobileParity?: MobileParityGap | null; /** * One entry per platform, in run order, present ONLY on a run that covered more than one. * A single-platform record keeps exactly the shape it has always had — nothing that reads * these records today has a new key to learn. */ platforms?: VerifyPlatformRun[]; } /** One platform's half of a multi-platform run. */ export interface VerifyPlatformRun { platform: VerifyPlatform; ok: boolean; /** Unprefixed — the platform is the sibling key, so the prefixing the merged top-level * `failures`/`warnings` need would only be noise here. */ failures: string[]; warnings: string[]; renderer?: RendererObservation; retriedWithSoftwareGl?: boolean; settle?: SettleObservation; snapshot?: StateSnapshot | null; events?: EventsObservation | null; /** Touch affordances the engine rendered. Only on a mobile run. */ touchControls?: TouchControlsObservation | null; /** * File names, not paths: every artifact is a sibling of the `result.json` these appear in, and * an absolute path baked into a persisted record describes the machine that wrote it rather * than the run. */ screenshotFile?: string; consoleFile: string; } export declare function writeVerifyRecord(artifactDir: string, record: VerifyRunRecord): void; /** * The last recorded run, or `null` if there is none to trust. * * Unreadable and malformed both read as null on purpose. This is not swallowing an error: the * caller's next move is identical in all three cases — tell the creator to run `bitmagic * verify` — and a partially-parsed record would be a worse thing to gate a publish on than no * record at all. */ export declare function readVerifyRecord(artifactDir: string): VerifyRunRecord | null; /** * What a classified verify run produces: the record to write, and whether the command then * fails. * * Split out of the command so the ORDER is testable. The record must be written even when the * run failed — publish distinguishes "verify failed" from "verify never ran", and those have * different messages and different remedies. Inside `run()` that ordering is only observable by * spawning vite and a browser, which is why it went unguarded. */ export type VerifyRunExtras = Pick; /** * One platform's finished run, ready to merge. * * Shared by the one-shot command and the watch loop because both assemble exactly this from * exactly these parts, and the only interesting thing about the assembly — which observations * belong to the platform and which to the run as a whole — is the thing that would drift if it * were written twice. */ export declare function toVerifyPlatformRun(platform: VerifyPlatform, result: VerifyResult, observations: Pick, retriedWithSoftwareGl: boolean, paths: { screenshotPath: string; consolePath: string; }, fast: boolean): VerifyPlatformRun; /** * One verdict, and one set of top-level fields, from however many platforms the run covered. * * The prefixing rule is what keeps a `both` run readable. A finding EVERY platform produced is * one problem, not two — a missing `Loaded world.json` is the same missing file whichever * viewport asked for it — so it is reported once, unprefixed. A finding only some platforms * produced is prefixed with the platform, because there the platform IS the information. A * single-platform run therefore comes out byte-identical to what it produced before this * existed: every finding is in every run, so nothing is prefixed. * * The top-level observations are the LEAD run's (`runs[0]`), which `resolveVerifyPlatforms` * chooses as the platform the game is actually for. `platforms` is only attached for a genuine * multi-platform run, so a single-platform record grows no keys. */ export declare function mergeVerifyPlatformRuns(runs: VerifyPlatformRun[], mobileParity: MobileParityGap | null | undefined): { result: VerifyResult; extras: VerifyRunExtras; }; export declare function decideVerifyOutcome(result: VerifyResult, fingerprint: string, at: string, extras?: VerifyRunExtras): { record: VerifyRunRecord; shouldThrow: boolean; }; export interface VerifyReporter { write: (artifactDir: string, record: VerifyRunRecord) => void; info: (line: string) => void; error: (line: string) => void; } /** * Record the run, report it, and fail the command if it did not pass. * * The record is written BEFORE the throw and regardless of the verdict, because publish * distinguishes "verify failed" from "verify never ran" and those have different remedies. * That ordering lives here, behind injectable seams, precisely so a test can prove the record * survives a failing run — inside `run()` it was observable only by spawning vite and a browser, * which is why it went unguarded. */ export declare function recordAndGateVerify(artifactDir: string, outcome: { record: VerifyRunRecord; shouldThrow: boolean; }, /** * Every file this run actually wrote, in the order to list them. A list rather than a * console/screenshot pair because a run can now cover two platforms and write four files, and * because a `--fast` run captures no screenshot at all — naming a file that does not exist * (or worse, a stale one from an earlier run) is exactly what this avoids. */ artifacts: string[], reporter: VerifyReporter): void;