Changelog0.3.0

Added

Added in fragcap 0.3.0.

Live capture (run, tap, and analyzer capture) now detects when it is not running with Administrator rights and refuses before touching the capture driver, explaining how to re-launch elevated, instead of failing later with a lower-level driver error. Offline and read-only commands still run without elevation.

A master JSON Schema (Draft 2020-12) now governs every machine-readable targeting and attribution artifact under one versioned vocabulary: a profile, a hand-authored package, a heuristic hint, and a hint-database export share a single schema discriminated by a top-level kind, each carrying a structured fidelity tier (authored, verified, heuristic-unverified, observed) and, for the loose forms, provenance. The schema is embedded in the binary as the single source of truth, published under docs/schema/, and rendered on the documentation site. A new fragcap schema validate <file> validates any JSON file against it and reports every structural violation in one pass, distinguishing a JSON syntax error from a schema violation; fragcap schema print emits the embedded schema. Structural validation (types, required keys, enums, unknown-key refusal, the discriminators) lives in the schema; the semantic invariants of profile loading (acyclic ancestry, a single terminal stage, role reachability, no ambiguous image match) remain the profile-load path's responsibility, and the seam is documented (issue #75).

A target resolution cascade now decides what to capture for a game (issue #77, the resolver core). A TargetResolver consults an ordered set of providers of varying trust and returns the highest-precedence available answer, a Target, stamped with a targeting fidelity tier (authored > verified > heuristic-unverified > observed) and a provenance. Two providers carry data: the profile provider wraps the section 15.3 lookup and stamps its answer with the profile's own declared fidelity, and the runtime-observation provider matches a live process by identity (an exe image name plus optional path anchors) and stamps an observed answer, using only the process snapshot and opening no process handle. Three providers (hint database, engine rule, platform walker) are registered and decline until their own slices fill them, so adding their data is additive. The precedence order is total and imposed rather than incidental, proven by a permutation test. The in-memory Profile now retains and exposes the kind, fidelity, provenance, and notes it declares, which it previously discarded after validation. The run command resolves through the cascade and captures byte-identically. This targeting fidelity is separate from the attribution fidelity (live/retained/none) and neither is derived from the other. No dependency is added and nothing is added to fragcap-core.

A watch subcommand captures a target by identity, launch-agnostic (issue #77, watch mode). It arms the process watcher and sinks and captures the first process matching an executable name plus an optional path anchor (--exe/--path/--path-regex), however and wherever it was started, with no authored profile and no managed launch, which is what makes a modded install launched from a mod manager, a standalone title, and every non-storefront game capturable at all. Where tap matches an executable name only, watch adds the path anchor that distinguishes a modded install and a --wait acquisition timeout. Watch mode also attaches to a target already running when it starts: the process watcher's query-only startup snapshot is now folded into the capture session at arm (a new CaptureSession::apply_snapshot), so an already-running process is acquired without a later start event, and the offline process-script grammar gains a snapshot line so this is tier-1 testable. The S027 runtime-observation provider resolves the identity over the snapshot to report the honest observed answer naming the already-running process, while the session remains the single acquisition authority. A watch that never sees its target gives up at the acquisition timeout with the existing StopReason::AcquisitionTimeout and its discard accounting surfaced (P-4). Watch mode's output is byte-identical to an equivalent single-stage profile capture. The master specification (sections 7.1 and 10.5) now names watch mode as the default launch-agnostic path, and the glossary gains a watch mode entry. No dependency is added.

The target resolution cascade (issue #77) gained its first general-purpose provider: an engine rule that recognizes a game's socket-holding client from its game engine's documented on-disk install layout, with no per-title data. It fills the engine-rule provider slice S027 registered as a declining stub. The layout signatures track the open SteamDatabase/FileDetectionRuleSets ruleset (the source behind SteamDB's engine attribution, filename and path based): Unreal by a *-Win64-Shipping.exe under a Binaries\Win64 directory (the client a root launcher stub relaunches), Unity by a *_Data directory beside a UnityPlayer.dll or a GameAssembly.dll (IL2CPP builds), Godot by a *.pck archive beside a like-named executable, and Ren'Py by a renpy directory and .rpa archives. The provider reads the filesystem only: it opens no process handle, reads no process memory, launches nothing, and ignores post-run artifacts such as per-user AppData, which do not exist before the first launch. Every answer is stamped heuristic-unverified with provenance engine-rule, never a higher tier, because a documented on-disk convention is a good guess rather than an authored fact. When a rule recognizes a layout but matches more than one candidate client, the provider declines rather than pick one arbitrarily and records the ambiguity; when a filesystem error leaves a scan incomplete, it declines with the unreadable path recorded rather than resolving from a partial view, so an inaccessible install is distinguishable from an unrecognized engine. In both cases the cascade falls through to runtime observation, which disambiguates at runtime. A resolution request now carries an optional install directory (which the S030 platform walker will populate unchanged), and a resolved target gained an engine-rule origin naming the client executable and the match rules the pipeline binds it by. No dependency is added, nothing is added to fragcap-core, and the provider lives in fragcap-profile beside the rest of the cascade.

The target-resolution cascade (issue #77) gained its platform-walker provider, completing #77: Steam is now one optional provider feeding the shared resolver rather than the spine of targeting. The walker makes a Steam-installed title's install directory available to the resolver so the higher-precedence engine-rule provider (S029) can name the socket-holding client from layout, and, when the engine rule does not recognize the layout, it answers at its own precedence by classifying the install directory's executables into a single client. It reuses fragcap-steam's existing library enumeration and the scaffold classifier predicates, and reads the filesystem and registry only: no process handle, no memory, no network. Every walker answer is stamped heuristic-unverified with provenance steam-library, an honest name for the library walk and install-directory classification it performs. The walker declines rather than guess: it resolves only when exactly one plausible client remains after dropping installers and launcher stubs; zero, several, or an unreadable install is a decline (with the ambiguity or unreadable path recorded), and the cascade falls through to runtime observation, which resolves the game from the live socket-holding process. The provider lives in fragcap-steam (which already depends on fragcap-profile; the reverse is forbidden by the dependency-direction check), implementing the cascade's provider trait; the no-op stub in fragcap-profile is retired and the CLI assembles the resolver with the real walker. A resolved target gained a platform-walker origin naming the client and the match rules the pipeline binds it by, and the resolver gained walker ambiguity and unreadable notes. Steam's steam:// managed launch is unchanged and stays a convenience adapter. No dependency is added.

A technology-detection surface (slice S031) reports the technologies present in a game's install directory: its game engine, anti-cheat (EasyAntiCheat, BattlEye, Vanguard, and the rest), SDK, emulator, container, and launcher. It is built on the open SteamDB SteamDatabase/FileDetectionRuleSets ruleset (MIT, (c) 2021 SteamDB), which recognizes technologies from depot file paths alone. The whole rules.ini is vendored verbatim, pinned to upstream commit 243cf741921d2c8fd6b844f83831edf4692cf788, carried with its MIT attribution (THIRD_PARTY_NOTICES.md) and integrity-locked by a recorded SHA-256 over its bytes (rules.lock.json), in the same spirit as skills-lock.json. Because fragcap runs against a real install the operator already has on disk, the whole ruleset applies; the depot-manifest license gate SteamDB itself faces is a catalog-scale concern for titles nobody owns and does not apply here.

Detection reads directory entries and matches the ruleset's path regexes against the relative paths it finds, using file names and relative paths only: it opens no process handle, reads no process memory, reads no file content, launches nothing, and makes no network call (P-1). A detected anti-cheat is surfaced as a user-safety and consent signal, so an operator knows what watches a game before capturing alongside it; fragcap detects it and never interacts with it. Every finding is stamped heuristic-unverified and names the marker path that produced it, as auditable evidence (P-9). The vendored ruleset is authored for a PCRE-style engine and contains constructs the project's RE2-family regex engine cannot compile (atomic groups); each such pattern is skipped, counted, and recorded with the technology it belonged to, never silently dropped, and compiled + skipped == total is asserted over the vendored asset (P-4). An unreadable install subtree is surfaced distinctly from a clean empty scan.

Findings surface two ways. A new fragcap technologies --path <dir> command prints them grouped by category, with a heuristic banner and a note when ruleset patterns were skipped as incompatible. And the Steam profile scaffold now carries the detected set into the target artifact it materializes, as a new multi-category technologies structure added to the master target schema (categories engine, anti_cheat, sdk, framework, emulator, container, runtime, launcher), each finding recording its category, name, marker path, and fidelity. This labels technologies; it does not change which executable the resolver picks as the socket-holding client, and it does not run inside the live capture loop or alter the packet-stream output. The detection engine lives in fragcap-profile beside the engine rule and adds no dependency (the existing regex engine matches, and a hand-rolled SHA-256 locks the asset), so nothing is added to Cargo.lock and the minimum supported toolchain stays green.

run can now capture a target the resolution cascade resolves without an authored profile (slice S032). Slices S027 through S030 built a cascade that resolves a socket-holding client from install layout (an engine rule, a Steam platform walker) or runtime observation, but run refused any resolved target that had no backing profile, so those providers resolved targets nothing could then capture. This slice closes that gap.

run takes exactly one of three mutually-exclusive target inputs (a clap group enforces it, so supplying none or more than one is a usage error before any resolution): --profile <ref> (unchanged, byte-identical), --install-dir <path> (resolve the cascade over a given install directory), and --steam <app_id> (resolve the app id to its install directory through the local Steam library lookup, then take the same path). For a resolved target that has no profile, run reads the resolved target's match predicates (its image name plus any path anchors), synthesizes a one-stage profile from them, and captures it through the same launch-agnostic engine watch uses.

The synthesized identity is built through the same validating profile construction an authored profile takes, and is stamped heuristic-unverified, never authored: it was resolved by an install-layout heuristic or runtime observation, not typed by an operator (P-9). Its game identity is a generic placeholder, plus the Steam app id carried as a fact on game.app_id when the input was --steam. The capture reaches the target the same passive way as every capture: the session arms, folds a query-only startup snapshot to attach to an already-running target, and attributes from outside the process; no process handle is opened and no process memory is read (P-1). An install location the cascade cannot resolve to a single client, an unreadable install tree, or a Steam app id that is not installed each produce a surfaced command failure (exit 1) that names the reason and captures nothing (P-4), distinguishable from a game that ran but sent no traffic; a command-line misuse is a usage error (exit 2). The --profile path is unchanged and its output byte-identical to the existing goldens. A small pure accessor, Target::identity, returns the resolved identity for a non-profile target. No dependency is added, nothing is added to fragcap-core, and the whole slice lives in fragcap-cli over contracts the other crates already expose.

The master target schema's loose hint-record subschema (issue #75) was revised so the targets hint database (#78) can emit conformant JSON, per the Steam catalog research #83 absorbed (slice S033). Three optional structures now appear on a hint at the top level and inside each export record: a launch array of a title's Steam launch configurations (each entry an optional os/arch/launch-type/ beta-branch filter, a required non-empty executable, and optional arguments and description), a launcher_mediated boolean marking titles Steam starts through a publisher launcher, and an engine object carrying an optional engine name, a source (pcgamingwiki | exe_heuristic | depot_filename_rules), and a confidence (confirmed | high | medium | low | unknown).

The launch array is carried whole and is never reduced at seeding time to a single game binary: for a launcher-mediated title the invoked entry is a publisher launcher, not the socket holder, and deciding which entry (or descendant) holds the sockets is the resolution cascade's runtime job (#77), not a seeding-time transformation. The engine confidence is a within-field grading of one heuristic guess, deliberately not a rung on the record fidelity ladder, so a low-confidence engine guess never silently moves the record's overall trust (P-9); the engine source is likewise distinct from the record's provenance source. A failed engine lookup leaves the object absent rather than present with a fabricated value.

The change is additive within schema version 1, applied byte-identically to the embedded and published schema copies, so every pre-existing artifact still validates and no version bump is made. The three fields are refused on the strict profile and package variants and on the export envelope's own top level, so the authored capture format stays free of hint-seeding metadata. The hand-rolled variant validator shape-checks the new structures wherever they are permitted, two new diagnostic codes name an out-of-enum engine source or confidence, and the conformance corpus gained fixtures for a full valid hint, an export record carrying the fields, an out-of-enum source, an out-of-enum confidence, a launch entry with no executable, and a strict profile that carries a hint-only field (rejected). No runtime code consumes these fields yet; that is #78. No dependency is added and the minimum supported toolchain stays green.

The targets hint database (issue #78) gained its foundation: a new fragcap-targets crate holding an embedded SQLite store of known game binaries and launch patterns, the three-tier seeding model, and a schema-conformant JSON export (slice S034). The store carries a game's Steam application id, name, and catalog metrics, its launcher_mediated and token_required flags, an optional engine attribution, its launch entries (carried whole, never flattened to a single process name), and per-title technology findings, plus per-tier seed state so a later fetch can resume. The three seeding tiers own their columns independently: the public catalog owns appid and name, the launch metadata owns the launch array and the launcher flag, and the community engine data owns the engine attribution. No seeder runs this slice; there is no network fetching, and the store is populated offline.

The store exports to the export variant of the master target schema: a single envelope of records, one per title, each stamped fidelity heuristic-unverified regardless of engine confidence (P-9), with an unknown engine and an empty launch array both represented by omission. The exporter validates its own output against the embedded schema before returning it, so it can never emit a document the validator rejects. The store cannot hold a row it could not export: SQLite CHECK constraints and the value types refuse an out-of-set engine source or confidence and an empty launch executable, and an engine attribution must carry both a source and a confidence or neither.

A fragcap targets command imports a local JSON seed document into a store and exports a store to schema-conformant JSON, both offline. Import is transactional and idempotent per application id: a duplicate appid within one seed is rejected with no partial store, and an appid already present is replaced wholesale rather than merged into a half-updated row; a malformed seed leaves no store behind. A committed seed fixture (The Elder Scrolls Online as a launcher-mediated title, a title carrying an engine, and a catalog-only title) round-trips through the command to schema-valid JSON.

The crate is exposed through the facade behind an optional targets feature, so a default library build compiles no SQLite engine; the shipped command-line tool enables it. The one new dependency, rusqlite with default-features = false and bundled, adds six packages to Cargo.lock, is MIT or Apache-2.0 across the delta (the bundled SQLite amalgamation is public domain), and keeps the minimum supported toolchain at 1.82, verified by building through it.

The targets hint database gained its first seeder (issue #78, slice S035): the Tier 1 catalog seeder that fills a store's public-catalog columns (application id, name, and popularity metrics) from a catalog source. A CatalogSource trait fixes the shape the seeder reads, so its fetch-parse-gate-merge pipeline is driven in every test by an offline FixtureCatalog over committed data, with no network; the live HttpCatalog (behind a new net feature) is a thin read-only HTTPS adapter that continuous integration compiles but never runs, the same posture as live packet capture.

A corpus gate scopes the written rows to titles that are games and clear a configurable review-count threshold, so the store holds the corpus that matters rather than the whole ~150k app-list universe. Every fetched title is accounted for in a seed summary as written, excluded, a within-run duplicate, or failed, and the counts reconcile (fetched equals written plus excluded plus duplicates plus failed), so a corpus that could not handle something, or a repeated appid that would otherwise overstate the total, can never read as complete (P-4, P-9). A title whose popularity is unknown is excluded rather than admitted on a guess; an entry with a present but wrong-typed field is counted as failed rather than coerced to an absent value; and a single unparsable entry is counted as failed without aborting the run. The offline targets seed command requires exactly one catalog source, so --from together with the live --steam, or neither, is a usage error rather than a silent choice.

The seed is idempotent and resumable: it merges each title by application id through a new merge_catalog that writes only the Tier 1 columns, leaving any launch entries (Tier 2) and engine attribution (Tier 3) a later seeder wrote intact, and it records a resume cursor after each page so an interrupted seed continues rather than restarting. It never prunes: a stored title absent from a run is left as it is. After a seed the store still exports schema-valid JSON, every record heuristic-unverified.

A fragcap targets seed --from <catalog> --db <store> command drives the offline seed and prints the summary; a maintainer builds with --features net to seed from the live catalog with --steam. The one new dependency, http_req with native-tls, is optional behind net, adds 18 MIT/Apache packages to Cargo.lock, and does not touch the minimum supported toolchain, which stays 1.82.

The targets hint database gained its Tier 3 seeder (issue #78, slice S036): the engine seeder that fills a store's engine-attribution columns (engine name, source pcgamingwiki, and a confidence grade) from PCGamingWiki, keyed by Steam application id. An EngineFeed trait fixes the shape the seeder reads, so its fetch-parse-resolve-merge pipeline is driven in every test by an offline FixtureEngineFeed over committed data, with no network; the live HttpEngineFeed (behind the existing net feature) is a thin read-only HTTPS adapter over PCGamingWiki's MediaWiki Cargo query API that continuous integration compiles but never runs, the same posture as the S035 catalog source and live packet capture. No new dependency is taken: the seeder reuses the http_req client S035 chose for the whole seeder arc.

The seeder writes an engine only for a title that resolves to a single unambiguous engine name; a title with no engine, or an ambiguous one (the feed names more than one), is left absent and counted excluded, never guessed (P-9). Every fetched title is accounted for in the reused seed summary as written, excluded, a within-run duplicate, or failed, and the counts reconcile (fetched equals written plus excluded plus duplicates plus failed), so a partial enrichment can never read as complete (P-4, P-9). A present but wrong-typed field, or an out-of-set confidence token, is counted as failed rather than coerced to a default and reported as excluded, so the summary never misattributes why an engine is or is not present; a single unparsable entry does not abort the run. The engine confidence is a within-field grade of one heuristic field, never a fifth fidelity tier: a seeded engine leaves the record heuristic-unverified however confident the field grade.

The seed is idempotent and resumable: it merges each engine by application id through a new merge_engine that writes only the engine columns (source and confidence bound together to satisfy the store's both-or-neither invariant), leaving any catalog data (Tier 1) and launch data (Tier 2) a prior seeder wrote intact, and inserting an engine-only row for an application id the store has not seen. It records a resume cursor under the engine tier after each page so an interrupted seed continues rather than restarting, and it never prunes: a stored title absent from a run is left as it is. After a seed the store still exports schema-valid JSON.

A fragcap targets seed-engine --from <engine-doc> --db <store> command drives the offline seed and prints the summary; a maintainer builds with --features net to seed from PCGamingWiki with --pcgamingwiki. The live flag names its actual source rather than --steam: the tier is keyed by Steam application id but the data is PCGamingWiki's.

The targets hint database is now wired into the live resolution cascade as its precedence-2 provider (issue #78, slice S037), the final wiring step of the targeting redesign. A new HintDatabaseProvider reads a store row for a Steam application id carried on the resolution request and, when that row names a single usable Windows client executable, answers with a heuristic-unverified target keyed on that executable, carrying the row's launcher-mediated flag and engine name as facts. It sits below authored and curated profiles and above the engine rule, the platform walker, and runtime observation, so a title the community has documented resolves without an operator authoring a profile first, while a live observation always overrides it.

The provider never guesses. A sparse catalog-only row, an engine-only row with no launch executable, a request with no application id, a launcher-mediated row (whose launch executable is the publisher launcher rather than the socket-holding client, so resolving it would record the launcher as the game and lose the gameplay traffic), and a row whose Windows launch entries name more than one distinct executable are all declines, so the cascade falls through to the lower providers rather than arming a capture against a launcher or a guessed process (P-4); an ambiguous decline records the application id and candidate count, surfaced by the run error, so a not-resolved outcome can explain itself. Launch entries are first restricted to those applicable to Windows and reduced to the set of distinct executable file names, so one executable repeated across arguments, architectures, and beta branches is one candidate, not an ambiguity. Every answer is stamped heuristic-unverified with provenance hint-db, the same name the database's export projection uses, and it carries no on-disk path because the store knows the executable name but not where a machine installed the title (P-9).

The database is optional and its absence is never an error. A fragcap run --hint-db <path> option, and a FRAGCAP_HINT_DB environment override, supply a database for resolution; a --steam capture then offers the provider the application id while the install root stays available to the lower providers. When no database is supplied, or the path does not exist, or the build excludes the targets feature, precedence 2 is simply empty and resolution is byte-identical to before this slice. A database that is present but cannot be opened (corrupt or a wrong schema version) fails loudly at the boundary where the operator named it, rather than being silently treated as absent. The whole feature is testable offline: the cascade ordering, every decline, and the graceful degradation are proven over an in-memory store with no network and no game.

The concrete provider lives in fragcap-targets, which already depends on fragcap-profile and implements its provider trait, so no dependency is introduced from the resolver's home crate onto the targets database; the no-answer stub the profile crate held at precedence 2 is removed. This mirrors the S030 platform walker, which lives in fragcap-steam for the same reason. No new dependency is taken.

Each copy of fragcap now learns its own Steam games' launch executables locally and privately. When a hint database is configured, a capture run first walks the installed Steam library and, for each installed title, reads that title's launch configuration from the machine's own application-info cache (appcache/appinfo.vdf) into the local store's launch columns, so the hint provider can name a socket-holding client the engine rule and platform walker would miss. A hand-rolled parser reads the binary appinfo format (a different format from the text VDF of libraryfolders.vdf and the .acf manifests), framing each application's section by size so one malformed section is isolated rather than losing the file. Titles already current are skipped by their appinfo change-number, so the first run is slower and later runs are mostly skips, and a user accumulates a growing personal collection over time. Nothing about any user's library is shipped or shared; the distributed database still carries only the public catalog and engine data. The read is passive: no network, no process handle, only a file Steam already wrote. Every considered title lands in exactly one counted outcome and the account is surfaced, so a partial walk cannot read as complete.

fragcap now ships an unsigned Windows MSI installer alongside the portable archive. The installer places the binary per-machine, adds its directory to the system path so fragcap resolves in any new terminal, ships the barebones hint database beside the binary, best-effort excludes its own install directory from Windows Defender (removed on uninstall), and links the npcap download page on completion. It is unsigned by design for this release, so the documentation explains the expected unrecognized-publisher warning and points at the checksum as the integrity check; code signing is tracked separately.

Every release now publishes three downloads, each with its own checksum: the portable archive, the installer, and a barebones targets hint database. The hint database is an empty store that the local launch-data accumulation fills from the user's own machine over time.

The hint database now has a per-user default location, %APPDATA%\fragcap\hint.db, created on first run when no --hint-db option or FRAGCAP_HINT_DB environment variable names one. On first use fragcap seeds it from the database shipped beside the executable when present, and otherwise creates an empty store, so hint resolution and local accumulation work with no configuration for both the installer and the portable archive.

The documentation site gains a License page, a Brand page presenting the brand kit (palette, logos, typography, and downloads, single-sourced from brand/), and a Changelog section generated from CHANGELOG.md with a page per version and category. The home page now shows the current release version, and the footer links to the License and Brand pages beside the Disclaimer.