forge3d
AGENTS.md821 starsChanged 30 days agoMITView on GitHub
We split this AGENTS.md into the rules it gives an agent
11 rules. About 0 are common, 0 are distinctive, and 11 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
11
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 7
When removing dead code that probed nonexistent module-level functions, also clean up imports that become unused (e.g., _forge3d, warnings …
- Very specific: line 12
When a Rust struct has #[pyclass] but no corresponding m.add_class::<T>()?
- Very specific: line 13
When importing multiple types from the same module (e.g., crate::sdf::py), consolidate into a single use statement with a braced group …
- Very specific: line 21
When the Python wrapper (e.g., mesh.py) has a feature-detection guard like _HAS_TBN = hasattr(_forge3d, 'mesh_generate_cube_tbn'), simply …
- Very specific: line 27
When adding state tracking to Scene for a new feature (SSGI, SSR, bloom, etc.), follow the pattern: add fields to the struct, initialize …
- Very specific: line 45
When returning numpy arrays from PyO3, prefer PyArray1::from_vec_bound(py, data) over the deprecated from_vec(py, data) in pyo3 0.21+.
- Very specific: line 53
When splitting a file to stay under 300 lines, the natural boundary for COPC is dataset/hierarchy (copc.rs) vs chunk decoding/parsing …
- Very specific: line 57
When testing LAZ decompression end-to-end from Python, the las::Reader already decompresses LAZ transparently.
- Very specific: line 62
When exposing [f32; 4] color arrays to Python, convert to/from tuples (f32, f32, f32, f32) rather than Vec<f32> for consistency with other …
- Very specific: line 74
When a test asserts exact equality on last_render_metadata, pop nondeterministic timing keys (offline_accumulation_ms, timing_source) first.
- Very specific: line 112
After deleting a method, sweep the file's imports: removing execute_chain orphaned RenderError and GpuTimingManager in chain.rs, and …
≈ “Common” is an estimate: at least 1 in 5 of the 36,368 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Agent Reflections
P0.2: Wrapper/Native Callsite Mismatch (2026-02-19)
- When probing for native API methods, always verify whether a function is a module-level free function or an instance method on a class. The old
offscreen.pycheckedhasattr(_native, "render_rgba")butrender_rgbais a method onSceneinstances, not a module-level export. This pattern of dead probes can persist silently for a long time because the fallback path works. - The PyO3
#[pyo3(text_signature = "($self)")]annotation onScene.render_rgbaconfirms it takes no positional arguments beyondself-- width and height are baked in atSceneconstruction time. Always read the Rust signature before wiring up Python calls. - When removing dead code that probed nonexistent module-level functions, also clean up imports that become unused (e.g.,
_forge3d,warningsinviewer.py). - Contract tests that assert "function X does NOT exist at module level" are valuable for documenting architectural decisions and preventing future developers from re-introducing the same mistake.
P0.3: Register Orphaned PyO3 Classes (2026-02-19)
- When a Rust struct has
#[pyclass]but no correspondingm.add_class::<T>()?;in the#[pymodule]init function, the class is invisible to Python even though it compiles fine. Always check that every#[pyclass]has a matching registration insrc/lib.rs. - When importing multiple types from the same module (e.g.,
crate::sdf::py), consolidate into a singleusestatement with a braced group rather than adding separateuselines per type. - Negative contract tests ("X is NOT registered") should be flipped to positive assertions ("X IS registered") when the registration is intentionally added. Also add construction tests that verify the class is not just importable but actually usable (constructible, methods callable).
- The
EXPECTED_CLASSESlist in Section 1 oftest_api_contracts.pymust be updated whenever newm.add_classregistrations are added, otherwise the parametrized existence test won't cover them.
P0.4: Resolve Mesh TBN Drift (2026-02-19)
- When Rust code is gated behind a Cargo feature flag (e.g.,
#[cfg(feature = "enable-tbn")]), you must also add that feature to the maturin build features inpyproject.toml. Otherwise the module compiles but the functions are excluded from the extension. Thepyproject.toml[tool.maturin].featureslist is the single source of truth for which features are included in the Python wheel. - PyO3
#[pyfunction]wrappers that convert Rust structs to Python dicts should use a shared helper function (e.g.,tbn_result_to_py_dict) to avoid duplicating dict-building logic across multiple wrapper functions. - When the Python wrapper (e.g.,
mesh.py) has a feature-detection guard like_HAS_TBN = hasattr(_forge3d, 'mesh_generate_cube_tbn'), simply registering the function in the pymodule is enough to flip the flag. No changes to the Python wrapper itself are needed. - For feature-gated registrations in
lib.rs, wrap related function registrations in a#[cfg(feature = "...")]block to keep them conditional, matching the module-level gating inmod.rs. - The
EXPECTED_FUNCTIONSlist in the contract tests should be updated alongside registration to maintain the contract lock.
P1.1: SSGI/SSR Settings Wiring (2026-02-19)
- When adding state tracking to
Scenefor a new feature (SSGI, SSR, bloom, etc.), follow the pattern: add fields to the struct, initialize with defaults in the constructor, add#[pymethods]for enable/disable/is_enabled/set_settings/get_settings, and update the.pyitype stubs. get_*_settings()should return a dict (not a typed object) for maximum flexibility and compatibility with the Python side. Usepyo3::types::PyDict::new(py)anddict.set_item(...).- Behavior tests for Scene methods should test at the class level (
hasattr(_native.Scene, "method_name")) when instance construction is blocked by GPU/shader issues. This avoids test fragility while still asserting the API contract.
P1.2: Bloom Execute Wiring via Resource Pool (2026-02-19)
HISTORICAL (superseded 2026-07-10): CENSOR deleted
PostFxEffect,BloomEffect,PostFxResourcePool, andPostFxChainas zero-caller dead structure. The notes below document the original wiring only;BloomConfigand Scene's CPU bloom path remain live.
- The
PostFxEffect::execute()trait method needsqueue: &Queueto upload uniform data before dispatching compute passes. When modifying a trait signature, check that the only implementor isBloomEffectto avoid a wider refactor. BloomEffectdiffers fromTerrainBloomProcessorin that it must use thePostFxResourcePool's ping-pong texture pairs for intermediate storage rather than owning its own textures. Allocate pairs duringinitialize()and retrieve views duringexecute().- The bloom composite pass requires a 4-binding layout (original + bloom + output + uniforms), distinct from the 3-binding brightpass/blur layouts. This was missing from the original stub and had to be added alongside the composite pipeline and shader loading.
- Bloom default-off semantics are critical for backward compatibility:
BloomConfig::default().enabled == false, andexecute()returnsOk(())immediately when disabled. - The
PostFxChain::execute_chain()must also acceptqueueand forward it to each effect'sexecute()call.
P2.1: Point Cloud GPU Rendering Path (2026-02-19)
- The Rust
PointBufferstores positions as flatVec<f32>[x,y,z,...] and colors asOption<Vec<u8>>[r,g,b,...]. Thecreate_gpu_buffer()method interleaves them into [x,y,z,r,g,b] per point, normalizing u8 colors to 0..1 f32. Default is white when no colors are present. - PyO3 bindings for simple data structs work best as thin wrapper types (e.g.,
PyPointBufferwrappingpointcloud::PointBuffer) rather than deriving#[pyclass]directly on the Rust struct, because the inner struct lacksClone/Copyand holds non-PyO3-compatible fields. - When returning numpy arrays from PyO3, prefer
PyArray1::from_vec_bound(py, data)over the deprecatedfrom_vec(py, data)in pyo3 0.21+. - Constructor validation for
PointBuffer(positions length divisible by 3, colors matching point count) prevents downstream GPU buffer mismatches. Always validate at the boundary. - The
renderer.rsfile (353 lines) is slightly over the 300-line guideline due to pre-existing duplication betweenload_copc_pointsandload_ept_points. A future refactor could extract a commonload_points_generichelper. MemoryReportseparates observation from policy: it reports cache_used, cache_budget, utilization, and entry_count without deciding what to do about high utilization. This keeps the renderer testable without GPU access.
P2.2: COPC LAZ Decompression Feature Gate (2026-02-20)
- The
lazcrate is a transitive dependency vialas = { features = ["laz"] }but must be added as a direct optional dependency (laz = { version = "0.9", optional = true }) to use its API directly incopc_decode.rs. A marker feature alone (copc_laz = []) is insufficient sinceuse laz::...requires the crate to be a direct dependency. - When splitting a file to stay under 300 lines, the natural boundary for COPC is dataset/hierarchy (copc.rs) vs chunk decoding/parsing (copc_decode.rs). The decode module uses
pub(crate)visibility to exposedecode_chunkandparse_uncompressed_pointsonly within the crate. - For feature-gated code using
#[cfg(feature = "copc_laz")], the non-feature path must explicitly suppress unused-variable warnings usinglet _ = (data, point_count, ...)or the compiler will warn about unused parameters. - The COPC file format stores LAZ compression parameters in a VLR with user_id "laszip encoded" and record_id 22204. The existing code only read the first VLR (COPC info); the fix iterates all
num_vlrsVLRs to also capture the LAZ VLR. - The MtStHelens.laz fixture uses NAD83 State Plane Washington South coordinates (US feet), not UTM. Always check actual coordinate values before writing range assertions in tests.
- When testing LAZ decompression end-to-end from Python, the
las::Readeralready decompresses LAZ transparently. Exposing aread_laz_points_info()PyO3 function that returns (count, coords, has_rgb) provides a lightweight fixture validation without requiring a COPC-specific fixture file.
P2.3: Labels Python Bindings (2026-02-20)
- For PyO3 bindings of simple config structs like
LabelStyle, follow the establishedPySelectionStyle/PyHighlightStylepattern: separate#[pyclass]struct with#[pyo3(get, set)]on each field, a#[new]constructor with default values matching the RustDefaultimpl, bidirectionalFromconversions, and__repr__. - When exposing
[f32; 4]color arrays to Python, convert to/from tuples(f32, f32, f32, f32)rather thanVec<f32>for consistency with other bindings (e.g.,PySelectionStyle.color). Same for[f32; 2]offsets. - For
f32::MAXdefault values in Rust, use the literal value3.4028235e38in the#[pyo3(signature)]since PyO3 doesn't evaluate Rust constants in signature defaults. - Nested PyO3 classes (e.g.,
PyLabelFlagsinsidePyLabelStyle) must also be registered withm.add_class::<>()and deriveClone. When used as a field in another#[pyclass], the#[pyo3(get, set)]attribute works seamlessly. - New
py_bindings.rssubmodules should be declared unconditionally inmod.rs(not behindcfg(feature)), with the#[cfg(feature = "extension-module")]guard on individual items inside the file. This matches the pattern used bylighting/py_bindings.rsandterrain/cog/py_bindings.rs.
SUTURA: Zero-Placeholder MapScene (2026-07-07)
- The
allow_placeholderescape hatch is gone:MapScene.rendereither draws through the native GPU-terrain path or raisesMapSceneNativeUnavailable, whose.diagnosticscarry structured{"status": "diagnostic_block", "layer", "reason", "required_native"}dicts built by_map_scene_validation.diagnostic_block. Never reintroduce a CPU placeholder branch;tests/test_mapscene_sutura_integrity.py::test_no_allow_placeholder_symbolgreps the whole package for regressions. - Depth-occlusion label culling lives ONLY in
MapScene.compile_plan(), which is a total function of the serialized recipe (CPU camera/terrain sampler, never a live GPU depth frame)._render_native_offscreen_resultrequires aCompiledScenePlanand mutates no label state;MapScene.rendercompiles on demand whencompiled_planis None. - The frozen compiled plan (label plans + per-label visibility flags keyed by a camera+terrain hash) is stored in new
RecipeManifest.compiled_label_plans/depth_cullfields.manifest_to_jsoncanonicalizes floats (allow_nan=False,-0.0 -> 0.0) soto_json -> from_json -> to_jsonround-trips byte-identically. BUNDLE_VERSIONis 3: bundles persistscene/compiled_plan.jsonandMapScene.load_bundlerehydrates it verbatim; v2 bundles (no compiled plan) are recompiled once on load.- Dataclass serialization asymmetries (
Nonevs[]from_sequence) silently break byte-identical validation reports after a bundle round-trip — normalize at the decode boundary (_layer_from_dict), e.g.bounds=data.get("bounds") or None. - When a test asserts exact equality on
last_render_metadata, pop nondeterministic timing keys (offline_accumulation_ms,timing_source) first; they come fromtime.perf_counterin_render_terrain_renderer_result.
PROMETHEUS Remediation: Audit Fix Pass (2026-07-08)
- The terrain PT reservoir now uses the CANONICAL ReSTIR layout (
src/path_tracing/restir/types.rsReservoir/LightSample, 80-byte storage stride). When a WGSL struct must match a RustPodstruct in a storage array, verify the STRIDE, not the field list — the original ad-hocTerrainReservoirhad an 80-byte WGSL stride against a 64-byte Rust allocation and wgpu will not catch a short runtime-sized array until the last pixels write out of the bound range. - Real temporal/spatial reuse = dispatching the existing
pt_restir_temporal.wgsl/pt_restir_spatial.wgslas standalone pipelines insideHybridPathTracerwith three canonical reservoir buffers (curr candidates -> temporal merges prev+curr -> out -> spatial writes back into prev). The kernel M-clamps prev in place (cap 512) before each merge, otherwisem/w_sumgrow ~9x per spatial pass and overflow within ~40 frames. - The spatial pass needs a G-buffer (
gbuffer_nr/gbuffer_posat group(1) bindings 10/11) for target-pdf re-evaluation. For a static scene + deterministic camera, write it ONCE from a dedicated entry (main_terrain_gbuffer) with its OWN pipeline layout — keeping those two storage bindings out of the main kernel's layout is what keeps every pipeline within 8 storage buffers per compute stage. - Bind-group portability: lighting moved from group(4) to group(0)@binding(1) so all hybrid pipelines fit
max_bind_groups = 4. When a shader comment claims "consolidated to stay within max_bind_groups=4", count the actual pipeline-layout groups — the claim had drifted. HybridScene::dummy_storage_buffer()must cover ONE ELEMENT of the largest runtime-sized array it stands in for (WGSLBvhNode= 48 bytes); a 4-byte dummy fails wgpu validation with "Buffer is bound with size 4 where the shader expects N" only at dispatch time.- Memory-tracker hygiene for multi-resource render paths: wrap every tracked allocation in a Drop guard (
TrackedGpu) and implementDropon tracked wrapper types (TerrainMinMaxPyramid,TerrainPtScene) so?/early-return paths cannot leak tracker state. Gate the budget on tracker metrics captured AFTER all allocations, not onpeak_host_visible_bytesalone (device-local resources never appear in host-visible metrics). - "Variance across the last N frames" (02-prometheus.md:59) is implementable as a WINDOWED Welford over the running-mean luminance: reset at
frame_index % window == 0, gate onm2/(n-1)at window boundaries. Raw per-frame sample variance never falls for a stochastic estimator; variance of the running mean does. - Background Bash gotcha that bit this session twice:
cmd | tail -Nin a background task truncates the retained log AND masks the exit code (pipeline status = tail's). Redirect to a scratchpad file and grep it instead.
BOP-P2-02: Clipmap Geometry Provider + Height Streaming (2026-07-09)
- The 2026-07-05 audit's "adding the clipmap vertex entry to the shared terrain shader crashed Vulkan pipeline validation/construction" no longer reproduces on the current wgpu/driver stack. When a backend-conditional fallback exists only because of a historical crash, re-verify the crash before building more machinery around the gate — removing the two
Backend::Vulkanchecks and rendering produced near-identical output to the recorded DX12 evidence (RGB sum 1878111 vs 1877131). - WGSL entry points cost nothing until a pipeline uses them: moving
vs_clipmap_mainfrom a Rustpush_strhack intoterrain_pbr_pom.wgslis safe for every other pipeline compiled from the same preprocessed module (vs_main, AOV, offline HDR), because wgpu validates the resource interface per entry point at pipeline creation, not per module. cargo test <filter> --libwithout--features extension-modulesilently skips the entiresrc/terrain/renderer/tree. A run that reports "N passed, hundreds filtered out" can still mean your new tests never ran — check the module paths in the reported test names, then re-run with the feature.make_ring_skirtsstitched curtain quads across row/strip boundaries ("Simplified: add all for now"), creating triangles spanning the whole ring — caught bytests/test_geomorph_seams.py(max edge 4360 on a 1000 m extent). Skirt adjacency must be row-aware: passrow_width = resolution + 1and skip pairs wherei % row_width == 0. When a generator comment says "simplified for now", assume a test somewhere is already red because of it.- The fixed-LOD
HeightMosaicmode (slot = tile coords) makes the atlas a direct geographic mosaic that binds asheight_texwith plain [0,1] UVs — no page-table indirection needed in the mega-shader. Coarse-prefill (low-res read upsampled per tile at enable time) is what turns "tiles in flight" into "coarse terrain" instead of holes. ClipmapStreamer::update()only emits center/corner ring tiles, so camera-driven demand alone never converges full residency; pair it with a nearest-first top-up loop throughAsyncTileLoader::request()(which already dedups and enforces max-in-flight backpressure).- Clipmap vertex UVs are world-anchored (
(world + extent/2) / extent), so recentering the ring mesh on a moving camera keeps DEM alignment for free; putting the streaming center in the geometry cache key gives regeneration-on-move with zero per-frame CPU mesh cost when stationary. - Ten P2 recipe goldens (auto_water, clipmap, cloud_shadows, copc, arabic, screen-space pair, textured_gltf, thematic, tiles3d) were explicitly listed in
.gitignore— they existed only on this machine while the plan claimed "intended baselines are committed". When a golden gate depends on files, checkgit ls-filesfor them, not just the working tree.
CENSOR validation policy (2026-08-05)
- CENSOR is a small architectural execution-truth contract. Routine changes preserve capability negotiation, explicit degradations, tracked allocations, enforce-by-default budgeting, certificate/schema/tamper behavior, shader-use reporting, and honest probe outcomes.
- A downstream prompt that depends on CENSOR consumes those interfaces and invariants; it does not inherit CENSOR's full closure suite.
- The stable hosted pull-request gate is
PR Core Success. Complete Python/Rust/platform matrices, production signing, candidate-selected goldens, physical GPU checks, and scratch red-proof corruption are acceptance/release evidence reported byFull Acceptance Summary, not routine branch protection. Seedocs/censor-validation-policy.md. - Production signing is required only in protected acceptance/release workflows. Routine internal and fork PRs remain explicitly untrusted and must pass canonicalization, certificate-contract, and tamper-rejection tests without
FORGE3D_CERT_SIGNING_KEY.
CENSOR (2026-07-09/10, historical closure record)
- Project-wide honesty pass. Earlier areas (prior tasks): capability negotiation replaced the
Features::empty()device request so the device advertises what it actually enables; the host-visible budget default flipped to ENFORCE (BUDGET_POLICY_ENFORCE), over-budget host-visible allocations now returnRenderError::Budget; a source-level allocation gate (tests/test_allocation_gate.py) forbids rawcreate_buffer/create_textureoutsidesrc/core/resource_tracker.rs; and a signedRenderCertificate+ verifier attests real render provenance. - Task 14 (dead-feature + dead-structure removal): deleted six features from
Cargo.toml [features]that had ZERO#[cfg(feature="…")]refs anywhere insrc//tests//benches//build.rs—terrain_spike,exr(the standalone marker only;images = ["dep:exr"]kept because theexrcrate is still an optional dep the AOV path uses),enable-ibl,enable-csm,enable-render-bundles,enable-memory-pools.terrain_spikeguarded nothing, so its module already compiled unconditionally and was left alone. - The current portable feature inventory is
default,async_readback,copc_laz,cog_streaming,gis-remote,geos-topology,weighted-oit,wsI_bigbuf,wsI_double_buf,enable-pbr,enable-tbn,enable-normal-mapping,enable-hdr-offscreen,enable-renderer-config,enable-staging-rings,shader-contract-asserts..github/workflows/ci.ymland.cargo/config.tomlare authoritative; routine versus full execution is defined by the current workflow scope, not this historical list. Gate (c) intests/test_no_silent_degradation.pylocks routing and consistency. - Dead structure — all confirmed zero external callers before deletion. The final closure deletes the complete legacy
PostFxChain/PostFxEffect/PostFxResourcePoolgraph and its unreachable standaloneBloomEffect, while retaining the liveBloomConfigused by Scene's real CPU postfx path. The zero-caller legacysrc/core/framegraph.rswrapper and zero-callerTonemapProcessorwere also deleted;framegraph_implremains because diagnostics exercises it, andcore::tonemap::resolve_reference_hdr_to_rgba8remains as the authoritative adjudication resolve. Earlier deletions also removed render bundles and the parallelsrc/render/memory_budget.rs; the livesrc/util/memory_budget.rsIBL estimators are untouched. - Viewer bind groups are cached by resource identity in
viewer/render/main_loop/postfx_cache.rs, invalidated on resize and IBL replacement, and the frame-looppostfx.rscontains nocreate_bind_groupcalls. Snapshot composite bind groups are created lazily only on a cache miss; routine frames allocate none. - After deleting a method, sweep the file's imports: removing
execute_chainorphanedRenderErrorandGpuTimingManagerinchain.rs, andpostfx_apply_nooporphaneduse wgpu::*;inpostfx/mod.rs. Unused-import warnings are-D warningsclippy failures, so this is not optional cleanup. - F-11 closure removed the temporary sky/fog frame-loop exemptions: their bind groups are miss-only caches, resize invalidates every cache whose texture/depth identity changes, and the routine frame files contain no bind-group or sampler creation.
- F-06 production certificates use a random Ed25519 seed stored only as the
FORGE3D_CERT_SIGNING_KEYActions secret. The tracked public key and all committed certificates rotate atomically; protected acceptance/release lanes fail without the secret or on any key/signature mismatch. Routine internal and fork PRs are explicitly marked untrusted and never require the secret. - Pre-existing red found during verification (NOT caused by Task 14):
offscreen::adjudication_raster::tests::raster_twin_is_explicitly_blocked_and_reuses_shared_infraassertedforward.rscontains the literalglobal_tracker(), which sibling CENSOR commitf488592d("drop redundant manual tracking") had removed. RESOLVED ina9f1e05b: the lock now asserts the honest new invariant (read_hdr_texture+tracked_create_texture) and the test is green in the curated matrix. - Historical red-proof note: CI runners carried a newer clippy than local stable; three lints (
iter_kv_map,manual_filter, needless&inprintln!) failed the acceptance run and were fixed at the source. This does not make scratch red-proof reproduction a routine PR requirement.
CENSOR audit remediation (2026-07-10, historical acceptance record)
- The independent Fable 5 audit (local
docs/audits/fable5-moonshots/14-censor-implementation-audit.md;docs/audits/is git-ignored by policy) scored 12/20 requirementsfull, 8partial. Remediation closed the code-side gaps in one pass: - Golden negative control (F-02):
UPDATE_GOLDENSwas bound at import, so the control'smonkeypatch.delenvwas dead code — underFORGE3D_UPDATE_RECIPE_GOLDENS=1the "rejecting" control would have COPIED the corrupted image over the committed golden. Update-mode is now the call-time_update_goldens_enabled(), the control simulates a refresh run (setenv → delenv) and asserts the golden's bytes are untouched. Lesson: any env-driven test-mode flag read at module import defeats per-test monkeypatching. - Live
gpu_mseverywhere (F-04): newOneShotTimingwrapper incore/gpu_timing.rs(alsofor_devicefor renderers owning a non-global device, e.g. TerrainSpike). Every certified GPU render path now records live per-pass timings — vector oit/pick/fill/demo, adjudication (raster fully; PT frame-0 representative region — a wavefront frame spans multiple encoders), hybrid PT (frame-0 per pipeline), instancing, spike, offline batch (first-sample representative), debug pattern, BRDF tile (its dormant timestamp query set finally gets READ).timestamp_validis now derived (begin != 0 && end >= begin), and all record loops report invalid stamps as 0.0. Pass labels/orders unchanged ⇒ signed payloads byte-identical ⇒ committed certificates stayed valid. - Render-surface honesty (F-05):
render_brdf_tile{,_overrides}gained thecertificate=contract;test_render_certificate_contract.pynow sweeps every publicrender_*callable (forge3d +_forge3d) — each must takecertificate=or sit inDOCUMENTED_EXCLUSIONSwith a reason mirrored in its docstring. - Probe honesty (F-10):
terrain_ci_probe.pyexit codes now distinguish ABSENT (2: no CI-safe adapter → marker + job success) from CRASH (3: adapter present, smoke render raised → golden job FAILS). The probe step lost itscontinue-on-error. - Gates hardened (F-08/F-09/F-11): allocation gate adds UFCS/
create_texture_with_data/line-split patterns (immediately caught a helper false-positive — pattern narrowed toDevice::create_*); dead-structure gate greps forbidden symbols repo-wide and checks the whole frame-loop dir against a documented bind-group allowlist; a new capability gate rejectsrequest_device(&wgpu::DeviceDescriptor::default()in production code, andextrude_polygon_gpu_pynow uses the negotiated global context instead of a private default device. - Ledger cross-invariant (F-07):
finish_ledger_capturedebug-asserts equality on both axes between the ledger andResourceRegistry's exactResourceHandlesubset (the public registry totals intentionally also include estimate-only legacy bookkeeping); mid-capture ownerless allocations are counted (only pre-existing ambient entries are excluded) — both the paired and deliberately unpaired cases are unit-locked. - Historical acceptance snapshot after remediation:
cargo fmt --check/forge3d-clippy/ curatedcargo test(693 passed) / focused pytest (84 passed, 0 skipped, live RTX 3070) all green; determinism SHA unchanged across the refactor (8e397b1a…). Do not treat the full matrix or physical-GPU reproduction as a routine PR requirement.
ANAMNESIS completion audit (2026-07-22)
- A hermeticity test must exercise the public scheduler output, not compare a key against a second hand-written hash of the same inputs. The real-output mutation lane exposed that
render_sequence(..., capabilities=...)discarded explicit capabilities for the built-in executor. Preserve explicit fingerprints on every executor; only probe native capabilities when an external renderer supplies none. - A warm-store index that still opens every entry's
meta.jsonbefore loading defeats the cache on Windows. Validate the fast pack in memory, compare its key set with the directory inventory, and fall back to the full metadata scan only on mismatch. Count the fast-pack payload againstmax_bytesand remove the on-disk pack before writes/GC so the optimization cannot exceed the store budget. - Cross-backend portability requires two independent physical renders. A consumer may enter the portable TERRA equivalence class only after its own output matches the committed golden and its adapter metadata proves a non-software DX12 device; otherwise emit
ABSENTrather than treating a cached producer blob as proof of consumer determinism.
SIDERA sun-control closure (2026-07-26)
- The print-only
SetSunDirectionstub was fixed, not retained as a second path: observation and manual controls both update the live lit uniform and route terrain throughsync_terrain_sun_to_lit.forge3d.sky.set_observationpersists a process-level observation across viewer restarts; a later PythonViewerHandle.set_sunclears that replay, while a native terminal override is local to its viewer. Either manual path deactivates observation control in that viewer so reloads and intensity changes cannot mix the two sources. - Night rendering uses one 64-byte
NightInstancestorage layout for the 9,096-star Yale catalog, five planets, and the textured Moon. The Moon's quad basis points toward the projected Sun, its terminator uses exact illuminated-sphere geometry, and its LRO albedo comes from the NASA SVS CGI Moon Kit. Below the horizon, the live directional-light path switches from the Sun to a Krisciunas–Schaefer (1991) phase/extinction scale; the real Sun direction remains separate for the declared SIDERA −4° to −18° twilight ramp. The fixed Mauna Kea golden is generated only by_astro_night_golden_frame, and its certificate must name both models plusastro.night.shader.
SIDERA audit remediation (2026-07-28)
- The Moon does not get annual aberration. It shares Earth's barycentric velocity, so the +20.5″ aberration is cancelled almost exactly by the Moon's barycentric light-time displacement over the same 1.3 s; only the ~0.7″ geocentric retardation survives. Applying both was a silent double-count worth 20″ — it still passed the 30″ gate (22.30″), which is precisely why a gate with margin is not proof of a correct reduction. Fixing it took the Moon to 4.69″. The Sun keeps aberration-only (its light-time is the aberration); planets keep retarded-position + aberration.
- A gate that compares a function against a stored copy of its own output proves nothing. Two shipped this way and both were replaced: GMST was checked against three hand-written constants (now: an independently coded IAU-1982/Aoki polynomial swept over 1,694 epochs, plus a third implementation in Python inside the test), and refraction was checked against a constant that was Sæmundsson's own output at 5° (error 1.1e-10 — the tell). DoD 4 asks for the Bennett value at 5° apparent altitude, so
unrefract_altitudeinverts the Sæmundsson fit first. Same family as the ANAMNESIS "second hand-written hash" note above. - Ablation gates must run the pipeline they claim to characterise. The precession ablation measured
DVec3::Xthrough the precession matrix — it would have stayed green withprecess_j2000_to_datedeleted fromcatalog::star_instances. It now runs all 9,096 catalog stars through the real reduction chain: max 22.27′, median 18.65′, min 0.30′. The min is supposed to be near zero (precession rotates about the ecliptic pole), and asserting it proves the measurement is precession and not something else. - Source-grep tests must read the file they are about. The
set_sunstub gate read five downstream files but neveripc_command.rs; reverting that one file to itsprintln!body left the test green. - Golden bytes are backend-dependent. In-process the night render is only two-run reproducible; equality with the committed PNG holds on a pinned backend, so that claim lives in a subprocess test with
WGPU_BACKENDSset, exactly liketest_determinism_hash.py, with a SHA-256 sidecar intests/goldens/determinism/.has_gpu()is true for WARP/lavapipe, so byte-equality is additionally gated ondevice_probeproving a non-software adapter (ANAMNESIS ABSENT rule).degradations == []is likewise only true on a device granting every negotiated capability — assert the degradation kinds instead. - Two viewer bugs the golden could never catch, because the golden does not use the viewer. (1)
RENDER_ATTACHMENTwas added toviewer.lit.outputinstead ofviewer.sky.outputinresize_render_targets, so the first window resize with an active observation would fail wgpu validation; init and resize now sharesky_init::sky_output_descriptor. (2)vs_nightput quads at unit distance while the terrain camera clampsnearto 1.0 world unit, clipping the entire night sky away in any real scene — fixed with the skybox depth pin (vec4(clip.xy, 0.0, clip.w)). A golden that allocates its own camera and target validates the shader, not the integration. - Declared numbers must be gates.
MANIFEST.toml's per-asset error budgets and sha256 values are now all asserted, and the ΔT residual is gated against Horizons' ownTDB−UT/UT1−UTCcolumns rather than asserted in prose. The V-band photometric zero point was Allen's spectral flux density (3.6e−8 W m⁻² µm⁻¹) mislabelled as an irradiance — 11× too large, and invisible to every test because it cancels in the magnitude ratio the renderer uses. Now the band-integrated 3.19e−9 W m⁻², anchored by a test that the Sun's V-band irradiance stays below the solar constant. - Redistributed third-party data needs its notice shipped with it:
moon_terms.binis MIT-licensed and compiled into every wheel viainclude_bytes!, soassets/astro/THIRD_PARTY_NOTICES.mdnow carries the notice and the manifest points at it.
TESSELLA stream unification (2026-07-27)
- Height is the fourth feedback-driven VT family.
HeightVtFamilyRuntimekeeps the R32FloatHeightMosaicphysical representation required for numeric elevation, while demand retention,TileKeyidentity, family budgets, residency accounting, publicvt_stats(), and store-backed loading use the same policy as albedo, normal, and mask. Material feedback UVs feed height demand; camera ring requests are predictive prefetch. Both caller DEMs (ReaderPageStore) and COGs (CogPageStore) enterAsyncTileLoaderthroughVirtualTextureStore::page; shipped synthetic-reader callers remain forbidden. - Material VT and COG height input share
VirtualTextureStore::page. Packed material pages stay BC7/BC5 through the staging ring into a bindless three-texture atlas when the negotiated adapter grants BC compression and descriptor indexing; raw/single-atlas paths are explicit certificate degradations.
CHRONOS 1.40 integration (2026-09-30)
- Glyph quads overlap, so a per-quad fade alpha is rounded once per quad. Atlas cells (20 px) are wider than the advance (~7 px). Blending each quad into the 8-bit target at the CHRONOS fade alpha rounded overlap pixels twice and moved a channel 2 codes in one frame (all 17 such pixels lay under two or more quads). Fade a label run as one layer: draw it at full opacity, then
B + a * (over(L, B) - B), rounded once. Full-opacity output stays byte-identical. - With 8-bit output, "< 1/255 per frame" cannot hold for a 1/256 fade step: a pixel blended once can round all three channels in the same frame, a luminance step of exactly 1/255. Float Rec.709 sums land 1 ulp either side of 1/255, so compare luminance in integer weights (2126/7152/722) and gate channel steps exactly.
- A stacked-base branch cannot be integrated by merging its base.
paris-eiffel-day-ptwas 325 commits behind main with 51 conflicts, including goldens and hashes, and PR #189's commits were mostly Paris-line commits. Rebuild the feature as new history on main from the resolved tree, drop hunks whosegit log -Sorigin is the base line, and record every kept or dropped hunk. Check the base withgit merge-tree --write-tree origin/main <base>first: CI merges the candidate into main in preflight, so a conflicting base produces no CI evidence. - Check which lane a failing CI job belongs to before bisecting. The "Visual Goldens" Metal failure (SSIM 0.000245) came from the Paris line's old hosted macOS job, which main's
ci.ymlno longer has. Rendered from the rebuilt head on the RTX 3070 (Vulkan), both atmosphere scenes were byte-identical to main. main's only Metal terrain lane requiresvars.FORGE3D_RUN_METAL_DIAGNOSTIC == '1'; the variable istrue, so that job never runs. - Hosted macOS gives no Metal evidence.
macos-14exposes only the "Apple Paravirtual device";terrain_ci_probe.pyrecords it ABSENT, so the Metal golden job skips its Metal steps and the SUBSTRATIA diagnostic fails when enabled. Checking a.metal.pnggolden needs a physical Mac; keepFORGE3D_RUN_METAL_DIAGNOSTICopt-in with'1'. - Goldens rendered on a stacked base do not carry over to main. The #190 recipe goldens came from the RTX 3070 on the Paris line; rebuilt on main, the terrain under the overlay and strokes differs (SSIM 0.968-0.994). Re-render every golden a rebuild touches on the lane that owns it, and check that the local render is pixel-identical to that lane's CI artifact before committing it.
- Canonical recipe goldens are compared only by
certificate-refresh.yml(main-only, the RTX 3070, no variant). CI's NVIDIA lane checks onlymapscene_terrain_raster.nvidia-vulkan.png. Never regenerate withFORGE3D_UPDATE_RECIPE_GOLDENS=1outside that workflow: it also re-signs certificates with the dev key. Copy the rendered pixels instead. - Compare every failing job's test IDs with main's nightly before fixing. At 1.40.0, main's full run already fails Test Rust (
det_instrument_rewrite), ORBIS Wasm, TESSELLA (6 IDs), ANAMNESIS seed, determinism (NVIDIA hashba4b97b6…against the committed4aaa5bf6…), and ORBIS evidence (the self-hosted runner's tracked TIFFs are dirty, which skips every later NVIDIA gate). - Git Bash mangles
rev:patharguments that start with.(refs/x:.github/...became a Windows path andgit showreturned nothing). SetMSYS_NO_PATHCONV=1forgit show/merge-filescripting.
TERRA-DETERMINATA rewrite rerun (2026-10-02)
- Any shader change in
terrain_parts()needs a determinism-lint rerun. ORBIS (#141) added terrain WGSL without rerunningFORGE3D_DET_REWRITE=1 cargo test det_instrument_rewrite --lib, which left Test Rust red on every OS and hid the doctests and clippy behind it. Applying the rewrite also changesPINNED_TERRAIN_SOURCE_HASH(owner approval), and its spelling can break the source-text checks inshader_sources::tests(see thedet_rewrite.rsheader). - Barriers can be byte-neutral, but prove it with bytes, not tolerances. The golden gates allow SSIM 0.995 / mean abs 2.0, so a pass does not show identical pixels. Hash every rendered array from an unmodified-main wheel and the candidate wheel and compare them; the 2026-10-02 rerun matched exactly on the RTX 3070 (Vulkan).
- Three
test_determinism_hash.pysubprocess tests fail on an installed-wheel lane (FORGE3D_TEST_INSTALLED_WHEEL=1):test_determinism_probe_canary_is_repeatable,test_dupla_dd_demo_is_backend_pinned_and_byte_identical, andtest_device_probe_reports_initialized_render_adapterprepend the repo'spython/toPYTHONPATH, which has no native extension, so they reportnative_missing. Treat them as environment failures and compare them with the baseline run. - The pre-commit hook runs
cargo forge3d-clippywith the shell'sCARGO_TARGET_DIR. When committing from a.worktrees/checkout, setCARGO_TARGET_DIRto the worktree's target dir for the commit, or the hook builds into the shared main target.
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 28,693 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

