diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a03a187..ca04790 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ on: env: CARGO_TERM_COLOR: always # Turn adjustable rustc lint warnings into hard errors for local crates across - # the build/test/docs compile lanes (respected as of Rust 1.97; MSRV is 1.97.0). + # the build/test/docs compile lanes (respected as of Rust 1.97; MSRV is 1.97.1). # Complements clippy's `-D warnings` and the workspace deny lint tables. CARGO_BUILD_WARNINGS: deny diff --git a/Cargo.toml b/Cargo.toml index 0135e80..5de60e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ members = [ [workspace.package] version = "0.1.0" edition = "2024" -rust-version = "1.97.0" +rust-version = "1.97.1" authors = ["Marat Bukharov "] license = "MIT OR Apache-2.0" diff --git a/README.md b/README.md index 3075680..15f9306 100644 --- a/README.md +++ b/README.md @@ -88,10 +88,10 @@ pipeline, oracle, feature extraction, policy) are still `todo!()`. See the ```sh cargo build # whole workspace cargo run -p gp-game # run the graphite-gp binary (scaffold banner) -cargo test # 97 gp-core + 2 gp-gen tests green (supercover + corridor-graph + Size/Rect + overflow-safety + typed legal_mask + track-artifact contract + sim step + lap counter + crash rule + collisions + seeded RNG) +cargo test # 103 workspace tests green (97 gp-core; 2 gp-gen; 2 gp-render: tessellation smoke + golden guard; 2 doc-tests) ``` -MSRV: **Rust 1.97.0**. CI (GitHub Actions, `ubuntu-latest`) runs format, build, +MSRV: **Rust 1.97.1**. CI (GitHub Actions, `ubuntu-latest`) runs format, build, test, clippy (`-D warnings`), and docs on every push/PR to `main`, plus an advisory Miri lane; the workspace lint policy (`clippy::pedantic`/`nursery` = `deny`) lives in the root `Cargo.toml` + `clippy.toml` (see diff --git a/ai-docs/context.md b/ai-docs/context.md index c0a98f1..fbe0952 100644 --- a/ai-docs/context.md +++ b/ai-docs/context.md @@ -35,7 +35,7 @@ Dependency edges: `gen · render · ai → core`; `game → all`; `core` depends - Code: **scaffold + geom physics primitives** — module structure, `TrackArtifact` type, and stub APIs in place. `crates/core/src/geom/` (split into `mod.rs` + a private `graph.rs`) implements the exact integer `supercover` predicate (full §3 C4 test table) plus the corridor-graph helpers: 4-conn `flood_fill` / `component_count`, `bounded_complement_components` (the §2 Ф4 infield-hole test), in-`D` geodesic BFS (`CorridorScratch::geodesic_bfs` reusable-scratch visitor + eager `geodesic_layers`), and `walls_from_boundary` (Ф7 dual edges). The box/index math is factored into `Size { width, height }` (unsigned) + `Rect { origin, size }` value types; `Corridor` is `{ rect, cells }` with **unsigned** dimensions — negative dims are unrepresentable, so `Corridor::new` needs no `assert!` and **gp-core has zero production panics** (`Rect::index` is total via `checked_sub` + `usize::try_from`). Every integer op is now **overflow- and signedness-safe by construction** (issue #48): `Point::neighbors4` saturates, `legal_move` / `walls_from_boundary` use `checked_add`, and each deliberately-retained bounded raw op carries a documented domain bound + covering test. `Wall` is `{ cell, side: Side }` (4-way outward `Side`). `legal_mask` returns a typed `enumflags2::BitFlags` set (re-exported from `gp-core::sim` so consumers need no direct `enumflags2` dep) rather than a positional `[bool; 5]` — issue #51. The **`TrackArtifact` contract is now finalized** (issue #6): `SField` distance/gradient/tangent accessors (`t̂ = normalize(∇s)`, gate cells → `race_dir`, gradient barred at the gate cut), `StartGrid` (distinct `v=(0,0)` positions, front-to-back along `−race_dir`), the `TimingGate` half-grid segment on `StartFinish` (`separates` barrier + explicit `forward: Side`), and `Centerline::at` arc-length sampling (wrap-around + total, no panic) — contract types + read accessors on hand-filled fixtures; the block-1 generator that populates them stays `todo!`. `sim::step` — the pure accelerate-then-advance state advance (`(vx',vy') = v + accel`, then `pos += (vx',vy')`) — is implemented as an infallible `const fn` on the assumed-legal domain (no legality check; that stays `legal_move`'s sole job), joining the already-live `legal_move` / `legal_mask` path (issue #7). `LapCounter::register_move` — the signed **half-open S/F crossing test** over the half-grid timing gate (`+1` forward / `−1` reverse, ≤1 event/move via a doubled integer perpendicular coordinate whose odd `GATE_LINE` value is unreachable by real `Point`s), with the `legal_move`-first valid-finish conjunction expressed at the call site — is implemented (issue #8). `sim::resolve_crash` — the finalized **quench-with-scrub crash rule** (a search dead-end where `legal_mask` is empty): respawn at the last swept cell still in `D` (a projection-ordered `supercover` walk, no second geometry path), zero the into-wall velocity component and damp the along-wall component to `⌊t/2⌋` (a concave corner zeros both), plus a one-tick forced-`Coast` **scrub** marker and a whole-vector-halving **fail-safe** (guarded by an `L∈D` termination check; inherits `supercover`'s bounded-chord precondition) that never yields a penalty-free controlled `v=0` — is implemented as a pure `resolve_crash(d, s) -> CrashOutcome` whose `action_mask` / (total) `consume_scrub` make the scrub tick unit-testable inside `gp-core` (issue #9). `sim::resolve_collisions` — the **same-final-cell collision resolver** (issues #10 + #49): cars ending a turn on the same cell are bucketed, a seeded `rand_chacha` ChaCha8 RNG (canonical `sort_unstable_by_key(|g| g[0])` order → `groups.shuffle` → per-group shuffle → `u32` equidistant tie-draw, cross-arch-reproducible) picks the winner + displacement order, and each loser teleports to the nearest free cell via the in-`D` geodesic BFS (velocity retained, no supercover/lap-counter touch, single occupied-after-each pass). Per a **product-owner amendment (2026-07-16)** the predicate is same-final-cell **only** — the swap/pass-through detector (`docs/design.md` [D1]/[N2]) was dropped, so swaps / mid-segment / orthogonal crossings ending on **distinct** cells are allowed, and the two canonical docs were corrected to match. The tie-draw is total (`u32::try_from(..).unwrap_or(u32::MAX)`) so **gp-core keeps zero production panics**. A follow-up **PR #68 review amendment** changed `resolve_collisions` to take a **caller-owned physics RNG handle** (`rng: &mut ChaCha8Rng`, drawn from but never re-seeded) rather than a per-call `seed: u64`, establishing a **per-domain seeded-RNG architecture** — one long-lived stream each for physics / track generation / AI learning / AI inference (the AI streams and the block-3b game-loop stream ownership are deferred). Issue #49 also adds the `rand` + `rand_chacha` (`0.10`, `default-features = false` ⇒ no `getrandom`/OS entropy) stack to **both** `gp-core` and `gp-gen`, with a seeded `GenParams::rng() -> ChaCha8Rng` (gp-gen's generator stochastic usage stays deferred — #49 remains open for that remainder). **97 gp-core + 2 gp-gen unit tests.** The `gen` / `render` / `ai` algorithms are still `todo!()` (marked `TODO()`). Whole workspace builds clean. - **Visual base:** the Claude Design "Graphite GP Design System" is imported to [`docs/design-system/`](../docs/design-system/IMPORT.md) and adopted as the canonical visual language; render target is a **native Rust GUI** (design tokens/components are a spec to port, not runnable web code). The backend is **eframe/egui 0.35** (#11) — chosen because its widget layer makes #13–#16 token-styling tasks rather than from-scratch toolkit work; rationale + rejected alternatives in [`key-decisions.md`](key-decisions.md). - **Golden-image testing (block 2, from #11):** `gp-render` ships a committed golden PNG (`crates/render/tests/snapshots/placeholder.png` — the repo's **first binary asset**, plain git, no LFS) rendered offscreen through `egui_kittest`'s wgpu/Vulkan harness. Three mechanisms guard it, each catching what the others cannot: **exact compare** (`threshold(0.0)` + `failed_pixel_count_threshold(0)` — both must be overridden; the `0.6` default silently permits colour distance) catches *regressions*; **AC9 pixel probes** catch *degeneracy*; and the **`image-check` subagent** (spawned by `code-writer` at mint/regen only, **never CI**) catches *a golden that was wrong from birth*. Exactness is bit-exact **in flat regions only** — `egui_kittest` hardcodes dify's `detect_anti_aliased_pixels`, so AA-classified edge pixels differ silently with no diff artifact; accepted, revisit when dx12/metal lanes join the CI matrix. Adapter is asserted `DeviceType::Cpu` (llvmpipe), so a bit-exact golden can never be minted against a discrete GPU. -- **CI / tooling:** GitHub Actions CI (`ubuntu-latest`) is in place — `changes`-gated format/build/test/clippy/docs + advisory Miri + `-pass` branch-protection gates, sccache, and a mandatory Linux software-Vulkan env-init in the `test` job (ready for the block-2 wgpu/Vulkan renderer). Plus Dependabot (cargo + github-actions), MSRV bumped to **1.97.0** (`resolver = "3"` retained — virtual workspace), `CARGO_BUILD_WARNINGS=deny`, and a strict workspace lint policy (`clippy::pedantic`/`nursery` = `deny`, `missing_docs`/`broken_intra_doc_links` = `deny`, `clippy::arithmetic_side_effects` = `deny` — issue #48, machine-enforcing integer-overflow safety) in the root `Cargo.toml` + `clippy.toml`, each crate opting in via `[lints] workspace = true`. See [`code-style.md`](code-style.md) § Linter posture. +- **CI / tooling:** GitHub Actions CI (`ubuntu-latest`) is in place — `changes`-gated format/build/test/clippy/docs + advisory Miri + `-pass` branch-protection gates, sccache, and a mandatory Linux software-Vulkan env-init in the `test` job (ready for the block-2 wgpu/Vulkan renderer). Plus Dependabot (cargo + github-actions), MSRV **1.97.1** (`resolver = "3"` retained — virtual workspace), `CARGO_BUILD_WARNINGS=deny`, and a strict workspace lint policy (`clippy::pedantic`/`nursery` = `deny`, `missing_docs`/`broken_intra_doc_links` = `deny`, `clippy::arithmetic_side_effects` = `deny` — issue #48, machine-enforcing integer-overflow safety) in the root `Cargo.toml` + `clippy.toml`, each crate opting in via `[lints] workspace = true`. See [`code-style.md`](code-style.md) § Linter posture. - **Next implementation step: block 3a is COMPLETE** — `supercover` (#4), the corridor-graph helpers (#5), the `TrackArtifact` contract (#6), `sim::step` + `legal_move` / `legal_mask` (#7), `LapCounter::register_move` (#8), `resolve_crash` (#9), and `resolve_collisions` (#10) are all landed; **zero open `block:core` issues**. Per the §6 build order (`3a → (1 ∥ 2) → 4`), the next units are **block 1 (`gp-gen`)** — the Ф1–Ф7 generator pipeline (#24–#34) that populates the `TrackArtifact` contract, starting at Ф1 coarse-block ring (#24); its seeded `GenParams::rng()` already exists (#49, whose remainder is the generator's stochastic usage) — and, in parallel, **block 2 (`gp-render`)**, whose foundational GUI-backend scaffold (#11) is now **landed** — eframe/egui 0.35 picked, window/loop standing in `gp-game`, `render_frame` re-signed to take `&egui::Painter`, and the golden-image harness in place; block 2 continues at the design tokens (#12), which also owns the real font faces (#11 deliberately loads none — see [`key-decisions.md`](key-decisions.md)). - **Not 3a** (a recurring mis-attribution — both are tracked under later blocks): the **passability oracle** is block 1 (`gp-gen` Ф5a #28 / Ф5b #29), and the **tick/turn driver loop** — ordering `legal_move` → `step` → `register_move` → crash/collision resolution, then reading `laps()`/`raw()` for the win check — is **block:game** (#43, the full game loop). 3a ships the pure per-move primitives; sequencing them into a running game is block 3b/game. diff --git a/ai-docs/key-decisions.md b/ai-docs/key-decisions.md index 4a6ce14..c51145d 100644 --- a/ai-docs/key-decisions.md +++ b/ai-docs/key-decisions.md @@ -8,7 +8,7 @@ The finalized design and its full rationale live in [`docs/design.md`](../docs/d **Context.** Issue #11 is the foundational decision unit for Block 2 — `gp-render` ([`docs/design.md`](../docs/design.md) §4 *Рендеринг + UX*, §6 *Архитектура*); the other twelve `block:render` issues (#12–#23) all sit downstream of the pick. The design system to port (`docs/design-system/`) specifies flat vector geometry on a lattice, hairline pencil borders, crisp radii, and no photographic imagery. The downstream units cover **13 components** (#13–#16) across **4 screens** (#19–#22). -**Decision.** **eframe/egui 0.35** — `egui` is the draw layer, `eframe` the window/loop shell. Verified live 2026-07-16: `egui` and `eframe` `max_stable_version` = **0.35.0**; MSRV 1.92 ≤ the workspace's `rust-version = 1.97.0`; eframe's **default** renderer is wgpu (glow is opt-in), which matches the CI `WGPU_BACKEND=vulkan` env as-is. +**Decision.** **eframe/egui 0.35** — `egui` is the draw layer, `eframe` the window/loop shell. Verified live 2026-07-16: `egui` and `eframe` `max_stable_version` = **0.35.0**; MSRV 1.92 ≤ the workspace's `rust-version = 1.97.1`; eframe's **default** renderer is wgpu (glow is opt-in), which matches the CI `WGPU_BACKEND=vulkan` env as-is. **(a) Why it fits the aesthetic.** egui's `Painter` draws the design system's vocabulary directly — flat fills, strokes, and lines on a lattice — with no photographic or raster-asset pipeline to fight. Verified against `egui-0.35.0/src/painter.rs`: `rect_filled` (:397) and `rect_stroke` (:406) give crisp shapes and radii; `line_segment` (:318) and `hline` (:332) give hairline strokes; `hline` + `circle_filled` (:356) give the graph-paper ruling + dot motif. diff --git a/ai-docs/learnings.md b/ai-docs/learnings.md index 80cea44..86b11d6 100644 --- a/ai-docs/learnings.md +++ b/ai-docs/learnings.md @@ -81,3 +81,9 @@ Append-only correction/validation log — the feed for `/improve`. See AGENTS.md **Rule:** When adding a test that touches **FFI, a GPU, `dlopen`, or any foreign function** (wgpu / vulkan / a `-sys` crate / `libloading`), gate it with `#[cfg_attr(miri, ignore = "")]` **in the same commit**. Miri aborts the `--workspace` run at the first unsupported op and cargo's fail-fast drops every phase queued behind it, so the blast radius is the offending crate's whole binary plus the later phases (doc-tests especially) — and any crate whose unittest binary happens to be scheduled after it. **Measure the blast radius from the log before claiming it**; crates scheduled earlier complete normally, and asserting otherwise puts a mechanism that does not exist into the feed `/improve` escalates into AGENTS.md. Prefer the **per-test attribute** over a crate-level `--exclude` in `ci.yml`: it keeps that crate's non-FFI tests under Miri (`tessellation_smoke` really does run), states the reason at the site where a future reader hits it, and prints "ignored, " in the run output instead of silently omitting the test. Verify with the *exact* CI invocation (`MIRIFLAGS=-Zmiri-tree-borrows cargo +nightly miri test --workspace`) rather than a narrower `-p` run — `rustup component add miri --toolchain nightly` is a one-time install. **An `continue-on-error: true` job failing is still a regression**: it does not block merge, so the only thing standing between it and permanent decay is noticing. Check advisory checks after any PR that adds a new dependency class. **Kind:** correction **Escalated?** no + +### 2026-07-16 — documentation — fetched the issue when the user linked a COMMENT; then acknowledged two verbal corrections without propagating either into the artifact +**What happened:** The user reported the 1.97.0 miscompile with a **precise anchor**: `https://github.com/rust-lang/rust/issues/159035#issuecomment-4976844186`. I ran `WebFetch` on the issue URL **with the anchor stripped**. The tool summarised the issue **body** — accurate on 2026-07-09, listing "1.98.0 beta (affected), 1.99.0 nightly (affected)" and stating no fix release was known — and I wrote that into `README.md` + `ai-docs/context.md` as present-tense fact ("an LLVM bug also present in 1.98 beta / 1.99 nightly"). **The comment the user linked contained the refutation**: *"The beta backport was done in #159072, and the stable backport was done in #159288. The 1.97.1 release is on its way. Thus, closing this issue as resolved."* The user then said **"nightly already patched"** and **"1.98 stable is not released yet"** — I replied "consistent with your claim" and "fair", **and edited nothing**: the README kept asserting the opposite for two more turns until `self-review` caught it as a `major`. Two distinct failures: (a) fetching the *container* when the user cited an *anchor* — their link was precise and I widened it into a stale summary; (b) treating a user correction as **conversationally settled** rather than as a **defect in the work product**. (b) is the worse one: they corrected me directly, twice, and the artifact still contradicted them. +**Rule:** **When a user cites a URL with a `#fragment`, fetch THAT anchor and read it — the fragment is the citation, the page is merely where it lives.** A user who links a specific comment has usually already found the answer and is handing it to you; re-deriving it from the page body substitutes stale context for their current evidence. Corollary, and the sharper half: **a verbal acknowledgement is not a fix.** When a user corrects a fact — especially mid-flow, especially when you've already written that fact into a file — the correction is a **work item**, not a conversational beat: `grep` the artifact for the wrong claim and edit it in the same turn you say "you're right". Tell: any reply containing "fair", "good point", "consistent with", or "that closes it" that is **not accompanied by an Edit** to whatever asserts the now-refuted thing. Issue *bodies* are frozen at filing time while *comments* carry the resolution — for any bug-tracker citation, check the closing comment and the issue state before rendering the body's claims as current. Same family as [[2026-07-16 — search — find/ls proves on-disk presence, NOT git-tracked status]] and [[2026-07-16 — search — git status is BLIND to ignored files]]: asserting from a source that cannot see the thing asked about — here the source was stale rather than blind, and the live one was in the user's own link. +**Kind:** correction +**Escalated?** no