blender-visual-review
Skill20 starsChanged 42 days agoMITView on GitHub
Use when: looks off, check the model, look at it, review the render, inverted normals, exploded parts, scale drift. Multi-angle Blender MCP render-and-look. Complements blender-assembly (numeric only). Not DayZ winding: dayz-p3d-audit.
In this repository47 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- 3d-generation-harnessSkill
- ai-3d-to-dayzSkill
- ardy-motion-generationSkill
- blender-animationSkill
- blender-assemblySkill
- dayz-3d-viewerSkill
- dayz-ai-patternsSkill
- dayz-animation-pipelineSkill
- dayz-aviationSkill
- dayz-basebuildingSkill
- dayz-charactersSkill
- dayz-clothingSkill
- dayz-doorsSkill
- dayz-environment-hazardsSkill
- dayz-feature-specSkill
- dayz-mcp-verifySkill
- dayz-model-pipelineSkill
- dayz-mod-workflowSkill
- dayz-motorbikesSkill
- dayz-p3d-auditSkill
- dayz-p3d-inspectorSkill
- dayz-particlesSkill
- dayz-pbo-buildSkill
- dayz-pbo-reverse-engineeringSkill
- dayz-persistenceSkill
- dayz-physics-engineSkill
- dayz-preflightSkill
- dayz-proxy-alignSkill
- dayz-realistic-animation-directorSkill
- dayz-sound-systemSkill
- dayz-test-ingameSkill
- dayz-texture-pipelineSkill
- dayz-ui-developmentSkill
- dayz-undergroundSkill
- dayz-vehiclesSkill
- dayz-weaponsSkill
- enforce-script-referenceSkill
- mixamo-retargetSkill
- ovh-cloudSkill
- rigorous-data-auditSkill
- rip-vehicle-importSkill
- systematic-debuggingSkill
- task-observerSkill
- uv-clean-atlasSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
15 rules. About 0 are common, 8 are distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈8
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 20
Look, against the checklist: Don't free-associate — walk the categories below so you don't miss a whole class of defect.
- Very specific: line 34
Keep a per-model iteration history — a JSON list, one entry per iteration: {"fidelity": <your visual 0-1 score>, "defectTags": …
- Very specific: line 101
Run this via execute_blender_code.
- Very specific: line 181
Import a known-good vanilla model into the same scene at 1:1 and compare side by side (the import path lives in dayz-model-pipeline).
- Distinctive: line 250
Only the faceting question should move.
- Distinctive: line 265
Before judging a fix by eye, run the pixel-math comparator on the before/after pair of the SAME angle:
- Distinctive: line 325
The constant: If test set gold never changes value, constant response scores perfect and metric remains *unfalsified*, not validated.
- Distinctive: line 330
Confabulation: When asked for rationale it does not hesitate, it invents.
- Distinctive: line 359
If any of the five active ones triggers: fix and re-render before spending user's eyes.
- Very specific: line 360
If none triggers: look anyway — filter has not approved anything.
- Distinctive: line 394
All of the above comes from a single object.
- Very specific: line 415
keep question without rewriting, with skip.
- Distinctive: line 416
do not describe result as chance: failure changes with framing and grouping.
- Very specific: line 417
if asked to count a profile, frame the section until it fills the frame.
- Very specific: line 428
If facet_report.py is incorporated, calibrated candidate rule is sides < 12 and radius >= 6 mm, never provisional 4 mm threshold.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Blender Visual Review
verify_bounds() and audit_all() (from the blender-assembly skill) prove a model's topology is clean — parts overlap, transforms are applied, nothing is silently double-scaled. They cannot prove the model is right. A chair facing backwards, a quad sunk into the ground, a roof at half scale, faces lit from the inside — every one of those passes a numeric audit and fails the instant a human looks. Numbers verify topology; only eyes verify intent.
This skill is the missing half of the loop: render the model, look at the images, diagnose what you see, fix one thing, re-render the same angles to confirm. Run the numeric checks in blender-assembly; run the visual checks here.
When to use this
After you build, import, or modify any model; before you call a model "done" or export it; whenever the user says it "looks off" or asks you to "check" / "look at" / "review" a model; and — for DayZ — before you spend an in-game test cycle, because a render is free and an in-game iteration is not.
The loop
- Set up an honest view — frame the object, add a scale reference, pick diagnostic shading.
- Capture from several angles. One angle hides defects: a gap invisible head-on, a normal only wrong on one side.
- Look, against the checklist. Don't free-associate — walk the categories below so you don't miss a whole class of defect.
- Fix one diagnosis at a time.
- Re-render the same angles and compare before/after. A fix you didn't re-capture is a fix you didn't verify.
Bounded loop: typed outcomes + stop policy (added 2026-07-30, adapted from img2threejs v1.4.3, Apache-2.0 — provenance in references/NOTICE-img2threejs.md)
Step 4 of the loop says "fix one diagnosis at a time" — this section bounds how many times. Unbounded visual iteration is a documented failure mode (racing-game rip: N rounds on a red visual before escalating to a human; DZ-R5's in-game hard stop exists for the same reason). Every review iteration ends by choosing exactly one action:
continue— this aspect is good; move to the next check or part.refine-spec— the plan was wrong or shallow (missing detail row, wrong connection-map entry, wrong route): go back toblender-assemblyPhase 1/1.5, fix the plan, rebuild from it. Patching geometry around a wrong plan burns iterations without converging.refine-code— the plan is sound; the build doesn't match it. Fix the geometry/material.request-input— show the user the evidence (before/after renders, what was tried, what still fails) and stop iterating.stop— the target is not reachable from this input (unusable reference, wrong approach); say so instead of faking progress.
Keep a per-model iteration history — a JSON list, one entry per iteration: {"fidelity": <your visual 0-1 score>, "defectTags": ["short-stable-labels"], "reverted": <true if the fix made it worse and was undone>}, stored next to the renders (_review/history.json). After appending each iteration, run the stop policy (always --json: the plain output crashes cp1252 consoles):
python references/correction_loop.py --history _review/history.json --json| Condition (priority order) | Verdict |
| fidelity ≥ target and no open defect tags | stop — continue |
| same defect tag survived 2 consecutive fixes | stop — refine-spec: the plan is wrong, not the code |
| oscillating — 2 reverted fixes, or score direction flip | stop — refine-spec |
| progress plateaued (Δ < 0.02, below target) | stop — request-input |
| 6 iterations | stop — request-input, non-bypassable |
The script is the cycle counter nobody keeps under pressure. NEVER argue past its stop verdict with "one more try will fix it" — that is the exact failure it exists to catch. Escalate with the evidence instead.
Per-feature gates: a global "looks right" cannot rescue a failed critical feature
The detail list from blender-assembly Phase 1.5 is this review's contract. Its identity-defining rows — the ones that make the object this object (a grille shape, a stock profile, a hinge) — each get their own pass/fail during checklist step 3, judged on a native-res crop (LL-153). A render that reads well overall with one wrong critical feature fails the iteration; record that feature's tag in defectTags.
Report honestly: "improved" is not "done"
Each iteration's note states what changed (with numbers — "guard edge extended −0.56→−0.48"), why, what still doesn't match, and what the current check is blind to ("passes front view; top view not re-rendered"). A feature that got closer is "improved", never "done" — imprecise language here is how a defect survives into the next session.
Two ways to see — and when to use each
You have two distinct ways to get pixels back, and they answer different questions.
Quick viewport screenshot — get_screenshot_of_area_as_image(area_ui_type="VIEW_3D") returns the current viewport straight to you in one call, no file step. Use it for "what does it look like right now", and especially for the Face Orientation overlay (below), which shows only in the viewport, never in a render.
Reproducible multi-angle render — place a camera at canonical angles and render each to a file, then Read the files. Use it when you need identical framing every iteration, clean lighting, and before/after evidence. The helper below renders all angles in one call and also returns the measurable numbers (dimensions, lowest vertex, counts), so you get numeric and visual in one shot.
Rule of thumb: screenshot to glance, render to judge.
Setup that keeps captures honest
Scale reference. Proportion errors are invisible without a yardstick. Drop a 1 m cube at the origin so every render carries a known unit (size=2 so scale = half-extent, per blender-assembly):
import bpy
bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0.5))
ref = bpy.context.active_object
ref.name = "VR_ScaleRef_1m"
ref.scale = (0.5, 0.5, 0.5) # a 1 m cube sitting on z=0
bpy.ops.object.transform_apply(scale=True)
ref.display_type = 'WIRE' # wireframe so it never hides the modelDiagnostic shading. For shape and topology, Solid + cavity reveals facets and pinching that smooth shading hides. For normal direction, the Face Orientation overlay paints front faces blue and back faces red — the fastest inverted-normal detector there is (viewport only). Set it, then take a viewport screenshot:
import bpy
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
sp = area.spaces.active
sp.shading.type = 'SOLID'
sp.shading.show_cavity = True # facets / pinching
sp.overlay.show_face_orientation = True # front = blue, back = red
breakWorkbench needs no scene lights (it has its own studio light), which sidesteps the classic "render came out black because the scene has no lamp" trap. Use Workbench for diagnosis; switch to EEVEE or Cycles only when you are specifically judging materials.
Render paths in blender -b are absolute (SP-451, measured 2026-10-01). A relative render.filepath
without the // prefix saved the renders under a folder at the root of the drive, not in the working folder.
Pass absolute paths.
Multi-angle capture (the workhorse)
Run this via execute_blender_code. It renders the object from front / right / top / iso to files you can read, restores the scene's original camera and engine, and returns both the file paths and the measurable diagnostics. Then Read each path and actually look.
import bpy, os, tempfile
from mathutils import Vector
def vr_capture(obj_name, tag="iter1", out_dir=None, res=900):
obj = bpy.data.objects[obj_name]
scene = bpy.context.scene
# world-space bounds of the base mesh (good enough to frame and measure)
vs = [obj.matrix_world @ v.co for v in obj.data.vertices]
lo = Vector((min(v.x for v in vs), min(v.y for v in vs), min(v.z for v in vs)))
hi = Vector((max(v.x for v in vs), max(v.y for v in vs), max(v.z for v in vs)))
center, dims = (lo + hi) * 0.5, (hi - lo)
max_dim = max(dims.x, dims.y, dims.z) or 1.0
if out_dir is None: # a folder you can also Read afterwards
base = bpy.path.abspath("//") if bpy.data.filepath else tempfile.gettempdir()
out_dir = os.path.join(base, "_review")
os.makedirs(out_dir, exist_ok=True)
prev = (scene.render.engine, scene.camera, scene.render.filepath,
scene.display.shading.show_cavity)
scene.render.engine = 'BLENDER_WORKBENCH' # fast, self-lit
scene.display.shading.show_cavity = True # facets / pinching in the render
scene.render.image_settings.file_format = 'PNG'
scene.render.resolution_x = scene.render.resolution_y = res
scene.render.resolution_percentage = 100
cam_data = bpy.data.cameras.new("VR_Cam")
cam = bpy.data.objects.new("VR_Cam", cam_data)
scene.collection.objects.link(cam)
scene.camera = cam
dist = max_dim * 2.2
views = {"front": (0,-1,0), "right": (1,0,0), "top": (0,0,1), "iso": (1,-1,0.8)}
renders = []
for name, off in views.items():
cam.location = center + Vector(off).normalized() * dist
cam.rotation_euler = (center - cam.location).to_track_quat('-Z', 'Y').to_euler()
path = os.path.join(out_dir, f"{obj_name}__{name}__{tag}.png")
scene.render.filepath = path
bpy.ops.render.render(write_still=True)
renders.append(path)
(scene.render.engine, scene.camera, scene.render.filepath,
scene.display.shading.show_cavity) = prev # restore, then drop temp camera
bpy.data.objects.remove(cam, do_unlink=True)
if cam_data.users == 0:
bpy.data.cameras.remove(cam_data)
return {"renders": renders,
"dims": [round(c, 4) for c in dims],
"lowest_z": round(lo.z, 4),
"center": [round(c, 4) for c in center],
"counts": {"verts": len(obj.data.vertices), "faces": len(obj.data.polygons)}}
result = vr_capture("YOUR_OBJECT") # set the object name; optional: tag=, out_dir=, res=Read each path in result["renders"] and look. Use result["dims"], result["lowest_z"], and result["counts"] as the measurable backbone for the checklist. (Bounds come from the base mesh; if a modifier changes the silhouette, apply it or evaluate the dependency graph first.)
Diagnostic checklist
Walk every category — the point of a checklist is to catch the defect you weren't already worried about. Each item is a concrete, checkable signal, not a vibe: an invariant written as loose prose gets nodded at and skipped (LL-062), so tie each one to a number or a specific visual cue.
A. General 3D correctness (every model)
- Orientation — does "forward" point where it should; is anything upside down or mirrored? Read it against the scale cube and the world axes, not memory.
- Proportion / scale — measure against the 1 m cube and
result["dims"]. "Looks small" is a prompt to check the number, not a conclusion. - Inverted / black normals — Face Orientation overlay: any red facing the camera is a back-face you are seeing through. Also watch for surfaces lit as if from inside.
- Shading artifacts — black facets, pinching, smooth shading bleeding across a hard edge, n-gons on curved areas.
- Exploded / floating parts — gaps between parts that should touch; a part hovering off its mount. (
blender-assembly'sverify_overlapcatches the quantifiable ones; the eye catches the rest.) - Doubled geometry — a shimmer that flickers between angles means two faces occupy the same place (z-fighting).
- Animated parts: clearance per keyframe, not per render — presentation renders only show posed frames; impacts hide behind walls or closed doors. Evaluate the depsgraph mesh at every keyframe AND at intermediate points of each travel span:
BVHTree.overlapbetween each moving mesh and each fixed mesh, a sweep of fixed-geometry points (vertices, face and edge centres) that fall inside the part's at-rest footprint and travel range, and section cuts on the travel planes. A flush contact also reports as overlap — separate it from a penetration by measuring how deep the geometry enters the footprint, not how many triangles cross. [EXACT] Measured on an animated rock platform (2026-10-01): its 9 presentation cameras showed nothing, while the sweep found a 12 × 14.6 m platform crossing a lip 0.27 m into its footprint and an elevator cabin crossing the top threshold (0.14 m) on every ride, visible only from inside at frame 145. - Workbench
color_type = 'TEXTURE'paints one image of the material, not its shading [EXACT] — on a vanilla rock therock_*_maskinput rendered as flat RGB mask colours and looked like a defect. Judge materials in EEVEE with the file's own lights. (measured 2026-10-01)
B. DayZ parity vs a vanilla reference
Import a known-good vanilla model into the same scene at 1:1 and compare side by side (the import path lives in dayz-model-pipeline). Anchor each check to the reference, not to taste:
- Orientation / scale — same forward axis and roughly the same footprint as the vanilla equivalent.
- Ride-height (measurable) — for ground items, the lowest vertex (
result["lowest_z"]) sits at ≈ the ground plane (z≈0); for vehicles, the wheel contact is ≈ ground and the chassis belly clears it well (a vanilla sedan's belly clearance is ~0.43 m; a too-small wheel sinks the body). Measure it — don't eyeball "looks grounded" (LL-062;vehicle-structural-parity). - Proxy / slot presence — wheels, FireGeo bulk, and lights are visibly where the vanilla has them.
C. Reference-image comparison
When matching a photo or concept: load the reference as a camera background image (cam_data.show_background_images = True, then cam_data.background_images.new().image = bpy.data.images.load(path)), match the camera to the reference's angle, render, and compare named landmarks — roofline, wheel arch, handle position — not overall impression. Report silhouette deviation, missing or extra masses, and proportion drift by pointing at specific points.
D. Photo-resemblance scoring: calibrate the camera first (added 2026-09-25, SP-427)
- One Blender camera per reference photo, calibrated by silhouette IoU — maximize the IoU between the Workbench-alpha silhouette and the photo mask (Nelder-Mead over loc/yaw/pitch/roll/focal, initialized from a known-size feature such as the tyres from the spec sheet). Judge only photo|render crops taken with that same camera: with an eyeballed "similar" camera the proportion errors (a wing out of place, a dome floating 5 cm) stay invisible.
- Poor or partial views — low-res or off-angle photos converge to a false camera (IoU 0.81 on a brochure front view); solve PnP on 6-8 triangulated points across other photos instead, and reject any refinement that LOWERS silhouette IoU (a 2-anchor bundle shifted the camera 38 cm along the view rays and dropped IoU 0.936 to 0.923).
- Silhouette renders — set
render.use_compositing = False: an Alpha-Over compositor makes the whole silhouette opaque whenfilm_transparentis on. - Judge blind, cross-family. [EXACT] Same-family self-scores ran 1-2 checklist points above a blind audit by another model family over the same 27 image sets (measured in Blender 5.1.1 headless against studio photos, 2026-09-25); the working score is the per-criterion minimum of self and blind.
Hard guardrail: a Blender render cannot judge DayZ winding
Blender (and any Three.js viewer) renders right-handed; DayZ culls left-handed. A model that looks perfectly solid in Blender can render inside-out in-game. This is not hypothetical — it is a logged self-error: a render was used to declare "normals not inverted", and the exterior turned out transparent in-game (lessons-learned.md LL-029-era entry; dayz-p3d-audit/SKILL.md:474-476).
- NEVER conclude that DayZ face winding or normal direction is correct from a Blender render or screenshot. The render genuinely cannot tell you. Absolute winding is decided by
dayz-p3d-audit(its edge-pair topology check), not by eye. - The only valid visual winding signal is relative: the model winds the same way as a vanilla reference imported beside it. "Same as vanilla" is meaningful; "looks fine to me" is not.
- The Blender→DayZ map is the det=-1 reflection
(x,y,z)→(x,z,y)with negated normals (dayz-model-pipelineRule 12, measured in game 2026-10-01). A render cannot judge the result: the old det=+1 recipe renders solid in Blender and in DayZ and is still MIRRORED — check chirality on text or a one-sided part in game.
Everything else here — orientation, proportion, scale, exploded parts, ride-height, reference match — the render judges well. Winding is the one thing it cannot; hand that to dayz-p3d-audit.
Winding-flip detection workflow (added 2026-07-30, applies pending append 2026-06-24; LL-162)
A standard lit render cannot expose a winding flip (the guardrail above) because declared/custom split normals mask it: lighting follows declared normals while engines cull by triangle winding. Reproduced on the MercedesAMGLF Phase 2 shell — all 4 views passed here, in-game only back faces textured (LL-162). Two viewport/render mechanisms DO read winding-derived orientation, unaffected by declared normals:
- Face Orientation overlay (already in Setup above) — from OUTSIDE the model, any red face is winding-flipped. Screenshot the 4 canonical angles with the overlay on; a uniformly red exterior means the whole mesh is flipped.
- Backface-culling re-render — set
mat.use_backface_culling = Trueon every material (EEVEE honors it), re-render the same 4 angles, and diff each against its original render (references/vr_delta.pyper angle). Faces that vanish (holes; silhouette IoU < 0.95 on any view) were winding-flipped ⇒ verdictFLIPPED_LIKELY— do not approve the export.
Do NOT "verify" a suspected flip by running normals_make_consistent and re-rendering: recalc rewrites the winding itself, so the re-render comes back clean and the check false-negatives. Recalc is a repair tool; the repair decision for a DayZ export belongs to the import-transform rules in dayz-model-pipeline (LL-504 and dayz-model-pipeline Rule 12: the Blender→DayZ map is the det=-1 reflection (x,y,z)→(x,z,y) with the face order kept and the normals negated; the old (x,z,-y) rotation renders solid but mirrored).
When to run: any import from glTF/FBX/OBJ, any pipeline with a configurable reverse_winding, any build destined for a winding-culling engine (DayZ / Arma / BI). Absolute authority on the exported .p3d remains dayz-p3d-audit's topology check; this is the free early warning that saves the in-game cycle.
Quantified, and why checklist Q5 must never be scored by eye (measured 2026-08-21)
The guardrail above was reproduced from the other direction, on a purpose-built probe whose
winding is reversed by construction (q5_normals_build.py pattern: same object, same camera,
same shading, only the winding differs):
| capture | faces reversed | pixels changed |
this skill's own settings (Workbench + show_cavity, culling off) | 62 (face-0 normal dot -1.0001) | 0.00% |
| identical, backface culling on | 62 | 36.92% |
Two variants at different severities came out byte-identical to each other. The defect is absent from the pixels, so no reviewer can answer from a lit render -- which is exactly what the section above says, now with a number.
Consequence for references/checks_hardsurface.json. Its entry 4 -- "Are there any black,
inside-out or wrongly shaded faces visible?" -- asks a VLM the one question this skill states
a render cannot answer, over non-culled captures. It carries skip and must be routed to the
culling diff above, never sent to or scored by the VLM. A gate that cannot go red for the
cause it targets is decoration.
And it is not a local-model limitation. Given the culled pair where the flip changes 36.92%
of the image and the interior of the cylinder is plainly visible, ornith-1.5:9b,
qwen3-vl:30b and muse-glimmer:30b each answered NO on both variants across 3 seeds --
0/3 separation, all three -- and justified it fluently: "the visible faces are the front cap
and the exterior side of the cylinder", said of a render where the front cap is culled away
and the interior is what is on screen. The failure mode is a confident green, not a refusal.
What the local judges DO and DO NOT hold up on (same tanda)
Whole shipped checklist, over a pair differing only in facet count (48 vs 6 sides), 3 seeds. Only the faceting question should move; everything else should hold still:
| model | correct | the miss |
qwen3-vl:30b | 8/8 | -- |
ornith-1.5:9b | 7/8 | calls the SMOOTH variant holed, 2 of 3 seeds |
muse-glimmer:30b | 7/8 | reads the FACETED variant as bevelled, 3 of 3 seeds |
So the incumbent is the cleanest of the three, and a bigger or newer model did not beat it. Scope: one object, one pair, one framing where the round part fills the frame -- the condition where the faceting question is already known to work. It screens models; it does not rank them.
Deterministic delta check (report-only; added 2026-07-30)
Before judging a fix by eye, run the pixel-math comparator on the before/after pair of the SAME angle:
python references/vr_delta.py _review/model__front__iter1.png _review/model__front__iter2.png --jsonIt reports silhouette IoU, bbox scale/aspect deltas, SSIM, edge overlap, tonal/blowout/flat parity, and which signals moved beyond same-camera noise. Two uses:
- Prove the fix landed — an intended edit must move at least one signal;
identicalImages: trueafter an edit means the old scene got re-rendered (a real reproduced failure mode: the MCP render-depsgraph staleness documented inblender-assembly). - Catch collateral drift — signals moving that the edit shouldn't touch (a tonal shift after a pure geometry fix; a silhouette change after a material tweak).
Per LL-153 these numbers are a change filter, NEVER a correctness verdict — correctness is decided by eyes on the renders and native-res crops. Same-camera pairs only: scoring a render against a reference photo is not calibrated here (framing/background mismatch breaks every signal; that comparison stays with §C's named-landmark method).
Free local second opinion — SHADOW MODE (added 2026-07-30)
A local vision model served by Ollama answers checklist questions about renders at zero credit cost, so agent budget goes to gates and final judgment instead of every inner-loop glance. references/vr_score.py:
python references/vr_score.py ask _review/model__iso__iter3.png --checklist references/checks_hardsurface.json --model gemma4:26b --json
python references/vr_score.py score _review/model__iso__iter3.png --reference ref.jpg --model gemma4:26b --jsonPrefer ask over score: concrete yes/no questions are measurably easier for a small VLM than open judgment, and each "no" is a specific pointer instead of an opaque number. Derive the questions from the model's own detail list (blender-assembly Phase 1.5) — checks_hardsurface.json is the generic starter set.
This is SHADOW MODE and it is not negotiable: the local model's answers are evidence logged beside your own, NEVER a gate, NEVER a substitute for looking. Every call appends to a JSONL shadow log; that log IS the calibration dataset, built free during real work. Promotion path: shadow → pre-filter (it discards the obviously broken, you judge the survivors) → never final judge. Run references/vr_calibrate.py report against ≥15 renders you have judged yourself before delegating any single question; it reports agreement per question, which is the unit that gets delegated — not the model as a whole.
Measured 2026-07-30 (RTX 3090, n=2 renders — provisional): gemma4:26b ≈15 s warm per 8-question checklist, qwen3.5:27b ≈132 s for comparable quality, so gemma is the default. ⚠ That 9x gap was REFUTED in its cause on 2026-08-16: it was not the model, it was the factory num_ctx offloading 37% of qwen to CPU. With think:false + num_ctx=8192 it is 8.3 s and 17.2 s — 2x gap, and qwen becomes viable again as second opinion from another family. See §Pre-filter below. Both correctly flagged a faceted circle and both answered "unsure" rather than guessing when the view could not decide — but they gave opposite bevel verdicts on the same renders, so treat bevel/chamfer questions as agent-side until calibration says otherwise. Judge fine surface questions on a native-res crop, never a full-frame render (LL-153).
Setup notes: models live wherever OLLAMA_MODELS points; a 17 GB model fills a 24 GB card, so models load one at a time and compete with Blender for VRAM — unload before a heavy render. Always address the server as 127.0.0.1, never localhost: on Windows that resolves to IPv6 first and a stray IPv6-bound ollama serve will answer from a different model library.
Output / evidence convention
Render into a folder you can also read (the helper defaults to a _review/ folder next to the .blend, or the OS temp dir if the file is unsaved) and name files <model>__<angle>__<iter>.png. Keep the before/after pair for any angle you changed, so the diagnosis is auditable and you can prove the fix actually landed.
VLM Checklist: five active questions, three excluded (added 2026-08-16, SP-277; calibration data in SP-270)
The shadow section above governs any question not in this table. The JSON preserves
eight entries: five reach the model and three have skip; only the pre-filter rows change the loop.
The filter REJECTS, never APPROVES. A "no" triggers a fix before showing anything; a clean pass does not mean "it is fine", it means "it now deserves your eyes". The rule that the local model is never the final judge remains intact: it has been delegated the power to stop, not to approve.
What is calibrated and what is not
Measured on mk47_mutant, with the broken/fixed pair of the SAME object, three models, the actual
grouping of vr_score.py. The entire checklist achieves 77.8% against a constant response floor of
58.3%:
| question | accuracy | in the loop |
| connectivity / floating parts | 12/12 | pre-filter — by far the strongest |
| gaps or holes in the surface | 10/12 | pre-filter |
DO NOT delegate — skip: defect is not in capture; use mechanical culling diff | ||
| finished object vs bare boxes | 10/12 | pre-filter |
| DO NOT delegate (retracted 2026-08-22) — that 10-12/12 is an unfalsified score: broken/fixed pair against which it was measured contained no proportion defect, so answering "yes" always scores perfect. See note below | ||
| bevels / chamfered edges | 6/12 | DO NOT delegate — is at constant response floor. Confirms 2026-07-30 warning: both models gave opposite bevel verdicts |
| faceted cylinders | at random across 8 framings | DO NOT delegate, and do not rewrite it — four phrasings tested (2026-08-17) and the existing one is best: 6/6 in framings where round piece fills frame, 1-2 of 3 in whole object framing. Framing outweighs phrasing. Underlying failure is that object cannot answer it, see note below |
Before believing a high score from this table (2026-08-22)
A yes/no question has two failure modes that its own score hides, and the proportions row fell into both:
- The constant. If test set gold never changes value, constant response scores perfect and metric remains unfalsified, not validated. The question looked 10-12/12 because pair against which it was measured had no proportion defect. The check is one line: does gold change value in the set? If not, you measured nothing.
- Confabulation. When asked for rationale it does not hesitate, it invents. Facing a flange 37% wider than expected, one model declared it consistent with "standard fastener geometry" and another "appropriately scaled". A confident and false note is worse than "unsure".
Measured on 2026-08-17 with gemma4:26b, qwen3.8:27b, and qwen3.5:27b: with two constructed
defects, the question answered "yes" in all 9 cells (3 models × 3 variants).
Asking instead for the measurement —size and position of each piece as fraction of
frame, scored by direction of change— got 6/6 right on the same renders.
The measurement substitute is not yet implemented here: probe_truth.py does not exist
in references/ and the question remains in checks_hardsurface.json:29 as-is. Until
it is, this row is checked by eye and not delegated. And when implementing it, the trap that already
bit: declaring a coordinate convention does not beat the model's up-down
prior — asking for y=0 at bottom and mirroring its responses reduced vertical error from 0.107 to
0.017 of frame. Use image convention or detect mirror before scoring.
Where it enters the loop
Between step 2 (capture angles) and step 3 (look) of §The loop:
python references/vr_score.py ask --checklist references/checks_hardsurface.json \
--model gemma4:26b --json \
--view assembled__iso _review/m__iso__iter3.png \
--view assembled__profile_R _review/m__profile_R__iter3.png \
--view assembled__profile_L _review/m__profile_L__iter3.png \
--view zoom_receiver__right_iso _review/m__receiver__iter3.png \
--view zoom_muzzle__front_iso _review/m__muzzle__iter3.pngIf any of the five active ones triggers: fix and re-render before spending user's eyes.
If none triggers: look anyway — filter has not approved anything. The three excluded do
not appear in model responses: inverted faces go to mechanical diff; proportions and
cylinders keep gap declared until probe_truth.py and facet_report.py exist.
Calibration gate changes: per question, with broken/fixed pair
The shadow section asks for ≥15 already-judged renders before delegating a question. That is calibrating by accumulation and would have found none of this: the same question goes from 7/12 to 11/12 just by changing framing, and from 9/12 to 18/18 just by changing which other questions travel in the call. It is replaced by a cheaper gate that also attributes:
Take two renders of the SAME object, one with defect and another with defect fixed, and ask the same question to both. Needs no gold. Measures two things that are not the same:
- sensitivity — does response change where it should change?
- specificity — does it stay still where it should not change?
A judge answering identically to both versions is not reading the model, whatever score it may have. Any mod with a before/after serves as testbench: nothing needs to be manufactured.
Three things that invalidate a framing comparison
- The batch of the call changes the response. Same question, same image, same model: 18/18 in a call with four other shading questions, 9/12 in call with eight of the checklist. Comparing framings requires keeping batch fixed. And it is not size: removing a question from batch worsened the two that stayed.
- A framing without fixed render is not comparable to one that has it: scores over half of cells and there a constant response gets 6/6.
- Temperature is set to 0.1 in payload, so two samples of same cell turn out
almost identical: effective
nis number of cells, not number of samples. Do not confuse repeating with measuring.
Scope, so as not to repeat the error that originated this
All of the above comes from a single object. The day before I recorded that these models "do not detect
geometric defects" — and it was false: it measured my harness, not the models, and closed a line of work
that worked. Before writing that a model cannot do something, have varied phrasing,
framing, and batch, and state which of the three was varied. Origin and evidence in LL-289.
Rules promoted from the lessons corpus (added 2026-07-27)
Promoted from AI/20_Knowledge/lessons-learned.md so that they arrive via trigger instead
of relying on someone remembering to look them up. Each rule cites its source LL-NNN;
the full entry (symptom, origin, evidence) lives there. Do not remove the citation: the index
lessons-index.md detects promotion by searching for that reference inside the skills.
- LL-153 — Judge every critical visual zone on a native-resolution crop. Use RMS and scores only as change filter; never as correctness verdict, and keep full-res crop as evidence.
Q3 closed: do not rewrite or delegate faceting (SP-282, added 2026-08-31)
Current cylinders phrasing is best of four measured, but that does not make it a
gate. Asked alone it got 6/6; within the batch of eight of vr_score.py it fell to 3/6. On
actual object it produced 8 false alarms out of 12: visible octagonal piece is intentional and
authentic smoothed cylinders have subpixel sagitta in those framings. Therefore:
- keep question without rewriting, with
skip; does not enter active checklist or pre-filter; - do not describe result as chance: failure changes with framing and grouping;
- if asked to count a profile, frame the section until it fills the frame. Generic
distance
max_dim * 2.2of an elongated object leaves axial view without information; - correct substitute is deterministic and reports, does not judge: part, sides, radius, relative deviation, and sagitta, with allowlist for intentional prisms. Intention remains a decision of the modeler.
facet_report.py continues without being distributed in this skill. This section establishes its contract and its
limits; does not claim that executable is installed.
Calibrated contract of faceting report (SP-284, added 2026-08-31)
If facet_report.py is incorporated, calibrated candidate rule is sides < 12 and
radius >= 6 mm, never provisional 4 mm threshold. Furthermore, requires an almost square
cross section: ratio between sides of transversal bbox <= 1.30. That limit admits pentagons and
hexagons, but rejects planar shapes that the 20% outer filter confuses with a cylinder.
An allowlist silences deliberate prismatic profiles.
The coverage report is as important as flags. Must publish:
- meshes visible in render and parts with circular profile;
- examined polygons and percentage over visible ones;
- largest unexamined mesh and its percentage.
0 marked is INCONCLUSIVE if there are no visible meshes or if a single unexamined mesh
concentrates >=30% of polygons. Overall percentage covered does not decide verdict: an
asset composed mainly of boxes and rails can have low coverage and be valid for
examined parts.
Declared calibration: 0 false alarms across 41 circular-profile parts from three real assets and all three parts of synthetic defect remained marked; 12- and 48-sided probes remained clean. Limits: works per object, does not see tubes inside merged mesh, can miss thin or heavily slotted profiles, and does not certify that undetected geometry is round. Corpus does not include a published mod verified in-game; do not elevate this report to final gate.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

