Reuse mesh for untouched CSG edits
This commit is contained in:
parent
eb97a5342f
commit
300a9b74e6
8 changed files with 127 additions and 35 deletions
|
|
@ -110,6 +110,7 @@ fn run_case(case: PerfCase, mode: PerfMode) {
|
|||
let mut warnings = 0;
|
||||
let mut candidate_pairs = 0;
|
||||
let mut rejected_pairs = 0;
|
||||
let mut reused_mesh = false;
|
||||
|
||||
for iteration in 0..MEASURE_ITERS {
|
||||
let start = Instant::now();
|
||||
|
|
@ -120,12 +121,14 @@ fn run_case(case: PerfCase, mode: PerfMode) {
|
|||
warnings = output.report.warnings.len();
|
||||
candidate_pairs = output.report.candidate_pairs;
|
||||
rejected_pairs = output.report.rejected_pairs;
|
||||
reused_mesh = output.report.reused_mesh;
|
||||
black_box((
|
||||
triangles,
|
||||
fragments,
|
||||
warnings,
|
||||
candidate_pairs,
|
||||
rejected_pairs,
|
||||
reused_mesh,
|
||||
));
|
||||
timings.push(elapsed);
|
||||
}
|
||||
|
|
@ -141,7 +144,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\":{},\"candidate_pairs\":{},\"rejected_pairs\":{}}}",
|
||||
"{{\"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\":{},\"reused_mesh\":{}}}",
|
||||
mode.as_str(),
|
||||
case.name,
|
||||
case.brushes,
|
||||
|
|
@ -156,7 +159,8 @@ fn run_case(case: PerfCase, mode: PerfMode) {
|
|||
fragments,
|
||||
warnings,
|
||||
candidate_pairs,
|
||||
rejected_pairs
|
||||
rejected_pairs,
|
||||
reused_mesh
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,13 +15,14 @@ pub enum BuildWarning {
|
|||
IntersectIgnoredForNonBox { brush: String },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
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 reused_mesh: bool,
|
||||
pub warnings: Vec<BuildWarning>,
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +89,7 @@ struct BoxBuildCache {
|
|||
checkpoints: Vec<BoxBuildState>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
struct BoxBuildState {
|
||||
boxes: Vec<(Aabb, MaterialId)>,
|
||||
report: BuildReport,
|
||||
|
|
@ -101,7 +102,7 @@ struct GeneralBuildCache {
|
|||
checkpoints: Vec<GeneralBuildState>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
struct GeneralBuildState {
|
||||
mesh: TriangleMesh,
|
||||
solids: Vec<ConvexSolid>,
|
||||
|
|
@ -157,7 +158,6 @@ impl Assembler {
|
|||
self.compiled.push(CompiledBrush::from_brush(&brush));
|
||||
self.brushes.push(brush);
|
||||
self.generation = self.generation.wrapping_add(1);
|
||||
self.cache.borrow_mut().take();
|
||||
self.mark_dirty_index(index);
|
||||
id
|
||||
}
|
||||
|
|
@ -170,7 +170,6 @@ impl Assembler {
|
|||
self.brushes[index].set_dirty();
|
||||
self.compiled[index] = CompiledBrush::from_brush(&self.brushes[index]);
|
||||
self.generation = self.generation.wrapping_add(1);
|
||||
self.cache.borrow_mut().take();
|
||||
self.mark_dirty_index(index);
|
||||
true
|
||||
}
|
||||
|
|
@ -183,7 +182,6 @@ impl Assembler {
|
|||
self.brushes[index].set_dirty();
|
||||
self.compiled[index] = CompiledBrush::from_brush(&self.brushes[index]);
|
||||
self.generation = self.generation.wrapping_add(1);
|
||||
self.cache.borrow_mut().take();
|
||||
self.mark_dirty_index(index);
|
||||
true
|
||||
}
|
||||
|
|
@ -452,6 +450,7 @@ impl Assembler {
|
|||
}
|
||||
|
||||
let brush_count = self.compiled.len();
|
||||
let previous_output = self.cache.borrow().as_ref().cloned();
|
||||
let rebuild_from = match first_dirty_index {
|
||||
Some(index) => index.min(brush_count),
|
||||
None => self
|
||||
|
|
@ -479,8 +478,9 @@ impl Assembler {
|
|||
|
||||
let mut checkpoints = (start_index == 0).then(|| vec![BoxBuildState::default()]);
|
||||
|
||||
let mut changed_geometry = start_index == 0;
|
||||
for brush in &self.compiled[start_index..] {
|
||||
state.apply_compiled_box(brush);
|
||||
changed_geometry |= state.apply_compiled_box(brush);
|
||||
if let Some(checkpoints) = &mut checkpoints {
|
||||
checkpoints.push(state.clone());
|
||||
}
|
||||
|
|
@ -497,11 +497,16 @@ impl Assembler {
|
|||
cache.valid_checkpoint_index = cache.valid_checkpoint_index.min(start_index);
|
||||
}
|
||||
|
||||
Some(state.into_output(self.brushes.len(), self.generation))
|
||||
let reused_mesh = (!changed_geometry)
|
||||
.then_some(previous_output)
|
||||
.flatten()
|
||||
.map(|output| output.mesh);
|
||||
Some(state.into_output_with_mesh(self.brushes.len(), self.generation, reused_mesh))
|
||||
}
|
||||
|
||||
fn build_general_from_cache(&self, first_dirty_index: Option<usize>) -> BuildOutput {
|
||||
let brush_count = self.compiled.len();
|
||||
let previous_output = self.cache.borrow().as_ref().cloned();
|
||||
let rebuild_from = match first_dirty_index {
|
||||
Some(index) => index.min(brush_count),
|
||||
None => self
|
||||
|
|
@ -529,8 +534,9 @@ impl Assembler {
|
|||
|
||||
let mut checkpoints = (start_index == 0).then(|| vec![GeneralBuildState::default()]);
|
||||
|
||||
let mut changed_geometry = start_index == 0;
|
||||
for brush in &self.compiled[start_index..] {
|
||||
state.apply_compiled_brush(brush);
|
||||
changed_geometry |= state.apply_compiled_brush(brush);
|
||||
if let Some(checkpoints) = &mut checkpoints {
|
||||
checkpoints.push(state.clone());
|
||||
}
|
||||
|
|
@ -547,7 +553,11 @@ impl Assembler {
|
|||
cache.valid_checkpoint_index = cache.valid_checkpoint_index.min(start_index);
|
||||
}
|
||||
|
||||
state.into_output(self.brushes.len(), self.generation)
|
||||
let reused_mesh = (!changed_geometry)
|
||||
.then_some(previous_output)
|
||||
.flatten()
|
||||
.map(|output| output.mesh);
|
||||
state.into_output_with_mesh(self.brushes.len(), self.generation, reused_mesh)
|
||||
}
|
||||
|
||||
pub fn sample_room_with_door() -> Self {
|
||||
|
|
@ -649,18 +659,21 @@ impl CompiledBrush {
|
|||
}
|
||||
|
||||
impl BoxBuildState {
|
||||
fn apply_compiled_box(&mut self, brush: &CompiledBrush) {
|
||||
fn apply_compiled_box(&mut self, brush: &CompiledBrush) -> bool {
|
||||
let CompiledGeometry::Box(bounds) = brush.geometry else {
|
||||
unreachable!("box build state received a non-box brush");
|
||||
};
|
||||
|
||||
match brush.op {
|
||||
BrushOp::Add => self.boxes.push((bounds, brush.material)),
|
||||
BrushOp::Add => {
|
||||
self.boxes.push((bounds, brush.material));
|
||||
true
|
||||
}
|
||||
BrushOp::Subtract => {
|
||||
self.report.operator_brushes += 1;
|
||||
if !bounds.intersects_any(self.boxes.iter().map(|(bounds, _)| *bounds)) {
|
||||
self.report.rejected_pairs += self.boxes.len();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut out = Vec::with_capacity(self.boxes.len() + 4);
|
||||
|
|
@ -679,13 +692,15 @@ impl BoxBuildState {
|
|||
}
|
||||
}
|
||||
self.boxes = out;
|
||||
true
|
||||
}
|
||||
BrushOp::Intersect => {
|
||||
self.report.operator_brushes += 1;
|
||||
if !bounds.intersects_any(self.boxes.iter().map(|(bounds, _)| *bounds)) {
|
||||
let changed = !self.boxes.is_empty();
|
||||
self.report.rejected_pairs += self.boxes.len();
|
||||
self.boxes.clear();
|
||||
return;
|
||||
return changed;
|
||||
}
|
||||
|
||||
self.boxes = self
|
||||
|
|
@ -701,16 +716,27 @@ impl BoxBuildState {
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_output(mut self, input_brushes: usize, generation: u64) -> BuildOutput {
|
||||
fn into_output_with_mesh(
|
||||
mut self,
|
||||
input_brushes: usize,
|
||||
generation: u64,
|
||||
reused_mesh: Option<TriangleMesh>,
|
||||
) -> BuildOutput {
|
||||
let mut mesh = TriangleMesh::new();
|
||||
self.report.input_brushes = input_brushes;
|
||||
self.report.emitted_convex_fragments = self.boxes.len();
|
||||
for (bounds, material) in self.boxes {
|
||||
mesh.append_box(bounds, material);
|
||||
if let Some(reused_mesh) = reused_mesh {
|
||||
self.report.reused_mesh = true;
|
||||
mesh = reused_mesh;
|
||||
} else {
|
||||
for (bounds, material) in self.boxes {
|
||||
mesh.append_box(bounds, material);
|
||||
}
|
||||
}
|
||||
|
||||
BuildOutput {
|
||||
|
|
@ -722,9 +748,12 @@ impl BoxBuildState {
|
|||
}
|
||||
|
||||
impl GeneralBuildState {
|
||||
fn apply_compiled_brush(&mut self, brush: &CompiledBrush) {
|
||||
fn apply_compiled_brush(&mut self, brush: &CompiledBrush) -> bool {
|
||||
match brush.op {
|
||||
BrushOp::Add => self.add_brush_geometry(brush),
|
||||
BrushOp::Add => {
|
||||
self.add_brush_geometry(brush);
|
||||
true
|
||||
}
|
||||
BrushOp::Subtract => {
|
||||
self.report.operator_brushes += 1;
|
||||
if !brush
|
||||
|
|
@ -732,7 +761,7 @@ impl GeneralBuildState {
|
|||
.intersects_any(self.solids.iter().map(|solid| solid.bounds))
|
||||
{
|
||||
self.report.rejected_pairs += self.solids.len();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if let Some(cutter) = brush.convex_cutter() {
|
||||
self.solids = subtract_from_solids(
|
||||
|
|
@ -740,6 +769,7 @@ impl GeneralBuildState {
|
|||
&cutter,
|
||||
&mut self.report,
|
||||
);
|
||||
true
|
||||
} else {
|
||||
let has_candidate =
|
||||
record_solid_pairs(&self.solids, brush.bounds, &mut self.report);
|
||||
|
|
@ -750,6 +780,7 @@ impl GeneralBuildState {
|
|||
brush: brush.name.clone(),
|
||||
});
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
BrushOp::Intersect => {
|
||||
|
|
@ -758,9 +789,10 @@ impl GeneralBuildState {
|
|||
.bounds
|
||||
.intersects_any(self.solids.iter().map(|solid| solid.bounds))
|
||||
{
|
||||
let changed = !self.solids.is_empty();
|
||||
self.report.rejected_pairs += self.solids.len();
|
||||
self.solids.clear();
|
||||
return;
|
||||
return changed;
|
||||
}
|
||||
if let Some(cutter) = brush.convex_cutter() {
|
||||
self.solids = intersect_solids(
|
||||
|
|
@ -768,9 +800,11 @@ impl GeneralBuildState {
|
|||
&cutter,
|
||||
&mut self.report,
|
||||
);
|
||||
true
|
||||
} else {
|
||||
let has_candidate =
|
||||
record_solid_pairs(&self.solids, brush.bounds, &mut self.report);
|
||||
let changed = !self.solids.is_empty();
|
||||
self.solids.clear();
|
||||
if has_candidate {
|
||||
self.report
|
||||
|
|
@ -779,6 +813,7 @@ impl GeneralBuildState {
|
|||
brush: brush.name.clone(),
|
||||
});
|
||||
}
|
||||
changed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -845,11 +880,25 @@ impl GeneralBuildState {
|
|||
}
|
||||
}
|
||||
|
||||
fn into_output(mut self, input_brushes: usize, generation: u64) -> BuildOutput {
|
||||
fn into_output(self, input_brushes: usize, generation: u64) -> BuildOutput {
|
||||
self.into_output_with_mesh(input_brushes, generation, None)
|
||||
}
|
||||
|
||||
fn into_output_with_mesh(
|
||||
mut self,
|
||||
input_brushes: usize,
|
||||
generation: u64,
|
||||
reused_mesh: Option<TriangleMesh>,
|
||||
) -> BuildOutput {
|
||||
self.report.input_brushes = input_brushes;
|
||||
self.report.emitted_convex_fragments = self.solids.len();
|
||||
for solid in self.solids {
|
||||
solid.append_to_mesh(&mut self.mesh);
|
||||
if let Some(reused_mesh) = reused_mesh {
|
||||
self.report.reused_mesh = true;
|
||||
self.mesh = reused_mesh;
|
||||
} else {
|
||||
for solid in self.solids {
|
||||
solid.append_to_mesh(&mut self.mesh);
|
||||
}
|
||||
}
|
||||
|
||||
BuildOutput {
|
||||
|
|
@ -1295,6 +1344,34 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incremental_distant_tail_edit_reuses_previous_mesh() {
|
||||
let mut asm = Assembler::new();
|
||||
asm.solid_box(
|
||||
"source",
|
||||
Aabb::from_center_size(Vec3::ZERO, Vec3::splat(8.0)),
|
||||
MaterialId(1),
|
||||
);
|
||||
let far = asm.cut_box(
|
||||
"far",
|
||||
Aabb::from_center_size(Vec3::splat(100.0), Vec3::splat(1.0)),
|
||||
);
|
||||
let before = asm.build_incremental();
|
||||
|
||||
assert!(asm.set_brush_primitive(
|
||||
far,
|
||||
Primitive::Box {
|
||||
bounds: Aabb::from_center_size(Vec3::splat(120.0), Vec3::splat(1.0)),
|
||||
},
|
||||
));
|
||||
let after = asm.build_incremental();
|
||||
|
||||
assert_eq!(after.mesh, before.mesh);
|
||||
assert!(after.report.reused_mesh);
|
||||
assert_eq!(after.report.candidate_pairs, 0);
|
||||
assert_eq!(after.report.rejected_pairs, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incremental_convex_rebuild_matches_full_rebuild() {
|
||||
let mut asm = Assembler::new();
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ impl Plane {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ConvexPolygon {
|
||||
pub vertices: Vec<Vec3>,
|
||||
pub normal: Vec3,
|
||||
|
|
@ -73,7 +73,7 @@ impl ConvexPolygon {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ConvexSolid {
|
||||
pub polygons: Vec<ConvexPolygon>,
|
||||
pub clip_planes: Vec<Plane>,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use bevy_math::{Vec2, Vec3};
|
|||
|
||||
use crate::{Aabb, MaterialId};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct TriangleMesh {
|
||||
pub positions: Vec<Vec3>,
|
||||
pub normals: Vec<Vec3>,
|
||||
|
|
|
|||
|
|
@ -130,6 +130,12 @@ suffix. The result is parity-checked against a full rebuild. Cache validity is
|
|||
tracked as a prefix boundary so repeated edits do not deep-copy stale
|
||||
checkpoints. Final mesh emission is still whole-output.
|
||||
|
||||
Mesh reuse seam: incremental rebuilds now keep the previous generation's cached
|
||||
mesh and reuse it only when suffix replay proves no geometry was touched. This
|
||||
is intentionally exact and conservative: distant rejected edits can skip mesh
|
||||
emission, while candidate cuts still emit the full output mesh. The report
|
||||
exposes `reused_mesh` so the fixture and tests do not infer reuse from timing.
|
||||
|
||||
## Latest Local Baseline
|
||||
|
||||
Captured on 2026-05-07 with `.\tools\run_csg_perf.ps1 -UseRealtimeCsgCpp`:
|
||||
|
|
@ -190,7 +196,9 @@ falls back to full replay only when no valid checkpoint exists.
|
|||
|
||||
Tail-edit lesson: prefix caching cuts the room-grid dirty mean by roughly 7.8x,
|
||||
the rotated-stack dirty mean by roughly 8.4x, the common-box chain by roughly
|
||||
48x, and the distant-cutter case by roughly 39x. The single-cut case is still
|
||||
48x, and the distant-cutter case by roughly 39x in this run. A follow-up mesh
|
||||
reuse seam pushed distant rejected edits into low microseconds by reusing the
|
||||
previous mesh when no geometry was touched. The single-cut case is still
|
||||
slightly slower because cache machinery dominates. The next hard target is
|
||||
cached mesh emission or dirty output patching; replaying less CSG work is not
|
||||
enough if every edit still serializes the whole result mesh.
|
||||
dirty mesh range patching for candidate edits; replaying less CSG work is not
|
||||
enough when every real cut still serializes the whole result mesh.
|
||||
|
|
|
|||
|
|
@ -123,7 +123,9 @@ CSG and the general convex builder now have prefix checkpoint caches, and
|
|||
`build_incremental()` resumes from the checkpoint before the first dirty brush.
|
||||
Output parity is tested against full rebuilds. Checkpoint validity is tracked as
|
||||
a prefix boundary to avoid deep-copying stale history after tail edits. Mesh
|
||||
emission is still whole-output; the category-router path is still separate.
|
||||
emission is still whole-output for candidate edits; rejected dirty suffixes can
|
||||
reuse the previous mesh when replay proves no geometry was touched. The report
|
||||
flag is `reused_mesh`. The category-router path is still separate.
|
||||
|
||||
## Important Invariants
|
||||
|
||||
|
|
|
|||
|
|
@ -68,3 +68,4 @@
|
|||
{"ts": "2026-05-07T07:47:54+00:00", "type": "implementation", "status": "ok", "note": "Added the first realtime-editing seam to vg_csg: brushes can now mutate primitive/op by BrushId, invalidating cached output and incrementing generation; DirtyDemandFrontier computes the conservative ordered suffix after the first dirty brush, identifying the future prefix-cache boundary. This is a planning/cache seam, not yet incremental mesh rebuild."}
|
||||
{"ts": "2026-05-07T14:05:07+00:00", "type": "implementation", "status": "ok", "note": "Added box-only dirty prefix checkpoints to vg_csg. build_incremental resumes from the checkpoint before the first dirty brush and parity tests compare incremental output against full rebuilds. Perf fixture now includes incremental_dirty; tail edits improve in large box-only scenarios, but final mesh emission remains whole-output and non-box paths fall back."}
|
||||
{"ts": "2026-05-07T14:14:21+00:00", "type": "implementation", "status": "ok", "note": "Extended vg_csg incremental dirty rebuilds from box-only state to the general convex builder. The cache now tracks a valid prefix boundary so tail edits reuse the checkpoint before the dirty brush without deep-copying stale checkpoint history. Latest smoke fixture shows large tail-edit wins, including rotated_cut_stack_64 dirty mean dropping from about 3.49 ms full rebuild to about 0.42 ms incremental, with parity tests still green."}
|
||||
{"ts": "2026-05-07T14:39:10+00:00", "type": "implementation", "status": "ok", "note": "Added conservative mesh reuse for vg_csg incremental dirty rebuilds. Mutations no longer discard the previous BuildOutput; suffix replay tracks whether geometry was touched, and rejected dirty suffixes reuse the prior mesh exactly. BuildReport exposes reused_mesh and tests cover distant tail edits. Candidate edits still emit the whole mesh."}
|
||||
|
|
|
|||
|
|
@ -110,8 +110,8 @@ data_flow:
|
|||
- If state grows faster than understanding, cut it back.
|
||||
|
||||
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, dirty, and incremental dirty modes, mesh-array output, stable-generation output caching, dirty prefix checkpoints for box and general convex builders, 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 valid-prefix incremental dirty caching; tail edits in large box, distant-cutter, common-intersection, and rotated-convex scenes are much faster, but mesh emission is still whole-output and the category-router kernel still needs its own compact incremental cache. Known graph-tooling blockers remain repeat-zone feedback, repeat Top outputs, foreach zones, and gizmo nodes.
|
||||
next_action: Stop serializing the whole output on every edit: add emitted mesh range caching or dirty output patching for checkpointed CSG states, then port the valid-prefix checkpoint model into the compact category-router path without relaxing output parity.
|
||||
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, dirty, and incremental dirty modes, mesh-array output, stable-generation output caching, dirty prefix checkpoints for box and general convex builders, conservative mesh reuse for rejected dirty suffixes, 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, valid-prefix incremental dirty caching, and a `reused_mesh` report flag; tail edits in large box, distant-cutter, common-intersection, and rotated-convex scenes are much faster, but candidate edits still serialize the whole mesh and the category-router kernel still needs its own compact incremental cache. Known graph-tooling blockers remain repeat-zone feedback, repeat Top outputs, foreach zones, and gizmo nodes.
|
||||
next_action: Build dirty mesh range patching for candidate edits: track emitted mesh spans for checkpointed CSG states, patch only changed ranges when suffix replay touches geometry, then port the valid-prefix checkpoint model into the compact category-router path without relaxing output parity.
|
||||
remotes:
|
||||
vibegeometry: https://github.com/GameCult/VibeGeometry
|
||||
geometry_script_fork: https://github.com/GameCult/geometry-script
|
||||
|
|
|
|||
Loading…
Reference in a new issue