A small 3D rendering library for iced apps: instanced meshes, a shadow-mapped pipeline with optional post-processing, cameras, lights, a scene graph widget and WGSL shader preludes. Consumers write only a fragment shader.
go-task build # cargo build for the host target
go-task lint # fmt check + clippy --all-targets --all-features -D warnings
go-task test # unit + GPU tests; GPU tests need Vulkan
go-task test:unit # unit tests only, no GPU needed
go-task test:gpu # GPU integration tests only
go-task test:one TEST=<name> # one test, with a backtrace
go-task coverage # cargo-tarpaulin HTML report in coverage/
go-task ci # lint + test + build; CI runs it as docker:run TARGET=ci
go-task example EXAMPLE=showcase # run an example, built with the debug feature
go-task test:crates-io # test the crates.io build and dry-run its publishExamples: showcase, showcase_advanced (needs debug; keys 1 to 6 switch debug shaders), gizmo, gizmo_manual_draggable, gizmo_manual_interactive, crystal and transparent.
src/pipeline/:RenderPipeline3D, the GPU types, growable buffers, the shadow pass, post-processing and the shader constants.shaders/*.wgsl: the WGSL preludes, embedded intosrc/pipeline/shaders.rswithinclude_str!.src/camera/,src/light/,src/mesh/(primitives and SVG paths),src/math/: the building blocks.src/scene/builds the per-frameSceneData.src/graph/is theSceneGraph.src/widget/holdsscene_3d()andScene3DProgram.src/gizmo/andsrc/overlay/: the translation gizmo and the draggable and interactive overlays it is built on.tests/gpu_*.rs: GPU integration tests.tests/gpu_helper.rsmakes a headless Vulkan device.
- One type or concept per file, grouped by folder. A new camera goes in
camera/, a new light inlight/, re-exported frommod.rs. Never merge unrelated types into one file. - Struct fields stay private, behind accessors. Never expose internal GPU buffers or state.
- Every public type has a doc comment.
- WGSL lives in
shaders/*.wgsl. A Rust GPU struct and its WGSL twin must match byte for byte. - Each source file's unit tests sit in its own
<name>_tests.rs, pulled in with#[cfg(test)] #[path = "<name>_tests.rs"] mod tests;. Never combine several source files' tests in one file. - Unlike the shared default, this repo requires tests with every new feature or type, and aims for 80% coverage.
docs/testing.mdsays what each kind of change needs.
- grid-rs (the Grid app's background) uses
compose_shader,RenderPipeline3D,MeshandMeshBuilder,svg::parse_path,mathandic3d::glam. - The public API is what
lib.rsre-exports, plus thegizmo,graph,mathandwidgetmodules. There is noprelude.rs. compose_shader(fragment)prepends the engine preludes (scene uniforms, vertex IO, standard vertex shader, PCF shadows) to a consumer's fragment shader.- Bind group 0 is the engine's: scene uniforms, the lights array (up to
MAX_LIGHTS, 16), the shadow map and its sampler. Group 1 is the consumer's custom uniforms. Changing a binding or a uniform's layout breaks every consumer shader. icedandwgpuare Playtron forks, andglam0.29. All three are re-exported (ic3d::iced,ic3d::wgpu,ic3d::glam), so consumers use those instead of their own dependency.- crates.io rejects git dependencies, so a release publishes a copy that depends on the forks as published on crates.io,
iced-koraandwgpu-kora.scripts/crates-io.shstages it. A fork change ic3d needs must be published as-korafirst (playtron-os/iced,kora/README.md).
- Before changing the pipeline, a bind group, a GPU type or a WGSL prelude, read
docs/architecture.md. - Before adding a type or its tests, read
docs/testing.md. - Before changing the API apps call (pipeline,
scene_3d,SceneGraph,Scene3DProgram), readdocs/consumer-api.md.
- WGSL alignment differs from Rust
#[repr(C)]:vec2<f32>aligns to 8 bytes,vec3<f32>to 16, and eachmat3x3<f32>column takes 16. When adding a uniform struct, check Rustsize_ofagainst the WGSL. A mismatch corrupts data silently or fails validation at runtime. - iced's
shader::Primitive::render()gets nodeviceorqueue, so create and resize textures inprepare(). - iced's
Pipelinetrait needsSend + Sync, soBox<dyn PostProcessPass>must carry those bounds. - Without a Vulkan device, the GPU tests panic. Use
go-task test:unitthere. CI uses lavapipe.
This repository follows the rules every Playtron repo follows, plus those for libs packages. They are identical in every repo in this group, so fix them upstream rather than here.
AGENTS.mdis this repo's instruction file;CLAUDE.mdandGEMINI.mdonly point at it.- Every agent loads
AGENTS.mdon every turn. Keep it under 150 lines and 1,500 words, this block included. Topic detail goes indocs/, linked in one line that says when to read it. - Never append dated notes, design history or one task's findings to
AGENTS.md. Add a line only when it changes how the next task must be done. - Review policy goes in
REVIEW.md, not here. No personal machine paths in either. - Comment only what the code cannot say, in one or two short sentences. Longer reasoning goes in the pull request.
- Keep it simple and flat. Early return over nesting. No advanced language features where a plain one works.
- After changing Rust:
cargo check,cargo fmt,cargo clippy --fix --allow-dirty, until clean. - Run the repo's own lint and test tasks before calling work done. Never claim a command passed that you did not run.
- Conventional Commits,
type(scope): subject.featreleases a minor,fixandperfa patch, other types nothing. - A breaking change needs a
BREAKING CHANGE:footer.feat!:does not parse, so it releases nothing. - Commit subjects are imperative and under 72 characters. Add a body only for a non-obvious why, in three lines or fewer.
- Pull requests: Problem, Fix, Testing. 100 words for a small change, 250 at most. No investigation story, no file-by-file walkthrough.
- Write plain English: short sentences, everyday words, "Kora" not "she", no invented shorthand. The
writingskill has examples. - Never hand-edit a version; semantic-release owns it.
- Do not add tests unless asked or
REVIEW.mdrequires them. - Each package is its own repository: depend on a sibling by git URL, never by
path, symlink or../link. - A dev deploy to a Kora host must override every
/etc/systemd/userunit running the system build, with drop-ins naming the same store path. - A dev build of an agentos-accounts caller prompts on every credential read until root-owned
allowed-callers.dnames its exact exe.
- Public API is a contract. Export public types from
prelude.rs.#[must_use]on builders. - Sweep for consumers before changing a public name:
gh search code "<name>" --owner playtron-os. - Unsafe code is forbidden. Clippy
all+pedantic, warnings are errors. - WASM-safe: use
utils::platformabstractions, neverstd::time::Instantdirectly. - No hard-coded visual values. Playtron values are the trait and serde defaults; other brands override.
- Feature-gate component modules with
#[cfg(feature = "...")]. - Read neighbouring files and match them. Consistency beats individual improvement.
Full text with the reasoning: all, libs. With the playtron plugin installed, /playtron:architecture has them loaded.