Expected Inputs
context/staging/structure.json (output from intake-normalizer)
context/staging/building-code.json (output from building-code-validator)
Behavior
Calculate minimum safe timber dimensions for:
- Corner and intermediate posts based on roof area and jurisdiction snow/wind loads.
- Beams based on post-to-post span, tributary width, and species/grade.
- Rafters based on roof span, pitch, and specified spacing.
Write structural parameters into
context/staging/structure.json(members, roof, hub, footings sections).MANDATORY — Pre-Flight Intent Validation:
python3 plugins/garden-structure-designer/scripts/validate_intent.py context/staging/structure.jsonEnsures all 15 required structural fields exist and are within physically valid ranges before computing geometry.
MANDATORY — Run the Geometry Engine:
python3 plugins/garden-structure-designer/scripts/geometry_engine.py \ context/staging/structure.jsonThis enriches
context/staging/structure.jsonwith the geometry section. All compound cut angles, SVG pixel coordinates, rafter lengths, and total height values MUST be read from this file. You are forbidden from computing these values internally.Verify the
structure.jsongeometry section contains no warnings array entries. Ifwarningsis non-empty, re-adjust post cut length or pitch to resolve the height constraint violation before proceeding.
Compound Cut Reference (read-only — use geometry_engine.py to compute)
For a regular polygon structure with sides sides and roof pitch rise:run:
pitch_angle = arctan(rise / run)
plan_half_angle = 360 / (2 × sides) # degrees
Miter = arctan(cos(pitch_angle) × tan(plan_half_angle))
Bevel = arcsin(sin(pitch_angle) × sin(plan_half_angle))
Verified Example — Hexagon (sides=6), 4:12 Pitch:
- pitch_angle = 18.43°, plan_half_angle = 30°
- Miter = 28.71° (NOT 18.43°)
- Bevel = 9.10° (NOT 15.6°)
Using the raw pitch angle as the miter setting is the most common hallucination. The geometry_engine.py script prevents this entirely.
Output Schema (structure.json — members section)
{
"members": {
"posts": { "quantity": 6, "dimensions": "6x6", "cutLength_ft": 8.33, "spanDistance_ft": 5.0 },
"beams": { "quantity": 6, "dimensions": "6x8", "depth_in": 7.25 },
"hipRafters": { "quantity": 6, "dimensions": "4x6" }
},
"roofStructure": { "pitch": "4:12" },
"overhang_in": 12,
"_locked": false
}
Set "_locked": true once this file has passed validation. Locked files must not be modified without resetting the lock and re-running the geometry engine.
Gotchas
- Pitch angle ≠ miter angle. For a hexagonal hip rafter at 4:12 pitch, the pitch angle is 18.43° but the compound miter is 28.71°. Using the pitch angle directly produces cuts that won't close at the hub. The geometry engine prevents this — never bypass it.
- Post cut length ≠ finished post height. The geometry engine computes
cutLength_ftaccounting for the post base standoff. If you substitute the desired above-grade height directly, every post will stand too tall. warningsarray is a hard stop. A non-emptywarningsarray from geometry_engine.py means a height or geometry constraint is violated. Do not forwardstructure.jsonwith warnings to drawing-generator under any circumstances.- Locking is one-way without a reset. Once
_locked: trueis written, any upstream skill (bracing-system-designer updating coordinates, for example) must explicitly reset the lock before writing and re-run the geometry engine before re-locking. - Geometry engine must be invoked as a subprocess. The structural engine is explicitly forbidden from computing compound angles, rafter lengths, or SVG coordinates internally — even as a cross-check. All math must flow through
geometry_engine.py.
Smoke Test
- Standard hexagon: Given hex-6, 4:12 pitch, 14ft outer span with no height constraint:
structure.jsongeometry section exists,warnings = [],miter_deg ≈ 28.71,bevel_deg ≈ 9.10,structure.json._locked = true. ✓ - Height violation detection: Given an 8ft post with a strict 10ft total height limit that the roof rise would breach: geometry engine populates
warningswith a height constraint message, structural engine halts and does not lock the model. ✓ - Lock integrity: Re-invoking structural engine on an already-locked model without a prior lock reset: skill halts and reports the locked state rather than overwriting. ✓
Completion: HANDOFF_BLOCK
On successful completion emit this block so the design-orchestrator can gate Stage 2:
{
"stage": "structural-engine",
"status": "COMPLETE",
"outputs": [
"context/staging/structure.json"
],
"locked": true,
"warnings_count": 0,
"next_stage": "joinery-designer"
}
If any warning or lock failure occurred, set "status": "FAIL" and populate "failure_reason" before returning to the orchestrator.