Skip to content

Make CI green and unblock the 0.1.9 release#4

Merged
newjordan merged 20 commits into
mainfrom
fix/ci-green-0.1.9
Jul 13, 2026
Merged

Make CI green and unblock the 0.1.9 release#4
newjordan merged 20 commits into
mainfrom
fix/ci-green-0.1.9

Conversation

@newjordan

Copy link
Copy Markdown
Owner

CI has been red on main since May, and the release pipeline could not publish. This makes both green.

Why the release was blocked

release.yml publishes on a v* tag, but only after a validate job (cargo test --all-features, cargo clippy --all-features -- -D warnings, cargo doc -D warnings, cargo test --doc). That job was failing, so tagging v0.1.9 would have published nothing.

The clippy gate reported 3,975 errors. The cause was policy, not rot: Cargo.toml sets pedantic/nursery to warn (advisory), but CI's -D warnings promoted them to blocking. Two advisory lints were 85% of the total, and both were wrong to "fix":

  • unnecessary_literal_bound (1,971) — wants ProgressStyle::{name,theme,describe} to return &'static str. Those are public trait methods; narrowing them breaks every downstream implementor.
  • suboptimal_flops (1,406) — wants fused mul_add, which changes float rounding and so can change rendered output. Its sibling imprecise_flops was already allowed.

Both are now allowed with justification. clippy::all = deny remains the real bar, and every one of those errors is fixed rather than suppressed.

Real defects fixed (not just lints)

  • Six tracked examples shipped broken. webcam_viewer, webcam_tuner, webcam_selector, render_braille, render_cuda_dojo, generate_watermark had no [[example]] entry, so cargo built them with default features and they failed to compile — inside the published crate.
  • MSRV violation. wireframe.rs used f32::to_radians() in a const, which is not const-stable until 1.85, while rust-version = "1.70". Replaced with PI / 18.0 (bitwise-identical f32).
  • A wrong doc. ImageRenderer::ambient_seed told callers to "pass None" — but it takes u64, and the unpin_ambient_seed it linked does not exist.
  • Three tests that could never pass in CI. They loaded ~31 MB of gitignored fixtures, so they only worked on a machine that already had the files. Now self-contained.
  • An orphaned test in the tarball (tests/visual_regression.rs without its excluded tests/visual/ module).

Rendering is provably unchanged

All 664 styles hashed across 6 grid sizes × 21 progress × 17 time values (~1.4M frames), before vs after: byte-for-byte identical.

Known remaining

Cargo Deny / Security Audit may still fail on core2 v0.4.0, which is yanked and arrives transitively via image → ravif → rav1e → bitstream-io. The requirement is ^0.4 and the only published 0.4.x is the yanked one — there is nothing to move to. Not a release gate. MSRV Check (cargo check --all-features on 1.70) is untested locally.

Also note: ci.yml uses branches: ["*"], which does not match branch names containing / — so push-CI never runs on feature/...-style branches.

🤖 Generated with Claude Code

Frosty and others added 19 commits July 13, 2026 13:35
Packaging:
- Six tracked examples (webcam_viewer, webcam_tuner, webcam_selector,
  render_braille, render_cuda_dojo, generate_watermark) had no [[example]]
  entry, so cargo built them with default features and they failed to
  compile. They ship inside the crate, so `cargo build --examples` was
  broken for consumers. Declare their required-features.
- Gate tests/temporal_coherence_tests.rs on the `image` feature; it was the
  sole reason bare `cargo test` (what CI runs) failed to compile.
- Exclude tests/visual_regression.rs from the package. tests/visual/ is
  already excluded, so the tarball shipped a test whose `mod visual;` could
  not resolve.

MSRV:
- wireframe.rs used `10.0_f32.to_radians()` in a const, but f32::to_radians
  is not const-stable until 1.85 while rust-version is 1.70. Replace with
  PI / 18.0 (bitwise-identical f32, no behavior change).

Lint policy:
- CI runs `cargo clippy --all-features -- -D warnings`, which promoted the
  advisory pedantic/nursery lints to blocking: 3,975 errors. Curate an
  allow-list so `clippy::all = deny` remains the real bar. Notably
  unnecessary_literal_bound (1,971) cannot be fixed without a breaking
  change to the public ProgressStyle trait, and suboptimal_flops (1,406)
  would fuse multiply-adds, altering float rounding and rendered output.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mechanical, semantics-preserving rewrites clippy marked MachineApplicable:
unnecessary_min_or_max, bool_to_int_with_if, map_unwrap_or, int_plus_one,
range_plus_one, collapsible_if, redundant_clone, and similar.

Full suite (726 lib tests + visual regression) passes unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vetted before applying: 48 sites are integer (usize) with correctly-ordered
literal bounds, so clamp() cannot panic and NaN is not representable. The 2
float sites (floweroflife line_frac/arm_frac) clamp to literal 0.0..1.0 and
their input derives from ctx.eased = ease(kind, progress), which is finite --
so the max().min() vs clamp() NaN divergence is unreachable.

Clippy marks these MaybeIncorrect precisely because of that NaN case; it does
not apply here. Full suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deletes the vestigial 'outer: for pass in 0..2 loop in inject_electron. The
body always terminated on the first iteration, so the TAIL branch was dead --
correctly so, since step() implements WireWorld (HEAD -> TAIL -> CONDUCTOR)
and the automaton produces tails itself. The injector only ever needs to
place a HEAD.

Verified byte-identical rendering: all 14 cellular styles x 21 progress x 17
time x 4 grid sizes hash the same before and after.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI runs RUSTDOCFLAGS="-D warnings" cargo doc, so these blocked the release.

- medieval.rs: unnecessary parens around a function argument.
- temporal.rs: [`process_dots`] is an associated item; needs Self:: to resolve.
- image/mod.rs: ambient_seed's doc was not merely a broken link, it was wrong.
  It told callers to "pass None", but the method takes u64, not Option, and the
  unpin_ambient_seed function it linked does not exist anywhere in the crate.
  Replaced with the actual behavior: leave the seed unset for clock seeding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…theory

- needless_range_loop / explicit_counter_loop: real iterator rewrites; every
  rewritten bound was checked so no OOB-panic path became a silent truncation.
- type_complexity: private MarkedTri alias in penrose.
- too_many_arguments: bundle the five camera params into a private View struct
  in platonic and topology, using field-init shorthand so the same-typed ax/ay
  and cx/cy cannot be silently transposed.
- approx_constant: platonic CUBOCTA_SCALE now uses f32::consts::SQRT_2.
  NOTE: this is a real 1-ULP change, not a no-op -- the old literal 1.414_213_6
  was one ULP above SQRT_2. Verified output-neutral over 96M coordinate
  comparisons and a full render sweep.

Rendering verified byte-identical: ~30k frames (goldenratio/platonic/topology)
and 9,702 bars (penrose/numbertheory) hash the same before and after.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- needless_range_loop: iterate the fixed-size col_xs/row_ys arrays directly;
  buffer.iter_mut().enumerate() in the raytracer (buffer is built as
  vec![vec![0.0; width]; height], so the index sequence is identical).
- too_many_arguments on render_edges_with_orientation: narrowly-scoped allow.
  It is a pub fn re-exported from raytracer/mod.rs, so folding the params into
  a struct would be a breaking public API change.
- unnecessary_cast in geometry: n_max/n_total are only ever used as f32, so
  declare them f32 and drop the casts. Clippy's own suggestion here is broken
  (it retypes the binding but leaves the integer literal -> E0308) and was not
  applied. 400 and 361 are exactly representable in f32, so bit-identical.
  The separate n_total = 180 (cardioid) is genuinely usize and was left alone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
d22cb8f used `git add -A ... examples/`, which pulled in four uncommitted
scratch examples (mikey, raphe, truck, gif_to_pack) and six loose images under
src/image/. None of them were ever meant to be in the repo: the examples do not
compile (they reference dotmax::raytracer/image without required-features) and
examples/ ships inside the published crate.

Removed from the index only -- the files stay on disk, untracked, exactly as
they were before this session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lle, obj_loader

- needless_range_loop: iterator rewrites; each container's length was proven
  equal to the old range bound before rewriting.
- fruits: collapsed an if/else whose branches were identical ((ry+ny)/2 both ways).
- obj_loader: strip_prefix instead of starts_with + manual slice.
- braille: fixed a broken intra-doc link ([0,1] was parsed as a link) that was
  failing the RUSTDOCFLAGS=-D warnings doc gate.

Known deviation: raytracer/braille's .take().skip() rewrite silently visits
fewer cells on a RAGGED buffer where indexing would previously have panicked.
width is derived from buffer[0].len(), so every in-repo caller passes a
rectangular buffer and output is bit-identical there.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- webcam: manual_let_else + match_wildcard_for_single_variants collapsed into
  one let-else (ffmpeg Context has exactly two variants, so the old _ arm and
  the new else-branch cover the same case); two needless_continue removed
  (both were the final statement on their path, so removal is a no-op).
- dotmax_braille: explicit_iter_loop.
- progress/mod: doc-only paragraph split for too_long_first_doc_paragraph.
  No code changed in the public progress API.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI runs clippy with -D warnings, so these pedantic/nursery findings were
blocking the release gate.

- no_effect_underscore_binding: three dead let bindings with pure RHS, deleted.
- comparison_chain: if/else-if ladders -> match on Ordering (tech, gameboy).
- branches_sharing_code: hoisted the shared draw call in lasers and medieval.
  Draw order is preserved in both -- each branch already performed the shared
  draw first.
- gadgets: inverted an if-not-else; NaN behavior unchanged since Range::contains
  returns false either way. Merged a match arm the wildcard already covered.
- glitch: bool_to_int_with_if -> i32::from.
- cosmos: needless_for_each -> for loop, same order, same in-place swap.

Two narrowly-scoped allows, both deliberate:
- chaos: #[inline(always)] on a per-dot hash is a real perf choice.
- food: a while-loop on a float that provably runs at most twice (angle comes
  from atan2). A closed-form rewrite would change float rounding and therefore
  the rasterized wedge boundary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Last missing_errors_doc site. The two real bail-outs are an unopenable file
and an OBJ containing no geometry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restores 'cargo fmt --check' (the CI Rustfmt job) to green. Whitespace only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clears the last of ci.yml's 'cargo clippy --all-targets --all-features -D warnings'.

- examples: doc_lazy_continuation, needless_range_loop (real iterator rewrites),
  format_push_string -> write!/writeln!, match_same_arms, verbose_bit_mask,
  manual_rotate, bool_comparison, unchecked_time_subtraction -> saturating_sub.
- benches: deprecated criterion::black_box -> std::hint::black_box; removed
  redundant clones in setup only (nothing inside b.iter changed, so no benchmark
  measures anything different).
- prelude tests: six narrowly-scoped allows. The  bindings and the
  Result-returning helpers ARE the assertions -- they prove the re-exported names
  resolve and type-check. Having no runtime effect is the point of the test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
They were swept back in by another over-broad 'git add -A examples/'. Adding
them to .gitignore so a future bulk add cannot pull them into the crate.
Files remain on disk, untracked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- easing tests: float_cmp allowed at the test module with a reason. Exact float
  equality is the property under test (curves must hit 0.0/1.0 exactly, lerp must
  be exact at the midpoint) and every compared value is exactly representable, so
  an epsilon comparison would weaken the assertions, not fix them.
- quick.rs test: name the Animated variant instead of a wildcard, so a future
  MediaContent variant cannot silently fall into the panic arm.
- image_rendering_tests: #[ignore] now carries its reason string.
- property_tests: dropped a redundant clone; visual_regression / media_integration:
  dropped unused imports.
- tests/visual/mod.rs: allow(dead_code) with a reason -- the baseline helpers are
  UPDATE_BASELINES=1 maintenance tooling, deliberately not called by the suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test_extreme_wide/_tall_aspect_ratio and test_very_large_square_image loaded
viper_ultra_wide.png, viper_ultra_tall.png and viper_4k.png -- ~31 MB of fixtures
that are gitignored (.gitignore:51-54). So they only passed on a machine that
already had the files. On a fresh clone, and in CI under 'cargo test
--all-features' (which is exactly what release.yml runs), they failed on a
missing file. They have never actually run in CI.

Synthesize the pixels in memory instead: same dimensions, same pipeline, no
assets. The per-pixel pattern varies so auto_threshold sees a real distribution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
normalize_intensity must clamp to exactly 0.0/1.0 (including NaN and the
infinities) and be the identity in range -- exact equality IS the assertion.
Only visible under --all-targets, which builds the test cfg; the release gate
does not.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scheme_builder, animation::timing and raytracer::lighting tests assert exact
stored/reset values (a 0.5 colour stop, 0.0 fps after reset, exactly-zero
diffuse shading for a perpendicular light). Exact equality is the assertion.
Only visible under --all-targets, which builds the test cfg.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dotmax Ready Ready Preview, Comment Jul 13, 2026 9:01pm

Request Review

zone_stream_canvas_1/2/3.rs were exact copies of zone_stream.rs (same md5,
3,459 lines each). They carried no unique content, shipped inside the published
crate, and accounted for three quarters of the example lint surface.

-10,377 lines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@newjordan
newjordan merged commit 23c8e52 into main Jul 13, 2026
3 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant