- weld: Δ UV = 0.5 exact n'est plus soudé (ambigu: fente à sa plus large
vs saut légitime — la colonne u=1 du cône vs le chart disque du cap
tombait exactement dessus et mélangeait les charts)
- collapse: les UV se blendent linéairement. Le fold par coordonnée
(Δ entier → 0) figeait l'UV du sommet sur les vertices de base
(bande de rayures, signalée par l'utilisateur) — il était inutile:
les jumeaux de fente sont gelés, aucun repli ne traverse la fente
- welded renvoie un struct Welded (clippy)
- test cône: dual-chart (fan latéral bilinéaire + disque du cap),
surface latérale r = λ; test seam/span réécrit selon la sémantique finale
- doc: gpu-driven.md, ARCHI_CPU_GPU.md (box LOD), DRAFT.md (D10) alignés
sur la sémantique finale (gel des jumeaux, blend linéaire, seuil strict)
- AGENTS.md: gotcha 'ne jamais folder un saut entier de tuile'
102 tests passent, demo lance et rend sans erreur.
Root cause of the user-reported artifacts (stripes disappearing on the
far LOD): normals were RECOMPUTED from the surviving faces after the
collapse. uv_sphere is wound inward, so the recomputed normals pointed
inward — the far LOD was back-face-lit (measured deviation 2.0 vs 0.0
on L0).
Fix — normals are INHERITED, never recomputed:
- welded() now also welds normals (first-encountered per cluster)
- Collapse owns the normal table; collapse_edge λ-blends + renormalizes
at the same λ as the position (no seam guard: the attribute-aware
weld kept hard-edge vertices separate, so no edge crosses a shading
break)
- compaction reads the post-collapse table instead of recomputing
- the outward reorientation added earlier is removed: the source
winding + normals are preserved as-is, so every LOD level is
shading-compatible with L0 whatever the source orientation
Rim protection (attribute-aware weld leaves UV-seam slits / pole fans
as boundary rims): a face touching such a rim is never removed while
interior collapses remain — strict PQ mode (edges whose incident faces
are fully interior, re-validated at pop) with a best-effort fallback
when the interior alone cannot reach the target. Seam-free meshes
(icosahedron) stay topologically closed; sewn meshes stay geometrically
complete (no hole at the slit) — tests now assert seam-column survival.
Docs: gpu-driven.md §LOD, ARCHI_CPU_GPU LOD note, ROADMAP 4.3 updated
with the attribute-aware weld + rim protection + inherited normals.
Gate: fmt ✓, check 0 warnings ✓, 104 tests ✓, demo runs ✓.
Measured (uv_sphere 32×20): normal max deviation 0.0000 on L0–L2
(was 2.0000), UV max error 0.0084 vs analytical (was 0.5000).
Le vertex-cible d'un repli se déplace au point optimal de l'arête mais
conserveait l'UV du weld — désaccord position/UV croissant en cascade :
la texture 'fuit' et les motifs (rayures) disparaissent aux niveaux
lointains, avec un changement radical entre deux LOD.
- Collapse porte désormais les tables uvs/colors (clonées au weld).
- collapse_edge interpole les UVs de la cible : uv_t ← (1−λ)·uv_s + λ·uv_t,
avec le même λ que le déplacement (cost_and_point renvoie désormais λ).
- Garde-fou seam : si |Δu| > 0.5 ou |Δv| > 0.5 (saut de texture), la cible
garde son UV — l'interpolation ne traverse jamais une seam.
- Couleurs : toujours interpolées (espace colorimétrique continu).
- Compaction : la sortie lit les tables mises à jour (c.uvs/c.colors), pas
les tables d'origine du weld.
- Docs : DRAFT.md (Sortie), gpu-driven.md (décimination), ARCHI_CPU_GPU (LOD).
Rule A (area-sorted triangle removal) left holes and open boundaries
on closed meshes (visible artifacts when zoomed far out). The decimator
is now a proper quadric edge collapse:
- Collapse: welded u32 topology, per-vertex quadrics (accumulated
incident face planes), edge cost = quadric error at the optimal
point (clamped to the segment) + edge length, BinaryHeap with a
custom Ord (f32 is not Ord; inverted compare, NaN-safe).
- Standard GH semantics WITHOUT the new face: both incident pair faces
degenerate and are removed; neighbouring faces remap and sweep over
the region. Preserves the Euler characteristic and closedness (no
holes, no books, no duplicate faces); interior collapse = -2 faces,
boundary = -1. Guards: non-manifold edge (>2 faces) or a fold
(duplicate sorted triple) rejects the collapse.
- welded(): fuzzy welding (1e-6 relative tolerance, grid + 27-
neighbour broad phase, exact verify) - trig-generated seams differ
by ~1e-16, exact-bit welding missed them.
- Root causes fixed along the way: dead face slots are never reused
(stale edge/vface entries), vfaces updated on remap, degenerate
faces dropped, best-effort target (granularity -2/-1 can land 1-2
off; soft cap, deterministic).
- Docs: DRAFT D10, ROADMAP 4.3, ARCHI_CPU_GPU, gpu-driven, lib
README, mesh/scene/demo comments - 'greedy decimation / Rule A'
replaced by 'quadric edge collapse'.
- lod.rs test: deprecated glam perspective alias -> explicit
glam::camera::rh::proj::opengl::perspective.
cargo test --workspace: 100 passed (94 lib + 3 wgsl + 3 integration),
0 failed; demo runs clean with LOD on.
La lib n'a qu'un seul point de couplage au runtime (block_on dans app.rs) ; on ne crée volontairement
pas d'abstraction à ce stade. Consigné dans PLAN.md et ARCHI_APP.md : isoler derrière un module-pivot
unique si la lib acquiert d'autres appels async. Corrige aussi l'item check-list pollster devenu faux
dev-dependencies -> dependencies depuis la migration winit 0.30.
Clarify for each docs/tech document whether it describes the current
(implemented) or target (planned) architecture, and reconcile with the
readme/roadmap source of truth:
- ARCHI_APP, ARCHI_CPU_GPU, ARCHI_RENDU: mark status=target and add a
banner stating the GPU-driven two-pass pipeline, persistent VRAM buffers
and scene auto-render are not implemented (they are ROADMAP Phase 3 /
README roadmap items 1-3). render() still cannot draw the scene.
- ARCHI_ARENES: status=target/deferred. String IDs are the current design;
slotmap generational handles are the deferred 'typed handles' step and
the slotmap dependency is currently absent (supersedes the retired dep).
- FRAME_LOOP: stays status=current; correct the described API flow (Frame
based via get_next_frame/Renderer::present, plus the low-level
begin_frame/end_frame variant) and move the single-buffer VRAM note to
target.
- README: label each tech doc as current or target in the Documentation
section.