Add CSG demand frontier counters
This commit is contained in:
parent
09757f66cb
commit
6bb8726a37
9 changed files with 301 additions and 25 deletions
|
|
@ -87,6 +87,8 @@ fn run_case(case: PerfCase, mode: PerfMode) {
|
|||
let mut triangles = 0;
|
||||
let mut fragments = 0;
|
||||
let mut warnings = 0;
|
||||
let mut candidate_pairs = 0;
|
||||
let mut rejected_pairs = 0;
|
||||
|
||||
for _ in 0..MEASURE_ITERS {
|
||||
let start = Instant::now();
|
||||
|
|
@ -95,7 +97,15 @@ fn run_case(case: PerfCase, mode: PerfMode) {
|
|||
triangles = output.mesh.triangle_count();
|
||||
fragments = output.report.emitted_convex_fragments;
|
||||
warnings = output.report.warnings.len();
|
||||
black_box((triangles, fragments, warnings));
|
||||
candidate_pairs = output.report.candidate_pairs;
|
||||
rejected_pairs = output.report.rejected_pairs;
|
||||
black_box((
|
||||
triangles,
|
||||
fragments,
|
||||
warnings,
|
||||
candidate_pairs,
|
||||
rejected_pairs,
|
||||
));
|
||||
timings.push(elapsed);
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +120,7 @@ fn run_case(case: PerfCase, mode: PerfMode) {
|
|||
let p95_ns = percentile_ns(&timings, 95);
|
||||
|
||||
println!(
|
||||
"{{\"kernel\":\"vg_csg\",\"mode\":\"{}\",\"scenario\":\"{}\",\"brushes\":{},\"iterations\":{},\"warmup_iterations\":{},\"mean_ns\":{},\"min_ns\":{},\"p50_ns\":{},\"p95_ns\":{},\"max_ns\":{},\"triangles\":{},\"fragments\":{},\"warnings\":{}}}",
|
||||
"{{\"kernel\":\"vg_csg\",\"mode\":\"{}\",\"scenario\":\"{}\",\"brushes\":{},\"iterations\":{},\"warmup_iterations\":{},\"mean_ns\":{},\"min_ns\":{},\"p50_ns\":{},\"p95_ns\":{},\"max_ns\":{},\"triangles\":{},\"fragments\":{},\"warnings\":{},\"candidate_pairs\":{},\"rejected_pairs\":{}}}",
|
||||
mode.as_str(),
|
||||
case.name,
|
||||
case.brushes,
|
||||
|
|
@ -123,7 +133,9 @@ fn run_case(case: PerfCase, mode: PerfMode) {
|
|||
max_ns,
|
||||
triangles,
|
||||
fragments,
|
||||
warnings
|
||||
warnings,
|
||||
candidate_pairs,
|
||||
rejected_pairs
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ pub enum BuildWarning {
|
|||
pub struct BuildReport {
|
||||
pub input_brushes: usize,
|
||||
pub emitted_convex_fragments: usize,
|
||||
pub operator_brushes: usize,
|
||||
pub candidate_pairs: usize,
|
||||
pub rejected_pairs: usize,
|
||||
pub warnings: Vec<BuildWarning>,
|
||||
}
|
||||
|
||||
|
|
@ -256,38 +259,49 @@ impl Assembler {
|
|||
),
|
||||
},
|
||||
BrushOp::Subtract => {
|
||||
report.operator_brushes += 1;
|
||||
if !brush
|
||||
.bounds
|
||||
.intersects_any(solids.iter().map(|solid| solid.bounds))
|
||||
{
|
||||
report.rejected_pairs += solids.len();
|
||||
continue;
|
||||
}
|
||||
if let Some(cutter) = brush.convex_cutter() {
|
||||
solids = subtract_from_solids(solids, &cutter);
|
||||
solids = subtract_from_solids(solids, &cutter, &mut report);
|
||||
} else {
|
||||
report
|
||||
.warnings
|
||||
.push(BuildWarning::SubtractIgnoredForNonBox {
|
||||
brush: brush.name.clone(),
|
||||
});
|
||||
let has_candidate = record_solid_pairs(&solids, brush.bounds, &mut report);
|
||||
if has_candidate {
|
||||
report
|
||||
.warnings
|
||||
.push(BuildWarning::SubtractIgnoredForNonBox {
|
||||
brush: brush.name.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
BrushOp::Intersect => {
|
||||
report.operator_brushes += 1;
|
||||
if !brush
|
||||
.bounds
|
||||
.intersects_any(solids.iter().map(|solid| solid.bounds))
|
||||
{
|
||||
report.rejected_pairs += solids.len();
|
||||
solids.clear();
|
||||
continue;
|
||||
}
|
||||
if let Some(cutter) = brush.convex_cutter() {
|
||||
solids = intersect_solids(solids, &cutter);
|
||||
solids = intersect_solids(solids, &cutter, &mut report);
|
||||
} else {
|
||||
report
|
||||
.warnings
|
||||
.push(BuildWarning::IntersectIgnoredForNonBox {
|
||||
brush: brush.name.clone(),
|
||||
});
|
||||
let has_candidate = record_solid_pairs(&solids, brush.bounds, &mut report);
|
||||
solids.clear();
|
||||
if has_candidate {
|
||||
report
|
||||
.warnings
|
||||
.push(BuildWarning::IntersectIgnoredForNonBox {
|
||||
brush: brush.name.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -319,12 +333,15 @@ impl Assembler {
|
|||
match brush.op {
|
||||
BrushOp::Add => boxes.push((bounds, brush.material)),
|
||||
BrushOp::Subtract => {
|
||||
report.operator_brushes += 1;
|
||||
if !bounds.intersects_any(boxes.iter().map(|(bounds, _)| *bounds)) {
|
||||
report.rejected_pairs += boxes.len();
|
||||
continue;
|
||||
}
|
||||
let mut out = Vec::with_capacity(boxes.len() + 4);
|
||||
for (solid, material) in boxes {
|
||||
if solid.intersects(bounds) {
|
||||
report.candidate_pairs += 1;
|
||||
out.extend(
|
||||
solid
|
||||
.subtract_box(bounds)
|
||||
|
|
@ -332,20 +349,29 @@ impl Assembler {
|
|||
.map(|piece| (piece, material)),
|
||||
);
|
||||
} else {
|
||||
report.rejected_pairs += 1;
|
||||
out.push((solid, material));
|
||||
}
|
||||
}
|
||||
boxes = out;
|
||||
}
|
||||
BrushOp::Intersect => {
|
||||
report.operator_brushes += 1;
|
||||
if !bounds.intersects_any(boxes.iter().map(|(bounds, _)| *bounds)) {
|
||||
report.rejected_pairs += boxes.len();
|
||||
boxes.clear();
|
||||
continue;
|
||||
}
|
||||
boxes = boxes
|
||||
.into_iter()
|
||||
.filter_map(|(solid, material)| {
|
||||
solid.intersection(bounds).map(|hit| (hit, material))
|
||||
if let Some(hit) = solid.intersection(bounds) {
|
||||
report.candidate_pairs += 1;
|
||||
Some((hit, material))
|
||||
} else {
|
||||
report.rejected_pairs += 1;
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
|
@ -463,24 +489,36 @@ impl CompiledBrush {
|
|||
}
|
||||
}
|
||||
|
||||
fn subtract_from_solids(solids: Vec<ConvexSolid>, cutter: &ConvexSolid) -> Vec<ConvexSolid> {
|
||||
fn subtract_from_solids(
|
||||
solids: Vec<ConvexSolid>,
|
||||
cutter: &ConvexSolid,
|
||||
report: &mut BuildReport,
|
||||
) -> Vec<ConvexSolid> {
|
||||
let mut out = Vec::with_capacity(solids.len() + 4);
|
||||
for solid in solids {
|
||||
if solid.bounds.intersects(cutter.bounds) {
|
||||
report.candidate_pairs += 1;
|
||||
out.extend(solid.subtract_convex_owned(cutter));
|
||||
} else {
|
||||
report.rejected_pairs += 1;
|
||||
out.push(solid);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn intersect_solids(solids: Vec<ConvexSolid>, cutter: &ConvexSolid) -> Vec<ConvexSolid> {
|
||||
fn intersect_solids(
|
||||
solids: Vec<ConvexSolid>,
|
||||
cutter: &ConvexSolid,
|
||||
report: &mut BuildReport,
|
||||
) -> Vec<ConvexSolid> {
|
||||
let mut out = Vec::with_capacity(solids.len());
|
||||
for solid in solids {
|
||||
if !solid.bounds.intersects(cutter.bounds) {
|
||||
report.rejected_pairs += 1;
|
||||
continue;
|
||||
}
|
||||
report.candidate_pairs += 1;
|
||||
if let Some(fragment) = solid.intersect_convex_owned(cutter) {
|
||||
out.push(fragment);
|
||||
}
|
||||
|
|
@ -488,6 +526,19 @@ fn intersect_solids(solids: Vec<ConvexSolid>, cutter: &ConvexSolid) -> Vec<Conve
|
|||
out
|
||||
}
|
||||
|
||||
fn record_solid_pairs(solids: &[ConvexSolid], bounds: Aabb, report: &mut BuildReport) -> bool {
|
||||
let mut has_candidate = false;
|
||||
for solid in solids {
|
||||
if solid.bounds.intersects(bounds) {
|
||||
report.candidate_pairs += 1;
|
||||
has_candidate = true;
|
||||
} else {
|
||||
report.rejected_pairs += 1;
|
||||
}
|
||||
}
|
||||
has_candidate
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy_math::Vec3;
|
||||
|
|
@ -567,6 +618,9 @@ mod tests {
|
|||
|
||||
let output = asm.build();
|
||||
assert_eq!(output.report.emitted_convex_fragments, 1);
|
||||
assert_eq!(output.report.operator_brushes, 1);
|
||||
assert_eq!(output.report.candidate_pairs, 0);
|
||||
assert_eq!(output.report.rejected_pairs, 1);
|
||||
assert_eq!(output.mesh.triangle_count(), 12);
|
||||
}
|
||||
|
||||
|
|
@ -589,6 +643,9 @@ mod tests {
|
|||
|
||||
let output = asm.build();
|
||||
assert_eq!(output.report.emitted_convex_fragments, 1);
|
||||
assert_eq!(output.report.operator_brushes, 1);
|
||||
assert_eq!(output.report.candidate_pairs, 1);
|
||||
assert_eq!(output.report.rejected_pairs, 0);
|
||||
assert_eq!(output.report.warnings.len(), 0);
|
||||
assert_eq!(output.mesh.triangle_count(), 12);
|
||||
}
|
||||
|
|
@ -616,7 +673,16 @@ mod tests {
|
|||
let cached = asm.build();
|
||||
let dirty = asm.rebuild();
|
||||
|
||||
assert_eq!(stable.report.emitted_convex_fragments, dirty.report.emitted_convex_fragments);
|
||||
assert_eq!(
|
||||
stable.report.emitted_convex_fragments,
|
||||
dirty.report.emitted_convex_fragments
|
||||
);
|
||||
assert_eq!(
|
||||
stable.report.operator_brushes,
|
||||
dirty.report.operator_brushes
|
||||
);
|
||||
assert_eq!(stable.report.candidate_pairs, dirty.report.candidate_pairs);
|
||||
assert_eq!(stable.report.rejected_pairs, dirty.report.rejected_pairs);
|
||||
assert_eq!(stable.report.warnings, dirty.report.warnings);
|
||||
assert_eq!(stable.mesh.triangle_count(), dirty.mesh.triangle_count());
|
||||
assert_eq!(stable.mesh.vertex_count(), dirty.mesh.vertex_count());
|
||||
|
|
|
|||
167
crates/vg_csg/src/frontier.rs
Normal file
167
crates/vg_csg/src/frontier.rs
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
use crate::{Brush, BrushOp};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct DemandPair {
|
||||
pub source_index: usize,
|
||||
pub operator_index: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct DemandFrontier {
|
||||
pub pairs: Vec<DemandPair>,
|
||||
pub rejected_pairs: usize,
|
||||
pub operator_brushes: usize,
|
||||
}
|
||||
|
||||
impl DemandFrontier {
|
||||
pub fn from_ordered_brushes(brushes: &[Brush]) -> Self {
|
||||
let mut frontier = Self::default();
|
||||
let mut active_sources = Vec::<usize>::new();
|
||||
|
||||
for (operator_index, brush) in brushes.iter().enumerate() {
|
||||
match brush.op {
|
||||
BrushOp::Add => active_sources.push(operator_index),
|
||||
BrushOp::Subtract => {
|
||||
frontier.operator_brushes += 1;
|
||||
frontier.collect_pairs(brushes, &active_sources, operator_index);
|
||||
}
|
||||
BrushOp::Intersect => {
|
||||
frontier.operator_brushes += 1;
|
||||
let retained = frontier.collect_pairs(brushes, &active_sources, operator_index);
|
||||
active_sources = retained;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frontier
|
||||
}
|
||||
|
||||
pub fn candidate_pairs(&self) -> usize {
|
||||
self.pairs.len()
|
||||
}
|
||||
|
||||
fn collect_pairs(
|
||||
&mut self,
|
||||
brushes: &[Brush],
|
||||
active_sources: &[usize],
|
||||
operator_index: usize,
|
||||
) -> Vec<usize> {
|
||||
let operator_bounds = brushes[operator_index].bounds();
|
||||
let mut retained = Vec::new();
|
||||
|
||||
for &source_index in active_sources {
|
||||
if brushes[source_index].bounds().intersects(operator_bounds) {
|
||||
self.pairs.push(DemandPair {
|
||||
source_index,
|
||||
operator_index,
|
||||
});
|
||||
retained.push(source_index);
|
||||
} else {
|
||||
self.rejected_pairs += 1;
|
||||
}
|
||||
}
|
||||
|
||||
retained
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy_math::{Quat, Vec3};
|
||||
|
||||
use super::*;
|
||||
use crate::{Aabb, Brush, MaterialId, Primitive};
|
||||
|
||||
fn brush(name: &str, op: BrushOp, bounds: Aabb) -> Brush {
|
||||
Brush::new(
|
||||
crate::BrushId(0),
|
||||
name,
|
||||
op,
|
||||
Primitive::Box { bounds },
|
||||
MaterialId(1),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frontier_keeps_only_touching_ordered_pairs() {
|
||||
let brushes = vec![
|
||||
brush(
|
||||
"source",
|
||||
BrushOp::Add,
|
||||
Aabb::from_center_size(Vec3::ZERO, Vec3::splat(4.0)),
|
||||
),
|
||||
brush(
|
||||
"far cutter",
|
||||
BrushOp::Subtract,
|
||||
Aabb::from_center_size(Vec3::splat(20.0), Vec3::ONE),
|
||||
),
|
||||
brush(
|
||||
"near cutter",
|
||||
BrushOp::Subtract,
|
||||
Aabb::from_center_size(Vec3::ZERO, Vec3::ONE),
|
||||
),
|
||||
];
|
||||
|
||||
let frontier = DemandFrontier::from_ordered_brushes(&brushes);
|
||||
|
||||
assert_eq!(frontier.operator_brushes, 2);
|
||||
assert_eq!(frontier.rejected_pairs, 1);
|
||||
assert_eq!(
|
||||
frontier.pairs,
|
||||
vec![DemandPair {
|
||||
source_index: 0,
|
||||
operator_index: 2,
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection_frontier_reduces_active_sources() {
|
||||
let brushes = vec![
|
||||
brush(
|
||||
"a",
|
||||
BrushOp::Add,
|
||||
Aabb::from_center_size(Vec3::ZERO, Vec3::splat(2.0)),
|
||||
),
|
||||
brush(
|
||||
"b",
|
||||
BrushOp::Add,
|
||||
Aabb::from_center_size(Vec3::new(20.0, 0.0, 0.0), Vec3::splat(2.0)),
|
||||
),
|
||||
Brush::new(
|
||||
crate::BrushId(2),
|
||||
"common",
|
||||
BrushOp::Intersect,
|
||||
Primitive::OrientedBox {
|
||||
center: Vec3::ZERO,
|
||||
size: Vec3::splat(3.0),
|
||||
rotation: Quat::IDENTITY,
|
||||
},
|
||||
MaterialId(1),
|
||||
),
|
||||
brush(
|
||||
"cut",
|
||||
BrushOp::Subtract,
|
||||
Aabb::from_center_size(Vec3::ZERO, Vec3::ONE),
|
||||
),
|
||||
];
|
||||
|
||||
let frontier = DemandFrontier::from_ordered_brushes(&brushes);
|
||||
|
||||
assert_eq!(frontier.operator_brushes, 2);
|
||||
assert_eq!(frontier.rejected_pairs, 1);
|
||||
assert_eq!(
|
||||
frontier.pairs,
|
||||
vec![
|
||||
DemandPair {
|
||||
source_index: 0,
|
||||
operator_index: 2,
|
||||
},
|
||||
DemandPair {
|
||||
source_index: 0,
|
||||
operator_index: 3,
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ mod assembler;
|
|||
mod brush;
|
||||
mod convex;
|
||||
mod dsl;
|
||||
mod frontier;
|
||||
mod mesh;
|
||||
mod primitives;
|
||||
mod tree;
|
||||
|
|
@ -18,6 +19,7 @@ pub use assembler::{Assembler, BuildOutput, BuildReport, BuildWarning};
|
|||
pub use brush::{Aabb, Brush, BrushId, BrushOp, MaterialId, PolygonCategory, Primitive};
|
||||
pub use convex::{CategorizedPolygons, ConvexPolygon, ConvexSolid, Plane};
|
||||
pub use dsl::LevelDsl;
|
||||
pub use frontier::{DemandFrontier, DemandPair};
|
||||
pub use mesh::TriangleMesh;
|
||||
pub use primitives::{
|
||||
DomeCapZSpec, FloretArmSpec, append_cylinder_z, append_dome_cap_z, append_floret_arm,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ The script builds `vg_csg` in release mode and writes JSONL to
|
|||
- warmup and measured iteration counts
|
||||
- `mean_ns`, `min_ns`, `p50_ns`, `p95_ns`, `max_ns`
|
||||
- emitted triangle, fragment, and warning counts
|
||||
- demand-frontier `candidate_pairs` and `rejected_pairs`
|
||||
|
||||
The `vg_csg` fixture measures the stable-tree path after warmup. `Assembler`
|
||||
caches evaluated output by generation, so repeated `build()` calls on an
|
||||
|
|
@ -85,10 +86,19 @@ inside/outside/aligned/reverse-aligned categories, crossing polygon splits, and
|
|||
logical routing tables. Use it as readable doctrine. Use the native bridge as
|
||||
the performance target.
|
||||
|
||||
`vg_csg` now reports the first demand-frontier counters. These are not yet a
|
||||
new kernel path; they expose the ordered brush stream as affected source/operator
|
||||
pairs and rejected bounds pairs so future router/index work can prove it is
|
||||
refusing real work rather than merely rearranging it.
|
||||
|
||||
## Latest Local Baseline
|
||||
|
||||
Captured on 2026-05-07 with `.\tools\run_csg_perf.ps1 -UseRealtimeCsgCpp`:
|
||||
|
||||
This baseline predates the `candidate_pairs`/`rejected_pairs` fields. New
|
||||
fixture runs include them; keep the old block until a fresh C++ comparison is
|
||||
captured rather than mixing timing environments.
|
||||
|
||||
```jsonl
|
||||
{"kernel":"vg_csg","mode":"stable","scenario":"single_center_cut","brushes":2,"iterations":64,"warmup_iterations":8,"mean_ns":948,"min_ns":800,"p50_ns":900,"p95_ns":1100,"max_ns":1200,"triangles":72,"fragments":6,"warnings":0}
|
||||
{"kernel":"vg_csg","mode":"dirty","scenario":"single_center_cut","brushes":2,"iterations":64,"warmup_iterations":8,"mean_ns":6123,"min_ns":4300,"p50_ns":5500,"p95_ns":6000,"max_ns":44700,"triangles":72,"fragments":6,"warnings":0}
|
||||
|
|
|
|||
|
|
@ -80,11 +80,11 @@ Repo-local Geometry Script clone notes live in
|
|||
`.\tools\setup_geometry_script_clone.ps1` if `external/geometry-script` is
|
||||
missing.
|
||||
|
||||
The current next action is to replace the ordered `vg_csg` dense rotated
|
||||
subtraction path with the category-router kernel, then prototype a
|
||||
demand-shaped affected-pair frontier before adding any generic BVH/grid spatial
|
||||
index. Keep cached-vs-dirty output parity tests green and compare against the
|
||||
C++ timing oracle.
|
||||
The current next action is to use the new demand-frontier counters to replace
|
||||
the ordered `vg_csg` dense rotated subtraction path with a category-router
|
||||
kernel. Keep cached-vs-dirty output parity tests green, compare against the C++
|
||||
timing oracle, and only add BVH/grid storage after frontier batching proves it
|
||||
needs one.
|
||||
|
||||
Spatial-query correction from EpiphanyAquarium memory: GigaVoxels, froxels, and
|
||||
Dreams all point away from static "better tree" thinking. Let the consumer
|
||||
|
|
@ -93,6 +93,11 @@ tile/view, or visible surface set. BVHs, grids, sweep-and-prune, and pair caches
|
|||
are candidate layouts only after that frontier exists and timing evidence says
|
||||
they help.
|
||||
|
||||
First implementation slice: `vg_csg` exports `DemandFrontier`/`DemandPair`, and
|
||||
`BuildReport` plus the JSONL perf fixture now expose `candidate_pairs` and
|
||||
`rejected_pairs`. These counters are currently observational and follow existing
|
||||
bounds gates; they are not yet a replacement router.
|
||||
|
||||
## Important Invariants
|
||||
|
||||
- Target Blender 5.0+ unless the task says otherwise.
|
||||
|
|
|
|||
|
|
@ -43,6 +43,19 @@ not carry live next-action clutter.
|
|||
|
||||
## Near-Term Sequence
|
||||
|
||||
0. Make the Rust CSG machine meaner and leaner.
|
||||
- Replace the dense rotated subtraction carver with a category-router path
|
||||
that preserves source polygons, splits only crossings, routes categories,
|
||||
and keeps cached-vs-dirty output parity green before performance claims.
|
||||
- Add a demand-frontier prototype before any generic BVH/grid index. The
|
||||
frontier should emit affected brush pairs from dirty brushes, branch
|
||||
bounds, and requested output scope; indexes are candidate storage layouts,
|
||||
not the idea.
|
||||
- Add counters and fixtures for global rebuild, affected-brush rebuild, and
|
||||
request/tile-scoped rebuild so timings explain what work was refused.
|
||||
- Move hot polygon/category work toward reusable scratch arenas and compact
|
||||
arrays only after the router proves exact output parity.
|
||||
|
||||
1. Promote the Bloom coordinate/noise lessons into reusable builder seams.
|
||||
- Build on the new `vibegeometry/` helper library rather than adding more
|
||||
one-off example functions.
|
||||
|
|
|
|||
|
|
@ -62,3 +62,4 @@
|
|||
{"ts": "2026-05-07T02:49:43+00:00", "type": "implementation", "status": "ok", "note": "CSG speed pass kept parity tests green while adding an axis-aligned box fast path, compiled brush geometry cache, owned convex splitting, clipping allocation reserves, and stable-generation output caching. Stable-tree fixture now beats or matches the direct C++ bridge on sampled scenarios, but dirty dense rotated subtraction still needs category-router work."}
|
||||
{"ts": "2026-05-07T03:31:28+00:00", "type": "implementation", "status": "ok", "note": "Added explicit stable and dirty vg_csg perf modes, public rebuild() bypassing stable output cache, cached-vs-dirty output parity test, and convex containment shortcuts. Latest fixture shows dirty box-heavy and distant-rejection paths are respectable; dirty dense rotated subtraction remains the next category-router target."}
|
||||
{"ts": "2026-05-07T06:57:45+00:00", "type": "research", "status": "accepted", "note": "EpiphanyAquarium persisted memory changes the vg_csg acceleration prior: before adding a generic BVH/grid/sweep index, prototype demand-shaped spatial frontiers driven by dirty brushes, branch bounds, requested output scope, and visible/tile surfaces. GigaVoxels/froxel/Dreams doctrine treats rays or consumers as the query oracle; indexes are storage layouts for that frontier, not the central idea."}
|
||||
{"ts": "2026-05-07T07:19:04+00:00", "type": "implementation", "status": "ok", "note": "Started the meaner vg_csg pass: persisted the CSG roadmap, added exported DemandFrontier/DemandPair for ordered brush affected-pair planning, and added BuildReport/perf JSONL candidate_pairs and rejected_pairs counters along existing bounds gates. Tests/clippy pass; perf counters show rotated_cut_stack_64 still burns many rejected fragment/operator checks, so category-router plus frontier batching remains the next code target."}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ data_flow:
|
|||
|
||||
current_status:
|
||||
summary: VibeGeometry has verified Geometry Script translations for local samples, official Blender demos, and IRCSS French Houses footholds, plus from-scratch Aetheria Bloom and Lucent tether habitat builds. Durable lessons now driving the pipeline: Python orchestrates intent and batching; Geometry Script emits inspectable node groups; source-bound scenes need explicit coordinate contracts before ornament; spin-gravity habitats need ground planes orthogonal to their tether/rotation axis; utility petals, radiators, and solar arrays belong outside pressure glass and may need Citadel-scale presence; large habitat parts need visible load paths across coordinate frames; organic city maps need street fields, irregular parcels, parks, and land-use gradients rather than grids; dense detail must be batched; institutional spaces should model power as surfaces, rails, overlays, thresholds, shader behavior, and camera evidence; and visual acceptance needs domain-facing inspection renders plus verifier checks, not only overview renders. The first reusable `vibegeometry/` helper library now carries coordinate frames, fields, batching, Blender scene primitives, and verifier helpers used by the Bloom and Lucent builds. The Rust crate `vg_csg` now has a RealtimeCSG-shaped API skeleton plus a convex-brush stepping-stone kernel: CSG operation ordinals, branch vocabulary, tree/branch/brush handles, child and operation mutation, exact parity fixtures, ordered assembler compilation, AABB/oriented-box subtraction/intersection via convex plane splitting, crossing polygon category buckets, primitive/solid/tree bounds, additive cylinder/dome/floret primitives, JSONL performance fixtures with stable and dirty modes, mesh-array output, stable-generation output caching, tests, clippy, and examples. The `vg_grammar` crate is the Dream Machine authoring layer: semantic solid/void claims with tags compile into flat `vg_csg` brushes or generated CSG tree intent; it supports local frames, oriented claims, deterministic seeded variation, rule composition, and generated gallery chains. Public RealtimeCSG demo/source research points the target away from fragment carving and toward faster polygon classification through boolean trees. A standalone RealtimeCSG C++ P/Invoke bridge now mirrors the managed wrapper surface, verifies a native health cube, extracts generated mesh descriptions, and appends direct native timing records. Current perf pass: `vg_csg` has a fast axis-aligned path, cached compiled brush geometry, owned convex splitting, convex containment shortcuts, stable-tree output caching, and stable/dirty perf records; dirty box-heavy and distant-rejection paths are respectable, but dirty dense rotated subtraction still needs the category-router kernel. Known graph-tooling blockers remain repeat-zone feedback, repeat Top outputs, foreach zones, and gizmo nodes.
|
||||
next_action: Replace the ordered `vg_csg` dense rotated subtraction path with the category-router kernel, then prototype a demand-shaped affected-pair frontier before adding any generic BVH/grid spatial index; keep cached-vs-dirty output parity tests green and compare against the C++ timing oracle.
|
||||
next_action: Use the new demand-frontier counters to replace the ordered `vg_csg` dense rotated subtraction path with a category-router kernel; keep cached-vs-dirty output parity tests green, compare against the C++ timing oracle, and only add BVH/grid storage after frontier batching proves it needs one.
|
||||
remotes:
|
||||
vibegeometry: https://github.com/GameCult/VibeGeometry
|
||||
geometry_script_fork: https://github.com/GameCult/geometry-script
|
||||
|
|
|
|||
Loading…
Reference in a new issue