Articulate.
Articulate Lab · Engineering log

What broke, and why

An honest record from driving Blender through an LLM over two days. Not a highlight reel — the faults, their cost, and the rule each one produced. Day 2 ended with roughly three productive hours followed by two spent undoing damage; that ratio is the most useful thing on this page.

Why publish this

Most AI-workflow write-ups show the finished render. The useful information is in the failures: they are reproducible, they have causes, and each one converts into a rule that prevents a class of future error. A model that renders beautifully but cannot be taken apart is worth nothing; neither is a method that works until it silently doesn't.

Transforms and the bridge

The single most expensive category. Blender is driven here through an MCP bridge that executes Python without a full dependency-graph tick, so anything derived from evaluated state can be stale.

FAIL-MATRIX-WORLD-STALE

Four occurrences in one session · ~2 hours

Setting a transform and then reading matrix_world returns the old matrix. Every calculation built on that read is computed from a fiction. It produced a 109-metre leaf spring on Day 2 morning, a jockey 1.36 m from where it belonged, an awning whose parts sat at double height, and six wheels collapsed onto the world origin.

The insidious part: it was documented the same morning and used again four times that evening, because each new problem looked different from the last.

Rule: on this bridge, never read matrix_world. Compute world position by walking the parent chain yourself — parent_world @ matrix_parent_inverse @ matrix_basis — or measure raw vertex data. Where the numbers are known, hard-code them.

FAIL-OVERWROTE-PARENT-INVERSE

Destroyed data · ~1 hour to reconstruct

Re-parenting six wheel assemblies, the child matrix_parent_inverse values were reset to identity on the assumption they were redundant. They were not: on those wheels the parent inverse was the placement data — position, scale and the left/right mirror. All six wheels collapsed to the origin, the trailer wheels silently shrank from 778 mm to 741 mm, and one side ended up inside-out showing the hub face.

Rule: never zero a transform you have not first recorded. A parent inverse is data, not clutter.

FAIL-REPARENT-ON-DISPLACED-FRAME

121 objects, 26 m out

Objects were adopted into an animation rig while the timeline sat on frame 1 — where that rig is 26 m off-camera. Preserving their world positions baked a +26 m offset into every local transform. The car's wheels, the coupler hierarchies and the towball were left standing on the beach while the car drove away.

Rule: park the timeline on the rest frame before touching parenting. Better still, do structural work before any animation exists.

Scene structure

FAIL-PARENT-CHILD-COLLECTION-SPLIT

A trailer with no wheels, shipped

The wheel assemblies were empties filed in 03_RunningGear, but all 52 of their mesh children still sat in Reference_CC-NC from the day they were copied off the tow car. The licence-exclusion checkbox stripped the children and left the empty parents. The exported model reached the browser wheel-less — and the export was correct; the filing was wrong.

Rule: parents and children live in the same collection. Audit for split hierarchies before every export.

FAIL-CONSTRUCTION-GEOMETRY-VISIBLE

A mystery stipple line, twice investigated

Boolean cutters for the door openings were hidden from renders but left visible in the viewport, sitting exactly coplanar with the body skin. The result is a dashed white line along the panel — classic z-fighting, and easily mistaken for a modelling fault. A studio backdrop plane left over from a product render did the same thing to the sand.

Rule: construction geometry goes in an excluded collection, hidden in both viewport and render, the moment it has done its job.

FAIL-ORIENTATION-UNVERIFIED

Car hitched nose-first; doors swinging into the body

Two orientation errors survived because they were asserted rather than measured. The tow car was hitched by its front bumper. The rear doors were animated to swing into the trailer, because the rotation sign was assumed instead of tested. Both were caught by eye, from a viewport, by the operator — not by the numbers.

Rule: for any orientation question, test both options and pick by measurement — lamp positions, bounding boxes, clearance to a known plane. Never by intuition about sign conventions.

Export

FAIL-PROCEDURAL-DOESNT-EXPORT

Flat grey where the detail was

The chequer plate, the solar cells and the awning canvas were all procedural node graphs. glTF has no procedural nodes, so all three exported as plain slabs. This is not a bug; it is what a delivery format is.

Rule: bake anything procedural to image textures before export, and make it a fixed step in the recipe rather than a thing to remember. Full list of what glTF cannot carry.

FAIL-EXPORT-ALL-SCENES

A stowaway working copy, 100 KB

Blender's glTF exporter writes every scene in the file unless use_active_scene=True. A working copy of the jockey, living in a separate part scene, silently rode along in the published model. Debug collections do the same unless explicitly excluded.

Rule: export with use_active_scene=True, and count nodes in the resulting file rather than trusting the export dialog.

FAIL-SAVED-TO-BACKUP-PATH

Two hours of work invisible in the main file

A recovery step opened trailer_day2.blend1 — the backup file — and Blender then kept saving to that path, creating .blend11 as its own backup. For two hours the canonical trailer_day2.blend was frozen at an old state. Anyone opening it would have found the evening's work missing.

Rule: after any recovery or revert, assert the filepath explicitly with save_as_mainfile to the canonical name, and log it.

Method failures — the expensive ones

FAIL-COMPOUND-INSTEAD-OF-REVERT

The whole two hours

When the first wheel fix produced a wrong result, the response was another fix, computed from the same broken state. Then another. Four attempts, each inheriting the previous one's damage. The correct move after the first failure was to stop, restore, and change method — not to push forward.

Rule: two failed attempts at the same problem means the model of the problem is wrong. Stop. Restore. Re-measure from a known-good state.

FAIL-NO-SNAPSHOT-BEFORE-BIG-CHANGE

No clean restore point existed

Version discipline says snapshot before every significant change. It was skipped before the animation work began. When a rollback was finally requested, no file existed containing the evening's model work without the damage — the only choice was to strip the animation surgically rather than revert.

Rule: a named snapshot before any structural change, not after. Cheap insurance; the absence of it removes the option to undo.

FAIL-TOO-MANY-CHANGES-PER-STEP

Impossible to isolate the fault

One script built the drive path, wheel rotation, trailer articulation, body pitch, the camera and the terrain mask together. When the result was wrong, nothing could be attributed. Six variables, one observation.

Rule: one change per step, one verification per change. In animation especially: prove the wheels turn on a stationary vehicle before anything moves.

FAIL-SILENT-RUN-30MIN

Trust

A long sequence ran without progress reporting. Thirty minutes of silence during work the operator could not see. Separately: several results were reported as complete before being looked at — the geometry claim asserted from a return value rather than a render.

Rule: report every 30 seconds during long work, and never call geometry correct without looking at it. A scalar can be right and meaningless.

FAIL-VERIFY-BY-LIT-RENDER

Day 1, still relevant

Geometry checked in a lit render hides in shadow and specular. A blanket normals recalculation across 167 meshes fixed 42 and silently inverted 52 non-manifold wheel shells, where "outside" is undefined and the heuristic guesses.

Rule: verify geometry in Workbench with object colours or a normals override, never in a beauty render. And scope every fix to the measured offenders, never to everything.

Day 5 — fitting out the Glamper

Eleven faults across one long session of interior work. Four of them are two mistakes made twice. Full Day 5 write-up →

FAIL-HIDE-RESTORE-NOT-IN-FINALLY

838 objects · shipped models missing the body

A probe render hid everything except its subject, then errored on BLENDER_EEVEE_NEXT — an engine name that no longer exists in Blender 5.1 — and never reached its restore line. Every later script then captured that broken state as its "previous" state and faithfully restored it. 838 objects were left hide_render=True, body_shell among them, and the exporter drops hidden objects. Models published in that window shipped without the trailer body.

Rule: any hide/restore pair goes in try/finally. A straight-line restore is not a restore.

FAIL-SOLID-BOX-WHERE-SHELL-NEEDED

Three builds · nothing rendered inside

cfx5_body was a solid cube, entombing its own liner and wire basket. cfx5_gasket was a solid slab across the aperture, sealing the tub. And body_shell is the same class of object — the 12-vertex solid already logged in the import gate. Cutting the felt for the prow aperture only exposed the inside of that box, which reads as a flat dark panel and looks like a texture fault.

Rule: anything with an interior is built as base + walls + rim frame. Never a cube.

FAIL-STALE-MATRIX-WORLD (×4)

Four occurrences in one day

The bridge's own documented trap, hit four separate times. Rear-door MDF infills landed at rig-local coordinates in world space, hanging under the chassis. A fridge lid sat 400 mm high because the rig's matrix_world was still identity when its children captured it. A re-imported BBQ arrived at 308 metres. The prow parenting went the same way.

Rule: view_layer.update() before reading matrix_world; set matrix_parent_inverse before matrix_world, never after.

FAIL-BOOLEAN-BEFORE-SOLIDIFY

Body collapsed to 8 vertices

An aperture cutter was added to body_shell and pushed to the top of its modifier stack, ahead of the BEVEL and SOLIDIFY. The evaluated mesh dropped to 8 vertices — the whole body reverted to a bare cube. The seven existing cutters all sit after the solidify; that ordering is how the shell is generated, not housekeeping.

Rule: aperture booleans go last, after whatever generates the shell. Verify by evaluated vertex count, not by the modifier appearing in the list.

FAIL-REGEX-KNEW-HALF-THE-CONVENTION (×3)

Nine lamps dark · twelve panels uncoloured · a BBQ invisible

Three versions of one mistake, all in the viewer rather than the model. The night regex matched led_* and lens_* but not the nine emis_* lamps. The felt regex was /^felt_\d/, silently skipping felt_partition, felt_riser and felt_reardoor. And a BBQ option was defined but never added to the shown list, leaving 23 parts in the file with no control to switch them on. In all three cases the geometry shipped correctly and the consumer was never told about it.

Rule: after adding a naming convention, enumerate what the pattern matches against the shipped file. Do not assume the convention is singular.

FAIL-BLOCKER-ASSERTED-FROM-HIDDEN-GEOMETRY

A design decision argued from a phantom

A fridge extraction route was declared impossible partly because a jerry cage occupied the path. Those objects were eye-hidden — not in the exported model, not part of the design. A clash test that includes hidden geometry answers a question nobody asked.

Rule: filter clash tests by what actually ships — hide_get() and hide_render both — before quoting anything as a constraint.

FAIL-REASONED-PAST-THE-INSTRUCTION

Two rebuilds

Told a slide was “lengthwise, not sideways”, I analysed sideways anyway. Told a swing-out unit was “exactly the same as the prow door”, I moved its hinge to the opposite end and spent two rounds rediscovering that the door's own pivot — 44 mm proud of the face — was the answer. Both constraints had already been supplied.

Rule: when an existing part solves the same problem, read its transform before designing a new one.

FAIL-INSULATION-OUTSIDE-THE-WALL

Four screenshots missed it; one raycast found it

A six-direction ray sweep over a grid of interior points, flagging any first hit on body_shell, returned exactly one exposure: interior_insul_front built at y 1399–1424 against a shell inner face at 1398. The nose insulation was outside the wall, and the felt added on top of it inherited the error.

Rule: interior exposure is measured by raycast, not by eye.

The rules that came out of it

#RuleBorn from
1Never read matrix_world on this bridge. Compute the parent chain yourself, or measure raw vertices.4 failures
2Never zero a transform you have not recorded. Parent inverses are data.wheel collapse
3Park on the rest frame before any re-parenting.26 m offset
4Parents and children live in the same collection.missing wheels
5Snapshot before structural change, always, by name.no restore point
6One change, one verification. Two failures = stop and restore.the lost two hours
7Bake procedurals before export; count nodes in the output file.flat textures, stowaway scene
8Test orientation both ways and pick by measurement.reversed car, inward doors
9Construction geometry lives excluded and hidden.z-fighting stipple
10Assert the save path after any recovery.two hours in a .blend1
11No measurement is used until a second, independent one agrees with it. Two views, a published figure, or a re-measure by a different method.traced the wrong SVG paths; built a lozenge
12Related features are measured together, in one frame. Never one at a time in isolation.step arc cut straight through the fin boxes
13Before reporting anything visual, render what the operator is looking at — same shading mode, same visibility flags, orthographic when comparing silhouettes.diagnosed a white board as a specular blowout
14Two failed attempts on the same problem = stop and re-measure. Not a third attempt.four wheel fixes, each built on the last one’s damage
15Convert sRGB to linear before assigning a colour, and render on Standard when matching a reference.every colour 2.5× too bright for three rounds

The one that generates the rest

Every failure on this page reduces to the same thing: motion was chosen over certainty. Verifying costs a turn and produces nothing visible, so it loses to building — and a wrong build then costs five turns. The rigour was usually real; it was pointed at inputs nobody had validated. “I measured something” is not “I measured the right thing.”

Logged 2026-08-15 at the end of Day 2. The corresponding vault record is Claude/failures.md; this page is the public mirror.