Brush tree: target r2 (representation per hull, the hand cage as the measuring stick)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-12 17:44:07 +02:00
commit fd36e4e649

View file

@ -1,256 +1,148 @@
# Brush tree: target
# Brush tree: target (r2)
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.
Campaign `brush-tree`, owned by the Aetheria/Ghostlight Self. Drafted by Imagination
(`imagination-brush-tree-target-r2`, session `self-2026-10-11-ag2`) on 2026-10-12.
It supersedes r1 (`49948c8b`, the CSG-only target). This page holds the ends, not the means:
no cut plan. The typed `brush-tree:target:r2` holds the invariants, the not-in-scope list and the
canonical implementations; this page holds why, and the numbers behind the bars.
- **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/`.
- **Repos:** `GameCult/GameCult.Geometry.Csg` (the tree, the field, the kernel, the scorer),
`GameCult/Brokkr` (the agent's hands in Blender), `GameCult/Aetheria` (the first consumer).
- **Working branch:** `eureka/brush-tree`.
## Rationale
## The rulings this target rests on
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:
Read them in the mind by id; their operator quotes are the authority, not this page.
- **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.
- `brush-tree:ruling:representation-per-hull`: subdivision limit surfaces for continuous bodies like
Dexter Quiet, primitives for mechanical hulls like the squatter mech ship; both paths, per hull or
per part. It narrows the "mesh CSG is the main representation" clause of
`settled-direction-2026-10-10` to mechanical hulls.
- `brush-tree:ruling:pursue-sdf-composition-remeshing`: the continuous path is an SDF composition
remeshed automatically; viability rests on the remesher; the operator's own Dexter Quiet rebuild is
the reference it is measured against.
- `brush-tree:ruling:shippable-part-bar`: clean unbroken flow, part-coherent low-distortion UVs,
one-segment bevels with face-weighted normals.
- `brush-tree:ruling:no-unownable-tools-in-loop`: only code we can own is in the loop.
- `brush-tree:ruling:spike-sdf-smin-to-cage`, `:spike-instant-meshes-field-features`: the spikes
whose results (runs 4 and 7) this target reads.
- Unchanged from r1 and still in force: `openscad-target`, `brokkr-beats-blender-mcp`, `cli-not-mcp`,
and the defaulted rulings on the Blender sink, `run`, docs lookup, look files, the per-ship tree
`.cc`, persistence through cultcache-rs, the Package path, Studio and impostors.
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.
## What changed from r1, in one paragraph
Why this shape, by the evidence:
r1 lowered every hull through mesh CSG with field-derived microbevels. The probes showed CSG cannot
build Dexter Quiet's continuous curvature (run 2), and the operator ruled the representation per hull.
A hand-designed cage (run 3, v7: chamfer 0.228 %) beat every automated route; SDF composition with
smooth-min fuses the body and its sweeps "without breaking a sweat" (run 4) but its program fidelity
(0.52 %) and the remeshers' flow (runs 5-7: open field loops, 38-69 poles per 1000 vertices, UV area
distortion 6-10x the designed cage's) fall short. Run 7 also found that the fidelity wall belonged to
the SDF program, not the remesher: measured against the SDF itself, every remesh sat at 0.01-0.07 %.
Microbevels from the field are gone: the bar names one-segment mesh bevels.
- **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).
## What the pipeline turns a brush tree into
## Body facts the target rests on (probed 2026-10-10)
**The tree** is one typed document per ship (`tree-store-per-ship-cc`): primitives, transforms,
scoped booleans, and smooth unions with a named radius. Every part (top-level branch) declares its
representation, `field` or `kernel`. The declaration is authored, never inferred from the shape.
- **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)`.
**A `field` part** (continuous body, Dexter Quiet):
## Design truths the campaign produces
1. Field: the part's signed-distance program, every primitive and blend defined once in Geometry.Csg.
Hard edges the author wants crisp are declared as creases (a hard `max`, run 4's wing rim), not
detected afterwards.
2. Cage: a coarse quad-dominant control cage whose crease edges lie on the declared creases and form
closed loops, refit so its Catmull-Clark limit surface lies on the field's zero set (run 4's refit,
run 3's least-squares in a mirror-reduced space).
3. Evaluated mesh: the operator's own stack, Mirror, Subdivision (limit surface), Bevel (one segment,
on the creases), Weighted Normal (`dexter-quiet-cage/README.md`). Seams lie on the closed crease
loops and part boundaries; run 7 showed seams through a broken crease join wing top and bottom.
4. Output: the cage with its stack, as an object in the ship's `.blend`; Package applies and exports it.
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.
**A `kernel` part** (mechanical, the squatter mech ship): the polygon kernel builds exact planar faces
with scoped booleans, a one-segment bevel on hard edges, face-weighted normals, and UVs from each
brush's declared charts. The r1 kernel machinery and its open questions serve this path.
## Invariants
**Both** reach Aetheria the same way (`lowered-hull-through-package`): an object in the ship file,
exported by the existing Package. Nothing reads a cage, mesh or render back as truth.
- **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.
### Owners, inputs, outputs, mock points
## Settled direction (rulings in the mind, campaign brush-tree)
| Owner | Inputs | Outputs | Mock point |
|---|---|---|---|
| Tree (Geometry.Csg) | text through the one commit primitive | typed tree revision | in-memory store |
| Field | tree part | distance and gradient at a point | trait; analytic test shapes |
| Cage generator | field, declared creases, target face budget | cage with crease flags | trait; the hand cage is a fixed implementation |
| Refit | cage, field, subdivision rule | moved cage vertices | linear limit operator, injectable |
| Kernel | tree part | mesh with per-face brush and surface ids | trait |
| Scorer | two meshes | the bar's numbers | pure mesh math, no Blender |
| Brokkr bridge | intents from `brokkr-ctl` | receipts, objects in the ship file | directory store or loopback, per `blender-transport` |
- 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.
The cage generator is the owner nobody has built: runs 5-7 produced dense field-aligned meshes, never
a cage. It sits behind a port so that candidates can be swapped and scored against the bar below.
## Not in scope, and not consumers
## The hand cage as the measuring stick
- **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.
**What it is** (GameCult.Geometry.Csg `docs/research/dexter-quiet-cage/` @ d426f86, measured
2026-10-12 from `cage_raw.obj` and `cage_eval.obj`):
## Canonical implementations
- Half hull: 29 vertices, 25 faces (19 quads, 6 triangles), the mirror seam open (12 boundary edges).
Mirrored, about 50 faces for the whole hull.
- Creases: 9 edges at 1.0 forming one chain, vertex 5 (x = 0, nose) to vertex 22 (x = 0.003, tail),
so the mirror closes it into one loop round the wing rim.
- Evaluated at subdivision level 3: 3,154 vertices, 3,152 faces, all quads, no boundary, 31 vertices
of valence other than 4 (about 10 per 1000 vertices).
- No UVs.
- Fidelity to the scan, vertex to nearest vertex, % of the hull diagonal (1.492):
cage to scan mean 0.88, p95 2.04, max 3.11; scan to cage mean 1.29, p95 4.12, max 6.03.
- **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.
**Why the scan stays the shape reference.** The operator's words: "I'm afraid I had to take a bit of
artistic liberty to get the cage to behave, mostly because of how creases work in Blender (generates
horrible geometry if the crease path isn't closed)". The liberty was a concession to the tool, so the
cage's departures are a tolerance band, not a new shape. Whether her cage should instead become the
shape reference is question `fidelity-reference`.
## Evidence (DocRefs)
**The fidelity bar.** The evaluated output of a `field` part, measured the way the cage was (vertex to
nearest vertex against the same scan, both directions, at a vertex count within half to double the
cage's 3,154), is no worse than the hand cage in any of the six numbers above. Before the first cut
uses the shared scorer (chamfer, F@2 %, silhouette IoU, run 4's `tools/common.py`), the hand cage's
score under it is recorded as a body fact, and the bar is then also "no worse than the cage there".
- `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.
**Attribution.** Every measurement reports two parts separately: the program's zero set against the
scan, and the cage's limit surface against the field. Run 7 misdiagnosed the wall for a whole spike
because only the end-to-end number existed.
**The cage-quality bar**, from the same cage:
- Size: at most 4x the hand cage's face count for the hull (about 200 faces). The factor is a proposal.
- Faces: quads and triangles only, no n-gons; triangle share at most the hand cage's (6 of 25, 24 %).
- Creases: every crease chain is a closed loop, or ends on the mirror plane where its mirror closes it.
- Limit mesh at level 3: one shell, watertight, at most 2x the hand cage's 31 irregular vertices.
- UVs (the cage has none, so this comes from the bar's words and run 3's v7): islands follow parts,
seams on crease loops and part boundaries, mean |log2| area distortion at most 0.1 (v7: 0.076).
## Not yet known (research owed before cuts are mapped)
- **How the cage is generated.** Candidates in the research: a quad layout from the cross field
(patent US 12086939; Kloiber and Augsdorfer 2020), a coarse remesh then refit (run 6 at 144-342 faces:
115-170 poles per 1000 vertices), a template cage refit (run 7 "Next" item 4). No measured source
covers coarse feature-aligned patch layouts.
- **What text authors a `field` part.** OpenSCAD has no smooth union, so `openscad-target` and
`text-is-a-surface` from r1 cannot both hold for smooth-min parts.
- **How a `field` part and a `kernel` part meet** on one hull. The research found no convention
(subd-cages file s.0, s.5).
## Research it rests on
All in GameCult.Geometry.Csg `docs/research/` on `eureka/brush-tree`:
- `dexter-quiet-cage/` @ d426f86: the reference cage.
- `shippable-bar/` @ 253f34d: the operator's bar and render.
- `mesh-probe-run1/` to `mesh-probe-run7-im-field/`: OpenSCAD and CSG fits (1), CSG lowerings (2),
a designed cage (3), SDF with smin and a field cage (4), QuadriFlow (5), Instant Meshes (6), the
Instant Meshes fork with the SDF's cross field and a hard rim (7).
- `subd-cages-mixed-hulls-and-open-kernels.md` @ 390db63: cage fitting, remeshers as cage seeds,
creases versus bevels, mixed assets, open kernels.
- `primitive-seams-and-sdf-bevel-meshing-prior-art.md`, `brush-surfaces-and-sdf-bricks-prior-art.md`,
`openscad-evaluators-and-csg-export.md`, `octahedral-impostors-prior-art.md`: the kernel path.