docs/superpowers/plans/2026-05-27-ws6-prep-blockers-and-pr-foldin.md
-Dwarnings blockers + fold in deferred quality PRs — Implementation PlanFor agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Make the safe crate clippy--Dwarnings-clean and land the tracked-deferred quality PRs (idiom + soundness) with attribution, so the WS6a gate plan can flip the gates on a settled API surface.
Architecture: Two mechanical blocker fixes in raylib-sys/build.rs and raylib/src/core/callbacks.rs, then three small idiom PRs cherry-picked as-is, then a soundness pass that adapts (not blindly cherry-picks) PRs whose 5.x targets were largely rewritten by WS3 — "already covered" is an acceptable per-PR outcome, recorded with reasoning.
Tech Stack: Rust (edition 2024, MSRV 1.85), cargo, cargo clippy, gh CLI (against raylib-rs/raylib-rs for diffs), the WS4 software_renderer render-test harness.
Spec: docs/superpowers/specs/2026-05-27-ws6-platform-cicd-design.md §3 (WS6-prep), §2 (W6-4).
Working model: Branch 6.0-rc. Build/test with the safe crate's curated set. Commits end with Co-Authored-By: Claude Opus 4.7 <[email protected]>; cherry-picked PRs also credit the original author with a second Co-Authored-By trailer (GitHub handle → <handle>@users.noreply.github.com).
| File | Responsibility | Tasks |
|---|---|---|
raylib-sys/build.rs | Rename Platform/PlatformOS acronym variants (clippy blocker) | 1 |
raylib/src/core/callbacks.rs | Remove dead deprecated audio-stream trampoline + method (warning blocker) | 2 |
raylib/src/core/{audio,file}.rs | PR #272 — c"..." literals | 3 |
raylib-sys/src/color.rs, raylib/src/core/{camera,texture,vr}.rs | PR #268 — Into→From | 4 |
raylib/src/core/audio.rs | PR #266 — seal AudioSample | 5 |
raylib/src/core/{models,texture,...}.rs | PR #277 — remove unsound wrapper trait impls (adapt) | 6 |
raylib/src/core/models.rs | Mesh-accessor soundness pass (#257/#118/#256, adapt) | 7 |
docs/superpowers/notes/ws6-prep-complete.md | Completion note + per-PR disposition | 8 |
Reference commands used throughout:
cargo build -p raylibcargo clippy -p raylib --all-targets --features SUPPORT_MODULE_RSHAPES,SUPPORT_MODULE_RTEXTURES,SUPPORT_MODULE_RTEXT,SUPPORT_MODULE_RMODELS,SUPPORT_MODULE_RAUDIO -- -D warnings (the full alias does not exist yet — that lands in WS6a; until then lint the default + module set)..github/workflows/baseline.yml software-render job.build.rs acronym enum variants (clippy upper_case_acronyms)Files:
raylib-sys/build.rs (enum defs at ~606-622; match arms at ~200-201, 291-292, 439, 451, 455, 571-575, 579)clippy::upper_case_acronyms flags Platform::DRM, Platform::RPI, PlatformOS::BSD, PlatformOS::OSX. Rename to Drm, Rpi, Bsd, Osx (the idiomatic fix; build.rs is not public API). Feature names (drm, legacy_rpi) are unaffected — only the Rust enum variants change.
Run: cargo clippy -p raylib-sys 2>&1 | rg "upper_case_acronyms" -n
Expected: warnings naming DRM, RPI, BSD, OSX.
In raylib-sys/build.rs, change:
enum Platform {
Web,
Desktop,
Android,
Drm,
Rpi, // legacy raspberry pi
Memory, // raylib 6.0 windowless software-render platform (PLATFORM=Memory)
}
and
enum PlatformOS {
Windows,
Linux,
Bsd,
Osx,
Unknown,
}
Replace these exact occurrences (use Edit per site; there are eight):
Platform::DRM => conf.define("PLATFORM", "DRM"), → Platform::Drm => conf.define("PLATFORM", "DRM"), (the string "DRM" stays — it's the cmake -DPLATFORM value)
Platform::RPI => conf.define("PLATFORM", "Raspberry Pi"), → Platform::Rpi => ... (string unchanged)
Platform::DRM => "-DPLATFORM_DRM", → Platform::Drm => "-DPLATFORM_DRM",
Platform::RPI => "-DPLATFORM_RPI", → Platform::Rpi => "-DPLATFORM_RPI",
PlatformOS::OSX => { → PlatformOS::Osx => {
} else if platform == Platform::DRM { → ... == Platform::Drm {
} else if platform == Platform::RPI { → ... == Platform::Rpi {
"FreeBSD" => PlatformOS::BSD, / "OpenBSD" => PlatformOS::BSD, / "NetBSD" => PlatformOS::BSD, / "DragonFly" => PlatformOS::BSD, → ... => PlatformOS::Bsd, (four arms)
"Darwin" => PlatformOS::OSX, → "Darwin" => PlatformOS::Osx,
} else if cfg!(feature = "drm") {\n Platform::DRM → Platform::Drm
} else if cfg!(feature = "legacy_rpi") {\n Platform::RPI → Platform::Rpi
} else if matches!(platform, Platform::DRM | Platform::RPI | Platform::Android) { → matches!(platform, Platform::Drm | Platform::Rpi | Platform::Android)
Step 4: Verify the rename compiles and clippy is clean on these
Run: cargo clippy -p raylib-sys 2>&1 | rg "upper_case_acronyms" -n; echo "exit=$?"
Expected: no upper_case_acronyms lines (the rg exit is 1 = no matches, which is what we want).
Run: cargo build -p raylib-sys
Expected: builds (no cannot find variant errors — proves all arms were updated).
git add raylib-sys/build.rs
git commit -m "fix(ws6): rename build.rs Platform acronym variants (clippy upper_case_acronyms)
Co-Authored-By: Claude Opus 4.7 <[email protected]>"
Files:
raylib/src/core/callbacks.rs (remove fn at 130-136; remove method at 353-367)custom_audio_stream_callback (130-136) is #[deprecated] yet invoked at 362 inside the deprecated RaylibHandle::set_audio_stream_callback, producing a self-deprecation warning. The live replacement is the generic set_audio_stream_callback in core/callbacks/audio_stream_callback.rs. Verified callers (raylib-test/src/callbacks.rs, samples/audio_raw_stream_callback.rs) use the free functions, not this deprecated method — removal is safe. (The other deprecated RaylibHandle methods at 309-351 do not warn and are left as-is; full finalization of the callbacks.rs:130 TODO is out of WS6-prep scope.)
Run: cargo build -p raylib --features SUPPORT_MODULE_RAUDIO 2>&1 | rg "custom_audio_stream_callback|deprecated" -n
Expected: a use of deprecated function ... custom_audio_stream_callback warning.
Run: rg -n "\.set_audio_stream_callback\(" --glob '!raylib/src/core/callbacks.rs'
Expected: no hits in raylib-test/samples/showcase for the method form on a handle (the new generic free fn set_audio_stream_callback::<...> is a different symbol and is fine).
Remove these lines from callbacks.rs (including the stale TODO):
//TODO: before any merge find out what the status of the deprecated functions are and how to best finalize removing them
#[deprecated = "use [set_audio_stream_callback](core::callbacks::audio_stream::set_audio_stream_callback) and its trampoline instead."]
extern "C" fn custom_audio_stream_callback(a: *mut c_void, b: u32) {
let audio_stream = audio_stream_callback().unwrap();
let a = unsafe { std::slice::from_raw_parts(a as *mut u8, b as usize) };
audio_stream(a);
}
Remove the set_audio_stream_callback method block (353-367):
/// Audio thread callback to request new data
#[deprecated = "Decoupled from RaylibHandle. Use [set_audio_stream_callback](core::callbacks::set_audio_stream_callback) instead."]
pub fn set_audio_stream_callback(
&'_ mut self,
stream: AudioStream,
cb: fn(&[u8]),
) -> Result<(), SetLogError<'_>> {
if AUDIO_STREAM_CALLBACK.load(Ordering::Acquire) == 0 {
AUDIO_STREAM_CALLBACK.store(cb as _, Ordering::Release);
unsafe { ffi::SetAudioStreamCallback(stream.0, Some(custom_audio_stream_callback)) }
Ok(())
} else {
Err(SetLogError("audio stream"))
}
}
Run: cargo build -p raylib --features SUPPORT_MODULE_RAUDIO 2>&1 | rg "warning|error" -n
Expected: no warnings/errors. If audio_stream_callback() (the accessor) or AUDIO_STREAM_CALLBACK is now unused, confirm whether the live module still uses it (rg -n "AUDIO_STREAM_CALLBACK|fn audio_stream_callback" raylib/src/core/callbacks*); only remove genuinely-dead items, and keep anything the live audio_stream_callback.rs references.
Run: cargo build -p raylib --features SUPPORT_MODULE_RAUDIO
Expected: clean (the targeted deprecation warning is gone).
git add raylib/src/core/callbacks.rs
git commit -m "refactor(ws6): remove dead deprecated custom_audio_stream_callback
The trampoline was #[deprecated] yet invoked internally, tripping clippy
-Dwarnings. The live path is the generic set_audio_stream_callback in
core/callbacks/audio_stream_callback. No in-workspace caller used the
removed RaylibHandle method.
Co-Authored-By: Claude Opus 4.7 <[email protected]>"
c"..." literals (idiom, cherry-pick as-is)Files:
raylib/src/core/audio.rs, raylib/src/core/file.rsReplaces CStr::from_bytes_with_nul(b"...\0").unwrap() (and similar) with C-string literals c"...". Mechanical, no logic change. Author: AmityWilder.
Run: gh pr diff 272 -R raylib-rs/raylib-rs
Note each from_bytes_with_nul/from_bytes_until_nul literal it converts.
For each site the PR identifies, replace the verbose form with the c"..." literal. Example shape:
// before
let s = CStr::from_bytes_with_nul(b"some text\0").unwrap();
// after
let s = c"some text";
The PR targets 5.x; apply the same conversion wherever the equivalent literal exists in the current audio.rs/file.rs. If a site no longer exists, skip it (record in the completion note).
Run: cargo build -p raylib --features SUPPORT_MODULE_RAUDIO && cargo test -p raylib --doc
Expected: builds; doctests pass.
git add raylib/src/core/audio.rs raylib/src/core/file.rs
git commit -m "refactor(ws6): use c\"...\" literals over CStr::from_bytes_with_nul (#272)
Cherry-picked from raylib-rs#272.
Co-Authored-By: AmityWilder <[email protected]>
Co-Authored-By: Claude Opus 4.7 <[email protected]>"
Into<T> for U → From<U> for T (idiom)Files:
raylib-sys/src/color.rs, raylib/src/core/camera.rs, raylib/src/core/texture.rs, raylib/src/core/vr.rsIdiomatic-Rust convention fix: implementing From gives the Into for free; the reverse is discouraged (clippy::from_over_into). Author: AmityWilder.
Run: gh pr diff 268 -R raylib-rs/raylib-rs
impl Into<...> for ... blocksRun: rg -n "impl +Into<" raylib-sys/src/color.rs raylib/src/core/camera.rs raylib/src/core/texture.rs raylib/src/core/vr.rs
FromFor each, rewrite (the WS2 native-type move may have changed the concrete types — match the current ones):
// before
impl Into<ffi::Color> for Color {
fn into(self) -> ffi::Color { ffi::Color { r: self.r, g: self.g, b: self.b, a: self.a } }
}
// after
impl From<Color> for ffi::Color {
fn from(c: Color) -> ffi::Color { ffi::Color { r: c.r, g: c.g, b: c.b, a: c.a } }
}
Keep any call sites that relied on .into() working — they still compile (From supplies Into).
Run: cargo build -p raylib --features SUPPORT_MODULE_RTEXTURES && cargo clippy -p raylib-sys 2>&1 | rg "from_over_into" -n; echo done
Expected: builds; no from_over_into warnings remain.
git add raylib-sys/src/color.rs raylib/src/core/camera.rs raylib/src/core/texture.rs raylib/src/core/vr.rs
git commit -m "refactor(ws6): impl From over Into (#268, closes #267)
Cherry-picked from raylib-rs#268.
Co-Authored-By: AmityWilder <[email protected]>
Co-Authored-By: Claude Opus 4.7 <[email protected]>"
AudioSample (API hygiene)Files:
raylib/src/core/audio.rsSeals the AudioSample trait so downstream crates can't implement it (closes #213). Author: AmityWilder.
Run: gh pr diff 266 -R raylib-rs/raylib-rs
AudioSampleAdd a private sealing supertrait and bound AudioSample on it (match the current AudioSample definition in audio.rs):
mod private {
pub trait Sealed {}
}
/// ... existing AudioSample docs ...
pub trait AudioSample: private::Sealed {
// ... existing items ...
}
// for each concrete impl, also:
impl private::Sealed for i16 {}
impl private::Sealed for f32 {}
impl private::Sealed for u8 {}
Use exactly the set of types the current crate implements AudioSample for (find them: rg -n "impl AudioSample for" raylib/src/core/audio.rs).
Run: cargo build -p raylib --features SUPPORT_MODULE_RAUDIO && cargo test -p raylib --doc
Expected: builds; doctests pass.
git add raylib/src/core/audio.rs
git commit -m "refactor(ws6): seal AudioSample trait (#266, closes #213)
Cherry-picked from raylib-rs#266.
Co-Authored-By: AmityWilder <[email protected]>
Co-Authored-By: Claude Opus 4.7 <[email protected]>"
Files:
raylib/src/core/{models,texture,shaders,file,drawing,text,automation,misc,collision}.rs, raylib/src/core/macros.rs, raylib/src/rgui/safe.rsPR #277 removes AsRef/AsMut/Deref impls on thin wrappers that hold raw pointers (these hand out references with unbounded lifetimes — closes #276). The WS3 rewrite already redesigned these wrappers; an initial grep shows models.rs only uses impl AsRef<...> as parameter bounds, not as unsound trait impls. So this is an audit: find any remaining unsound impl AsRef/AsMut/Deref for <Weak*/thin wrapper> and remove it; if none remain, record "already covered by WS3". Author: AmityWilder.
Run: gh pr diff 277 -R raylib-rs/raylib-rs | rg -n "^-.*impl (AsRef|AsMut|Deref|DerefMut)"
This lists the unsound impls the PR deleted (the - lines).
Run: rg -n "impl +(AsRef|AsMut|Deref|DerefMut)<[^>]*> +for +(Weak|[A-Z][A-Za-z]*)" raylib/src/core raylib/src/rgui
For each hit, decide: is it a thin wrapper over a raw-pointer-bearing FFI struct where handing out &T/&mut T could outlive the backing allocation? (The PR's reasoning in its description is the rubric.)
For each unsound impl found, delete it and replace internal uses with an explicit accessor method or &self.0 field access (whatever the current code needs to keep compiling). If a removed impl was load-bearing for ergonomics, add a named method (e.g. fn as_ffi(&self) -> &ffi::X) instead of the blanket trait. If none survive, make no code change.
Run: cargo build -p raylib --features SUPPORT_MODULE_RMODELS,SUPPORT_MODULE_RTEXTURES,SUPPORT_MODULE_RTEXT && cargo test -p raylib
Expected: builds; tests pass.
If changes were made:
git add raylib/src/core raylib/src/rgui
git commit -m "fix(ws6): drop remaining unsound wrapper trait impls (#277, closes #276)
Adapted from raylib-rs#277 onto the WS3-rewritten surface.
Co-Authored-By: AmityWilder <[email protected]>
Co-Authored-By: Claude Opus 4.7 <[email protected]>"
If nothing remained: add a line to the completion note (Task 8) — "#277: already covered by the WS3 wrapper redesign (no unsound AsRef/AsMut/Deref impls remain; verified by grep on <date>)" — and make no commit.
Files:
raylib/src/core/models.rs (primary); possibly raylib/src/core/error.rs, raylib/src/core/texture.rsraylib/src/core/models.rs (#[cfg(test)]) or raylib/tests/Three overlapping mesh PRs are applied as one pass (spec §3):
RaylibMesh accessors form slices from possibly-null pointers without checking (closes #262). Author: AmityWilder.texcoords accessor for Mesh. Author: strizhkindenis.All three target the pre-6.0 Mesh. The goal is the soundness intent on the current 6.0 Mesh: every pointer-to-slice accessor must null-check (return &[] or Option<&[T]> rather than constructing a slice from a null data pointer), and texcoords must be exposed safely.
Run: gh pr diff 257 -R raylib-rs/raylib-rs ; gh pr diff 118 -R raylib-rs/raylib-rs ; gh pr diff 256 -R raylib-rs/raylib-rs
Capture: which accessors they null-guard, the chosen return convention (slice-vs-Option), and the texcoords accessor signature.
Mesh accessorsRun: rg -n "from_raw_parts|\.vertices|\.texcoords|\.normals|\.indices|\.colors|fn (vertices|normals|texcoords|indices|tangents|colors)" raylib/src/core/models.rs
List every accessor that builds a slice from a raw Mesh field pointer.
Add to models.rs test module (a Mesh with a null field should yield an empty slice, not UB):
#[cfg(test)]
mod mesh_soundness {
use super::*;
#[test]
fn null_field_accessors_are_empty_not_ub() {
// SAFETY: zeroed Mesh has null data pointers and zero counts — the
// accessors must treat null+0 as an empty slice rather than calling
// slice::from_raw_parts on a null pointer (UB).
let mesh: ffi::Mesh = unsafe { std::mem::zeroed() };
let m = WeakMesh::from_raw(mesh); // use the current constructor for a borrowed/weak Mesh
assert!(m.vertices().is_empty());
assert!(m.texcoords().is_empty());
assert!(m.normals().is_empty());
std::mem::forget(m); // do not run Drop on a zeroed/non-owning Mesh
}
}
Adjust WeakMesh::from_raw/the borrowed-Mesh constructor name to whatever the current code provides (find it: rg -n "struct (Weak)?Mesh|impl .*Mesh" raylib/src/core/models.rs). If no borrowing constructor exists, construct via the existing public path the tests already use.
Run: cargo test -p raylib --features SUPPORT_MODULE_RMODELS mesh_soundness -- --nocapture
Expected: FAIL (panic / non-empty / segfault) if accessors don't null-check; this proves the hole.
For every slice accessor, guard the pointer (apply the #257/#256 pattern to the current types):
pub fn vertices(&self) -> &[Vector3] {
let m = self.as_ffi(); // or &self.0
if m.vertices.is_null() || m.vertexCount == 0 {
return &[];
}
// SAFETY: vertices points to vertexCount * 3 f32 (== vertexCount Vector3),
// allocated by raylib and valid for the lifetime of this Mesh borrow.
unsafe { std::slice::from_raw_parts(m.vertices as *const Vector3, m.vertexCount as usize) }
}
Add the safe texcoords accessor (#118) the same way (m.texcoords → &[Vector2], length vertexCount). Mirror for normals, indices (&[u16], length triangleCount * 3), colors, tangents as the PRs cover them.
Run: cargo test -p raylib --features SUPPORT_MODULE_RMODELS mesh_soundness
Expected: PASS.
Run: cargo build -p raylib --features SUPPORT_MODULE_RMODELS,SUPPORT_MODULE_RTEXTURES,SUPPORT_MODULE_RTEXT && cargo test -p raylib --features SUPPORT_MODULE_RMODELS,SUPPORT_MODULE_RTEXTURES,SUPPORT_MODULE_RTEXT,SUPPORT_MODULE_RSHAPES,SUPPORT_MODULE_RAUDIO
Expected: green.
git add raylib/src/core/models.rs
git commit -m "fix(ws6): null-safe Mesh accessors + safe texcoords (#257/#118/#256)
Adapted the mesh soundness intent of raylib-rs#257, #118, and #256 onto
the WS3-rewritten 6.0 Mesh: pointer-to-slice accessors null-guard, and
texcoords are exposed safely. Closes #262.
Co-Authored-By: AmityWilder <[email protected]>
Co-Authored-By: strizhkindenis <[email protected]>
Co-Authored-By: meisei4 <[email protected]>
Co-Authored-By: Claude Opus 4.7 <[email protected]>"
Files:
Create: docs/superpowers/notes/ws6-prep-complete.md
Step 1: Confirm the safe crate is clippy-clean
Run: cargo clippy -p raylib --all-targets --features SUPPORT_MODULE_RSHAPES,SUPPORT_MODULE_RTEXTURES,SUPPORT_MODULE_RTEXT,SUPPORT_MODULE_RMODELS,SUPPORT_MODULE_RAUDIO -- -D warnings
Expected: exit 0, no warnings. (This is the WS6a gate target minus the full alias, which doesn't exist yet.)
Run the four software-render commands from .github/workflows/baseline.yml (render_shapes/render_text, render_gui, render_rlgl).
Expected: all green.
Record per-PR disposition (applied / adapted / already-covered, with the reason), the blocker fixes, and the resulting clippy-clean state. Link the spec.
git add docs/superpowers/notes/ws6-prep-complete.md
git commit -m "docs(ws6): WS6-prep completion note (blockers cleared, deferred PRs folded in)
Co-Authored-By: Claude Opus 4.7 <[email protected]>"
git push fork 6.0-rc
Then watch the existing baseline.yml run (it must stay green):
gh run watch <id> -R Dacode45/ms-raylib-rs --exit-status
Co-Authored-By plus Claude's. ✓vertices/texcoords/normals/indices) used in Task 7 match the Mesh field set; the full alias is explicitly deferred to WS6a (this plan lints against the module set instead). ✓