GameCult.Geometry.Csg/docs/brush-tree-target.md
Meta Vens 49948c8b80 Brush tree: campaign target and model page (Eureka step 0)
The ends of the brush-tree campaign: OpenSCAD-shaped CSG trees authored
in Blender through brokkr-ctl, with the typed brush tree as the one truth.
Sixteen invariants, scope, canonical implementations, evidence, and the
identity/lifecycle/authority table. No cut plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:57:45 +02:00

15 KiB

Brush tree: target

Campaign brush-tree, area geometry, owned by the Aetheria/Ghostlight Self (aetheria-release:ruling:brokkr-ctl-job-to-ag-self). Drafted by Imagination (imagination-brush-tree-target, session self-2026-10-10-ag2) on 2026-10-10. This is step 0: the ends, not the means. It contains no cut plan.

  • Title: Brush tree: OpenSCAD-shaped CSG trees authored in Blender through brokkr-ctl.
  • Repos: GameCult/GameCult.Geometry.Csg (the truth and the kernel), GameCult/Brokkr (the agent's hands in Blender), GameCult/Aetheria (the first consumer), and GameCult/CultLib, only if a ruling sends work there (the cultcache-py directory store, Q blender-transport). All four remotes are GitHub today; Aetheria moves to Forgejo under aetheria-release:ruling:forgejo-primary-github-mirror.
  • Working branch: eureka/brush-tree in each repo it touches.
  • This document's home: GameCult.Geometry.Csg/docs/brush-tree-target.md. The crate owns the truth the campaign builds, and two of the four research passes already live beside it in docs/research/.

Rationale

The operator rebuilds Tripo-generated hard-surface hulls by hand: retopology, seams and bevels, at about 30 minutes per part. The campaign replaces that with a collaborative loop in Blender. The work is split by what each party does well:

  • The operator points at part of the messy hull, using her Edit Mode selection plus the viewport framing.
  • The model proposes a parametric form, or builds the one she names ("these pipes are tubes", "a sphere, solidified, with a prism cut out"). It does the naming, grouping, symmetry and composition.
  • A classical fitter does the numbers: least-squares or RANSAC primitive fitting, plus GlobFit-style relations.
  • Feedback is a deviation heatmap plus a viewport render, iterated.

The output is a typed CSG brush tree. From that one tree come the lowered mesh, its UVs, its topology near cuts and its microbevels.

Why this shape, by the evidence:

  • Model fluency. CAD-Recode and MeshCoder emit code (CadQuery, Blender Python) precisely because language models write code fluently (CultLib docs/research/inverse-procedural-modelling-prior-art.md §1, 6bdc23c9). OpenSCAD is a CSG tree written as text, with a large public corpus.
  • Text-only loops fall short. BlenderGym shows the best VLM still far behind humans on geometry editing (Chamfer 1.120 vs 0.334, same doc §4). BenchCAD reports single-literal errors at high IoU. That is why numbers go to a fitter and judgement goes to the model and the operator, not to unaided generation.
  • Failure without coherence. VibeGeometry's recorded failure was "local plausibility without global coherence" (same doc §0b). The defence here is a typed tree with one owner, scored against the target by one distance function.
  • Brushes are the durable design surface. The crate's own doctrine says "the brush tree is the authority; the mesh is cache and evidence" (docs/realtime-csg-doctrine.md, Core Model). This campaign makes that true of persisted state, not only of the in-memory assembler.
  • The bar is the Blender MCP servers, and Brokkr exceeds it. They offer viewport sight, code execution and API docs, but none reads selected Edit Mode geometry, and none has auth or admission (Brokkr docs/research/blender-mcp-capability-bar.md §A(b), §B(b), f97d4dc). Brokkr already has typed intents, receipts and admission on its Unity side (docs/provider-contract.md, Admission).

Body facts the target rests on (probed 2026-10-10)

  • GameCult.Geometry.Csg (main at 5b5dc34):
    • Five primitives: Box, OrientedBox, CylinderZ, DomeCapZ, FloretArm (src/brush.rs).
    • The polygon kernel runs CSG only for Box and OrientedBox cutters. It skips other cutters with SubtractIgnoredForNonBox / IntersectIgnoredForNonBox (src/assembler.rs ~803, ~836).
    • The assembler and field.rs consume one flat ordered stream in which Intersect is global. CsgTree branches exist in src/tree.rs, but the assembler does not honour them as scoped nodes.
    • field.rs lowers the stream to a signed-distance bound for every op on every primitive. It has no smooth blend.
    • TriangleMesh gives every quad 0-1 UVs. It carries per-triangle material only: no brush id, surface id, smoothing or tangent (src/mesh.rs).
    • Brush and Primitive derive no serde.
    • The "CultCache records" in src/cult_geometry.rs are rmp_serde msgpack tuples with schema-string constants. They are not written through CultLib's cultcache-rs, and no record holds brushes.
  • Brokkr (main at f97d4dc):
    • The Blender add-on admits eight object-level commands (surfaces/blender/brokkr_bridge/blender_target.py:402-423). Its snapshot reports mesh counts only (:568-575). It has no render, no mesh read, no Edit Mode read and no code execution.
    • Its store is the cultcache-py single-file store. cultcache-py has no directory store (CultLib/packages/cultcache-py/src/cultcache_py/stores.py:22,85).
    • Its optional CultNet server binds 127.0.0.1 with no auth token (blender_target.py:288-350).
    • brokkr-command is a .NET, Unity-only caller (brokkr-command/Program.cs). brokkr-ctl does not exist.
    • The Unity side's rulings F4 to F6 settle a file transport, a sink that is off by default, and single-use command ids (docs/brokkr-adoption-cut.md, Rulings).
  • CultMath: BoundedLeastSquares exists in C# (CultLib/packages/cultmath/src/CultMath/BoundedLeastSquares.cs).
  • Mesh probe: the probe running under rulings mesh-probe-exploit-not-measure and mesh-probe-both-vocabularies has not written run1/RESULT.md yet. Its scratch demo-hull.scad already shows the model reaching for top-level variables, module, hull(), minkowski(), polyhedron, mirror and cylinder(r1, r2).

Design truths the campaign produces

  1. Each hull rebuilt through the loop exists as one typed brush-tree document in CultCache. Everything else is derived from it: text, mesh, UVs, bevels, heatmap, projections into Blender.
  2. The model reads and writes that tree as OpenSCAD-shaped text. The operator reads and writes it through Blender. Both edits land through one commit path.
  3. An agent drives Blender with brokkr-ctl blender look | selection | fit <primitive> | run <script>. Every call is a typed, admitted Brokkr intent with a receipt, and --help is the schema.
  4. Geometry.Csg lowers the tree to a mesh whose faces know their brush and surface. The mesh carries the UVs and seams the brushes declare, and smooth microbevels at cuts derived from the same tree's field.
  5. Aetheria receives the lowered hull through its existing ship pipeline.

Invariants

  • I1. One truth. The typed CultCache brush tree is the only authority for a hull's shape, surfaces and structure. OpenSCAD-shaped text, Blender objects, meshes, fields, heatmaps and impostors are derived. None of them is read back as truth except through I3.
  • I2. Text is a surface, not a store. The OpenSCAD-shaped syntax is an authoring and lowering surface. A tree prints to text deterministically. Printing a tree and parsing it back yields the same tree (round-trip). Any text the parser accepts, which also renders in OpenSCAD, means the same solid in both. Where our extensions add surface data, OpenSCAD ignores it and still renders the same solid.
  • I3. One commit primitive. Every change to a tree goes through one commit that names the revision it was based on. That covers parsed text, an operator edit in Blender, an accepted fit and a scripted change. A stale base is refused, never merged silently. No second writer exists: no Blender-side cache, no CLI-side tree and no daemon copy.
  • I4. One distance per primitive. Each primitive's signed distance is defined once, in Geometry.Csg. The fit residual, the deviation heatmap, the microbevel blend and field-based scoring all evaluate that one definition. A fitter that minimised a different distance would fit a shape the kernel does not build.
  • I5. No silent skips. Every primitive and operation the authoring surface accepts is honoured by every lowering that claims to build the tree: the polygon kernel and the field. Anything a lowering cannot honour is refused at parse or commit, never dropped with a warning.
  • I6. The tree is a tree. A branch's operation applies to its children only. Nested difference inside union means what OpenSCAD means by it. The flat global-Intersect stream may survive as an internal lowering, but it cannot change meaning.
  • I7. Brushes own their surfaces.
    • Each brush declares its surfaces: material, UV parameterisation and seams, smoothing, and bevel or blend radius.
    • Every emitted face carries the id of the brush and the surface it came from. Output UVs are evaluated from that surface's parameterisation.
    • No face gets default 0-1 UVs.
    • Who owns a cut face is one explicit policy (Q cut-face-surface), not array order.
  • I8. Topology is regenerable. The tree carries enough to regenerate good topology wherever a consumer needs it, without hand retopology: edge flow on curved primitives, strips at cuts, and LOD. The mesh is a cache. It is never hand-edited and never read back.
  • I9. Microbevels come from the tree. The fine bevels at brush cuts are derived from the same tree's signed-distance field, smooth-min style, with radii that are brush or tree parameters. The field stays a distance bound, never an overestimate, because sign tests and Newton projection depend on that (src/field.rs header).
  • I10. Stable identity. Brushes and surfaces keep their identity across print, parse and Blender edits (Q brush-identity). A receipt, a fit or a heatmap that names a brush still names the same brush after a round-trip.
  • I11. Brokkr's authority map holds.
    • Blender owns Blender truth: the source mesh, selection, viewport and undo.
    • Brokkr owns the contract, admission and receipts.
    • brokkr-ctl is a caller. It owns no state, no policy and no receipt ledger (docs/agent-access-cut.md Cut 5, authority map).
    • The exit code derives from the receipt, never from "the write succeeded".
    • Agent commands are off until the operator enables them, as on the Unity side (F5).
    • Command ids are single-use (F6).
    • Every mutating Blender command is one undo step.
  • I12. Brokkr is better than the MCP bar, not equal to it. Brokkr matches the bar on viewport sight, code execution and API docs. It reads Edit Mode selection live. On top of that it adds typed admitted intents with receipts, undo-safe transactions, and a transport that does not admit arbitrary local processes.
  • I13. A CLI, not an MCP server. The agent's hands are a stupid-simple CLI whose --help is the schema, and whose outputs are paths and plain text.
  • I14. Source is evidence. The Tripo mesh is read-only reference. The loop never edits it. The Tripo-mesh-as-bake-source rule of the Aetheria release map stands.
  • I15. One consumer path into Aetheria. The lowered hull reaches Aetheria through the existing ship pipeline, Blender add-on Package into the ship .cc (aetheria-release:ruling:schematic-is-designated-mesh). This campaign adds no second exporter.
  • I16. Open and general.
    • Everything is open source (eureka-body:ruling:always-open-source).
    • Geometry.Csg stays a general library with a clean public API. A capability a reasonable consumer expects is not cut because Aetheria does not use it.
    • The library does not acquire Aetheria vocabulary.

Settled direction (rulings in the mind, campaign brush-tree)

  • Mesh CSG is the main representation.
  • Octahedral impostors are a far-LOD tier only.
  • Brick-map marching of interval-pruned tapes stays CultMath research.
  • Unity's built-in pipeline is used by choice, with our own shaders. Its deprecation is never counted as a cost.

Not in scope, and not consumers

  • Hull panel decals. A separate later campaign (aetheria-release:ruling:panel-lines-mesh-decals).
  • The Aetheria/Hull shader. It belongs to aetheria-release. This campaign emits UVs and normals the shader can use. It does not edit the shader.
  • Brick-map or tape SDF rendering, and runtime raymarching. These stay CultMath research (docs/cultmath-tape-target.md, parked).
  • Impostor generation. It is named as a derived far-LOD tier. Whether this campaign builds it is Q impostor-scope, recommended out.
  • Unity agent access. Unity's brokkr-command and its intents are not consumers and are not changed. brokkr-ctl blender must not fork the Unity contract.
  • CultCache Studio. It may display the tree .cc like any CultCache document. It gains no brush-tree editor in this campaign (aetheria-release:ruling:studio-is-the-authoring-pane covers hull data, not brush authoring).
  • The prefab CDN pipeline and the Unity-Blender sync organ. Not consumers.
  • Training models. The loop uses a frontier model with tools it builds and uses (aetheria-release:ruling:mesh-probe-exploit-not-measure). Learned encoders of the CAD-Recode or MeshCoder kind are prior art, not scope.
  • Benchmarking the model's unaided spatial reasoning. Explicitly not a goal (same ruling).
  • VibeGeometry's deleted scene-generation tree. Read for its scars, not revived.

Canonical implementations

  • The brush tree types, the text printer and parser, the kernel, the field, per-primitive distance, and the fitter math (subject to Q fitting-home): GameCult.Geometry.Csg, Rust.
  • Persistence: through CultLib packages/cultcache-rs, not hand-rolled rmp_serde tuples.
  • Agent CLI and Blender adapter: Brokkr. brokkr-ctl and the surfaces/blender/brokkr_bridge add-on, over a Blender command contract extending brokkr.blender.command_intent.v0 and .command_receipt.v0.
  • Consumer: Aetheria's tools/blender/aetheria_ships add-on, through its existing Package.

Evidence (DocRefs)

  • GameCult/GameCult.Geometry.Csg docs/research/brush-surfaces-and-sdf-bricks-prior-art.md @ df89568
  • GameCult/GameCult.Geometry.Csg docs/research/octahedral-impostors-prior-art.md @ 5b5dc34
  • GameCult/CultLib docs/research/inverse-procedural-modelling-prior-art.md @ 6bdc23c9
  • GameCult/Brokkr docs/research/blender-mcp-capability-bar.md @ f97d4dc
  • GameCult/GameCult.Geometry.Csg docs/realtime-csg-doctrine.md @ 5b5dc34
  • GameCult/Brokkr docs/agent-access-cut.md, docs/brokkr-adoption-cut.md, docs/provider-contract.md @ f97d4dc
  • Pending: the mesh probe's run1/RESULT.md. Self commits it into Brokkr or the crate's docs/research/ and cites it once it exists.