img2threejs — Image to procedural Three.js
Rebuild the object visible in a reference image as a code-only procedural Three.js model,
gated by a staged sculpting pipeline and an AI-vision self-correction loop. This is
reconstruction-by-code, not photogrammetry, mesh extraction, or downloaded art packs. That
promise governs how the model is built — it says nothing about which file formats it can
subsequently be exported to; an explicitly-selected emission target (--target <kind>) is a
terminal, whole-artifact transform of the already-built model, verified to its own stated limit,
never a second way to build one.
Agent-agnostic: works under Claude Code, Codex, or OpenCode. Wherever this doc says "agent
vision" or "agent browser tool", use whatever the host provides — native image reading, a
browser MCP (playwright/chrome-devtools), the project preview, or a user-supplied screenshot.
This file is the always-loaded router: it holds the order of operations and every hard rule as one
line. The full contract behind each rule lives in the grimoire/ or docs/ file that rule names —
read the named file at the moment you reach that stage, not before.
Canonical shared checkout
Keep one checkout of this repository and let every host enter it through a symlink, so Claude and
Codex execute the same code instead of drifting apart:
~/.claude/skills/img2threejs -> <your checkout>
~/.codex/skills/img2threejs -> <your checkout>
When To Use
The user attaches/points to an object image and wants a procedural Three.js model, a
reconstruction/animation/destruction plan, a sculpt spec, or code. Also for material studies,
action-ready props, game objects, botanical/mechanical parts, and stylized reconstructions.
Core Promise
Sculpt from a photo, in order — never one-shot a mesh:
- Run
python3 forge/next.py --state .img2threejs/state.json [<spec>] first, at every start,
resume, and before every correction iteration. It reports the ordered checklist, exact next
command, evidence status, and bounded correction-loop status; it never replaces the spec/pass
gates. Obey a hard stop; never continue from memory.
- Validate the image is a suitable 3D target (
grimoire/intake/validation_rubric.md).
- Assess object class + complexity, then write a
qualityContract before any code.
- Spec it: component hierarchy, materials, lighting, pivots, sockets, action anchors.
- Build pass-by-pass from blockout → structure → form → material → lighting → interaction → optimization.
- Verify each pass with a screenshot compared against the reference; fail a pass if an
identity-defining feature is wrong even when the global score looks fine.
State explicitly when output is approximate/stylized/low-poly. A single image cannot reveal
hidden sides or guarantee exact geometry — say so instead of faking confidence.
Mandatory Local State Gate
Conversation context is disposable; .img2threejs/state.json is the local checklist authority.
Initialize once per reconstruction, then gate every step through it:
python3 forge/state.py init --state .img2threejs/state.json --reference <img> --profile <generic|character|installed-domain> --spec object-sculpt-spec.json
python3 forge/next.py --state .img2threejs/state.json [object-sculpt-spec.json]
python3 forge/state.py mark <step-id> --state .img2threejs/state.json --evidence <path>
next.py prints the current step, pass, incomplete mandatory steps, exact next command, and
loop/max. Exit code 3 or status=stopped is a hard stop: report the reason and request input.
Never bypass it by reconstructing progress from chat history.
- Every completed step needs evidence; mark a non-applicable step
skipped only with --reason —
silent omission is forbidden. Loop counts derive from reviewHistory actions
(refine-spec/refine-code), not agent memory. Defaults: 3 corrections per pass, 6 total.
- A domain profile's steps, gates and reference material come from the registry: in-repo
modules (
character) and installed plugins (cs2, animated-character from plugin-character) register identically, and
forge/state.py init names what is available. A profile adds mandatory gates without changing
the core order -- a domain plugin typically requires an authoritative classification, an intake
manifest, and a machine-readable domain review before AI review; character requires the
character contracts and landmark evidence; animated-character (requires the installed plugin-character) adds all of character plus the
nine Stage R steps (grimoire/readiness/animation_contract.md). Pick it whenever the rig must
MOVE — on character the Stage R gates are absent and the build completes without ever running
them, which is how animation used to ship broken. Its order is load-bearing: repair the mesh,
freeze it, bind additively, then verify parity. Every profile records suitability, projection
applicability, and
material-evidence applicability. The state file is a resumability index, not visual evidence:
renders, specs, review history, and deterministic gates remain the authoritative artifacts.
Required Inputs
- one image path / screenshot / URL / attached image (if missing or unreadable, ask)
- intended use: prop, game object, hero render, playable/destructible object, animation rig
(default: real-time browser prop with interactive performance)
- when a domain plugin serves the item, whatever authoritative record its intake step requires, or
an explicit request for the user/vision provider to supply one; heuristic detection alone is not
enough to select a geometry adapter
The Loop (scripts do enforcement; agent vision does judgment)
Run scripts from the skill root (forge/...). Pure Python 3.10+ stdlib, no pip installs.
Full flags: grimoire/scripts.md. Never let a script score visuals — that is the agent's job.
- Analyze the image first (agent vision, before any script): work the layered observation
protocol in
grimoire/intake/image_analysis.md — identify/classify, decompose macro→meso→micro,
map part relationships, name materials in PBR terms, list identity-defining features, and flag
what the single view hides. Observation before inference; controlled 3D vocabulary; 3D
object-space not 2D image-space. Then probe local images:
forge/stage1_intake/probe_image.py <image> (metadata only, not a visual check).
1a. Local Spec Search — after image analysis, before writing or refining a spec, pull local
domain evidence (anatomy/PBR/wear/geometry/runtime/physics) rather than inventing it:
python3 forge/stage2_spec/new_pre_spec_assessment.py "Name" --image <img> --out assessment.json
(auto-runs BM25 over the core_3d collection, or the collection a declared domain contributes
-- the collection is NEVER guessed from the target name; writes a localSpecSearch bundle that
new_sculpt_spec.py --assessment carries into the spec). Full query-expansion recipe
(bilingual terms, focused search_specs.py retrieval, cache rules):
grimoire/intake/local_spec_search.md. MUST read it before retrying an incomplete or
domain-specific query.
1b. Domain intake — when a domain plugin serves the item, complete its intake steps before
pre-spec authoring (admission, heuristic signal, classification, family/route resolution).
MUST read the contract its step names, completely, before creating the manifest or
running pre-spec assessment.
1c. Optional fidelity evidence adapters — only when they improve an observed weak point; the
stdlib core remains authoritative. Thin/complex masks → local SAM2; character face/pose →
MediaPipe; weak front/back cues → Depth Anything V2
(forge/stage1_intake/run_vision_adapter.py <segment|landmarks|depth> ...; every adapter emits
provenance; monocular depth is relative only). MCP-only scene mutations never count as
implementation — write the proven change back to the spec or TypeScript, rebuild, recapture.
Full adapter + MCP routing and authority boundaries:
docs/integrations/reference_fidelity_tooling.md.
- Pre-Spec Assessment Gate — classify + score complexity + write the quality contract:
forge/stage2_spec/new_pre_spec_assessment.py "Name" --image <img> --complexity <simple|moderate|complex|ultra-complex> --out assessment.json. Rules: grimoire/intake/quality_contract.md.
Set objectClass.primaryDomain (object | character | hybrid) and fill the seeded
detailInventory (its targetMinDetails scales with complexity). A domain plugin may raise
these floors through its augmentation -- the merge clamps, so a plugin can never lower one
(a skin's finish/wear/hardware IS the item, so such a domain is held to the
top fidelity bar. Author procedural GEOMETRY but route the FINISH through the projection path in
step 2c — a procedural finish for a patterned skin (Doppler/Gamma/Marble/Fade) reads visibly
wrong against the reference. A domain plugin ships its own finish rulebook and texture-acquisition
guide; read what its checklist steps name.
2b. Detail inventory (do not skip for detailed subjects) — scan zones and enumerate every
identity-defining small detail (gloss, bevel, fasteners, linework, contours, stains):
forge/stage1_intake/build_detail_inventory.py <image> --mode grid-3x3 --out-dir <dir> --out di.json.
Each detail MUST map to a component.localFeatures or material.localOverrides entry — never
prose only. Taxonomy + 3D-term recipes: grimoire/intake/detail_inventory.md.
2c. Projection-first fidelity (characters AND reference-matched surfaces — painted skins,
decals, painted patterns) — when the goal is matching a specific reference's surface, put the
photo's own pixels on the mesh instead of approximating them procedurally. This is the single
biggest fidelity lever; a procedural material for a patterned surface is the #1 reconstruction
failure. Recipe (grimoire/character/likeness_maximization.md — its two levers generalize past
characters): solve the camera (stage1_intake/solve_camera_pose.py → referenceCamera),
de-light the reference (stage1_intake/delight_albedo.py, hard requirement — de-lighting is
what makes projection safe), then project the de-lit crop and bake it into UVs
(stage3_build/bake_projected_texture.py --mesh-id <id>). For a painted skin the projected de-lit
crop IS the finish — no procedural Doppler material. For characters, first capture landmarks
(stage1_intake/extract_landmarks.py --out anatomy.json), fill preSpecAssessment.anatomy,
route grimoire/character/reconstruction.md. A single view cannot show hidden sides — report
per-region confidence and request more views when it matters.
Character sub-routes, in order — decide what parts exist before shaping any, and shape the head
before the hair that sits on it:
- Parts —
grimoire/character/structure_decomposition.md
- Head —
grimoire/character/head_construction.md (what the likeness gate reads against)
- Hair —
grimoire/character/stylized_hair_threejs.md + parameter contract in
grimoire/character/threejs_hair_parameter_contract.json. Lock topology only after the
silhouette review passes: material tuning cannot repair wrong lock topology.
2d. Reference-free humanoid — a generic figure with no reference image has nothing to measure,
so fill anatomy from public canon:
forge/stage2_spec/humanoid_proportions.py <spec> --style-heads 8 --in-place. It writes
anatomy.source: "canon-table" so canon is never mistaken for measurement, refuses to run when
the spec names a reference image, and names anything the corpus does not supply rather than
interpolating it.
- Author the spec from the assessment:
forge/stage2_spec/new_sculpt_spec.py "Name" --image <img> --assessment assessment.json --augmentation spec-augmentation.json --domain <profile> --out object-sculpt-spec.json (the checklist step carries the resolved flags).
Replace generic starter featureReviewTargets with the object's real identity-defining
systems (≤5 critical, ≤3 important per pass); for characters add anatomy-proportion,
face-landmark-placement, pose-silhouette, outfit-and-palette. Use 3D-graphics terms only
(grimoire/glossary/3d_vocabulary.md), never "nice/smooth/shiny". Classify every component's
topologyClass/topologyRationale per grimoire/intake/surface_topology.md before picking a
primitive — this is what prevents a continuous organic form from being picked as a box.
- When material fidelity matters and a source image exists, analyze each material's finish then
extract reference PBR evidence, both per crop (verify the crop is on the part you think it is):
forge/stage1_intake/analyze_texture.py <crop> --spec spec.json --material-id <id> --in-place
classifies the finish, extracts the gradient palette, and writes doc-grounded
MeshPhysicalMaterial scalars onto the material. Recipes + Three.js texture/PBR rules:
grimoire/build/threejs_texture_reference.md. Rule of thumb: solid albedo for flat paint,
real reference crop for patterned finishes.
forge/stage1_intake/extract_pbr_evidence.py <crop> --out-dir <dir> --material-id <id> --target-threshold 0.7.
Confidence < 0.7 is a stop/refine-input signal, not a pass. It is inference, not inverse rendering.
- For multiple named regions:
forge/stage1_intake/material_region_analysis.py --manifest regions.json --out-dir material-evidence --out material-analysis.json,
resolve each assignment from docs/materials/material-reference.json, wire it in with
forge/stage2_spec/apply_material_analysis.py.
- Emit the controlled material camera/crop contract (
forge/stage4_review/material_views.py),
compare visible-footprint crops (material_comparator.py), apply only bounded material-scoped
corrections (material_feedback.py), and record the blocking result (material_gate.py).
- Validate, then strict-validate before generating code:
forge/stage2_spec/validate_sculpt_spec.py object-sculpt-spec.json then --strict-quality.
Strict blocks shallow specs (a complex object with one root, no repetition systems, no
local overrides, no micro groups is NOT implementation-ready even if JSON validates).
- Locked build passes — only touch the currently unlocked pass:
forge/stage3_build/orchestrate_passes.py status object-sculpt-spec.json
forge/stage3_build/generate_threejs_factory.py object-sculpt-spec.json --out src/createObjectModel.ts
The generator is fail-closed: strict-quality must pass before it writes any factory, and a
future --pass-id fails until prior passes are reviewed continue. If blocked, preserve the
BLOCKED artifact and refine the subject-specific spec; do not substitute a generic template.
The local state adds --force only for a new pass or refine-spec; refine-code edits the
current artifact without regenerating it. Before overwriting, carry valid hand refinement back
into the spec; generated code must not be the only copy of reconstruction decisions.
6a. Hitting a triangle budget. performanceBudget.targetTriangles selects a tessellation tier
for every primitive with segment counts (low ≤6k, standard ≤60k, else hero) and caps
implicit-surface sampling grids. Where a tier is not precise enough, add
geometryDescriptor.decimate: {"targetRatio": 0.4} to that component — a quadric collapse in
the generated factory, run before skin binding so weights are computed on surviving
vertices. It keeps position only (normals recomputed), so it is refused on an
authored/unwrapped uvStrategy. Offline LOD tiers:
forge/stage3_build/decimate.py <mesh.json> --ratio <r> --json.
- Render the current pass in a browser/preview, capture a screenshot at a review viewpoint.
7a. Off-axis and placement gates — a single review viewpoint is not evidence about the model.
Capture a turntable, not one frame, and run all three; each catches a defect class the older
gates pass by construction (a hole through a skull, a hat at hip height and a floating charm all
survived eight front-only review rounds):
forge/stage4_review/turntable_gate.py --capture 0=front.png --capture 90=right.png --capture 180=rear.png --capture 270=left.png --json
node runtime/scripts/export_mesh_geometry.mjs --url <preview> --out meshes.json then
forge/stage4_review/self_intersection.py meshes.json --json
forge/stage4_review/attachment_anchor.py object-sculpt-spec.json --measured measured.json --json
All three exit 0 clean / 1 gate failure / 2 error. A failure blocks continue even when
the global fidelity score passes. Read sampledVertexCount / unmeasuredAttachments /
missingAzimuths before believing a clean verdict: each names what the gate did not look at.
- Run deterministic gates before AI vision. MUST read
grimoire/review/gates_reference.md and grimoire/review/self_correction.md completely. Run
forge/stage4_review/diagnose_render.py and record the passing Tier 1 result with
--spec object-sculpt-spec.json --pass-id <pass> --in-place; for non-planar forms also run
forge/stage4_review/diagnose_render_multi_angle.py with the fixed view and at least two
meaningful orbit views. Then run
forge/stage3_build/orchestrate_passes.py check object-sculpt-spec.json --pass-id <pass>.
- Package one side-by-side sheet, then inspect it with agent vision:
forge/stage4_review/make_comparison_sheet.py --reference <img> --render <shot> --out cmp.png --json.
- Record the review (overall + per-layer + per-feature scores + decision):
forge/stage4_review/append_review.py object-sculpt-spec.json --pass-id <pass> --fidelity <0-1> --action <continue|refine-spec|refine-code|request-input|stop> --summary "..." --render-screenshot <shot> --comparison-image cmp.png --ai-vision-score <0-1> --layer-scores-json '{...}' --feature-reviews-json <f.json> --in-place.
When a domain plugin contributes a review gate, produce its versioned report first with
the command that plugin's review step names, then attach it with
--domain-review-json <report>.json --review-scene-json <the plugin's scene fixture>. The
checklist step carries the resolved paths.
A failed family, painted-region, projection-coverage, critical-detail, or orbit gate blocks
continue even when the global score passes. See the plugin's own review-gate documentation.
- Sync pipeline state after manual review edits, record checklist evidence, then re-run the local
state gate before another correction or pass:
forge/stage3_build/orchestrate_passes.py sync object-sculpt-spec.json --in-place
python3 forge/next.py --state .img2threejs/state.json object-sculpt-spec.json.
- Before declaring completion, run
forge/stage4_review/check_part_coverage.py --spec object-sculpt-spec.json --manifest parts.json
and verify the action-ready hierarchy. Mark part-coverage and action-ready only with evidence.
GLB-mediated v2 render-fidelity track (1.5 alpha)
When the user supplies a GLB as an intermediate reference, the browser-rendered GLB is the
structural and visual baseline for an independently authored procedural factory. The raw GLB is
never pixel evidence and its topology/materials are never copied into the factory. Before any
factory edit — full contract in grimoire/build/python_threejs_render_bridge.md, machine-readable
schema in docs/specs/render-profile.v2.schema.json (+ example; fail-closed validation):
forge/stage1_intake/probe_glb.py first. A merged one-node/one-mesh asset is insufficient
for semantic labels; request a multipart GLB or a browser semantic-ID pass before claiming
exact regions.
- Author ONE shared
render-profile.v2 (forge/stage4_review/validate_render_profile.py) used by
both the GLB and procedural routes. Region IDs are subject-specific, never inherited from the
example profile; declare the required set in extensions.requiredSemanticRegions so omission is
a hard validation error.
- Capture six passes per admitted view (
beauty, alpha-silhouette, semantic-id, depth,
normal, roughness-material-id); score with forge/stage4_review/compare_region_passes.py.
Missing semantic-ID data blocks per-region confidence rather than falling back to whole-image
scores.
- Use region-specific continuous geometry — never replace a face/head volume, cloth shell, or tail
with floating primitives when the region's silhouette requires a continuous surface.
- Run ONE correction group per loop, in order:
camera → silhouette → face → clothing → accessory → materials → lighting; recapture the full pass set after each group and record the changed
group, hashes and score. Never combine groups when diagnosing improvement.
Gates (do not skip)
Before any visual review or continue decision, MUST read the full gate-by-gate contract in
grimoire/review/gates_reference.md (Divine Eye, VLM rescue, multi-angle, interior difference,
chirality, hair, domain review, bounded correction, Divine Eye fitting, screenshot feedback, assembly,
attachment, material, detail inventory, rig payload, character track). In short:
- Validate references first (
grimoire/intake/validation_rubric.md, check_reference_admission.py).
divine_eye.py is deterministic-first; the VLM (vlm_gate.py) is a gated last layer, never
consulted on a hard-gate failure.
- A non-planar form must hold from ≥2 angles (
diagnose_render_multi_angle.py).
- Measure INSIDE the silhouette every visual pass (
interior_difference.py). Silhouette IoU reads
~11% of figure cells: a model with its face deleted scored the same 0.8803 as the finished face.
- Every
-l/-r pair is a MIRROR, not a rotation — hard at spec time (validate_chirality). A pair
wrong the same way on both sides still passes, and needs medial_lateral_bias vs a reference.
- Hair subjects:
scalp_exposure.py is HARD and runs on geometry before any render; hair_gate.py
is soft and subordinate to it. A coverage shortfall never authorises widening the masses.
- Flat colour regions with hard boundaries (blaze/bib/socks, livery stripe, painted marking) are an
identity feature, so their boundaries are gated on geometry:
vertex_region_gate.py. Never a
texture — this pipeline emits code; the shape predicates live in _shared/vertex_paint.py.
- A curve claim ("curled into a hook, not a straight cone") needs
swept_arc_gate.py: silhouette IoU
passes a straight cone occupying roughly the right cells.
- Character builds validate the rig payload (
stage5_rig/validate_rig_payload.py) before binding a
THREE.Skeleton; it proves payload integrity only, never pose stress or likeness.
- A rig that must MOVE runs the animation gates too (
grimoire/readiness/animation_contract.md;
the checklist steps and gate come from the installed plugin-character -- stage5_rig/ remains in
this repo as the emitter's library, not the checklist authority). A clip that exists is not a
clip that plays: only G1
(maxSampledBindingDelta <= 2^-23) separates the two, and a gate whose input is missing reports
unevaluated, never a pass. Bind at IDENTITY in attached mode and take the display offset from
the mesh bounds alone; loop is decided by poseReturn, never by travel.
- A domain plugin's review gate also runs against its versioned scene fixture.
- Local state enforces 3 corrections per pass and 6 total by default; reaching either limit is a
hard stop.
correction_loop.py may stop earlier on repeated defects, oscillation, or plateau.
continue requires a render + comparison sheet + AI-vision score ≥ threshold, every critical
feature ≥ its own threshold (grimoire/feedback/render_capture.md).
- Every model ships explodable AND clickable — a structure gate, not pixels
(
check_part_coverage.py, grimoire/build/geometry_patterns.md).
- Action-ready, attachment, material/lighting, detail inventory, and character-track requirements:
grimoire/readiness/action_rigging.md, grimoire/readiness/joint_attachment.md,
grimoire/feedback/shading_realism.md, grimoire/intake/quality_contract.md,
grimoire/intake/validation_rubric.md.
Self-Correction
After every pass, decide exactly one: continue | refine-spec | refine-code | request-input | stop.
refine-spec fixes a wrong/missing/shallow spec (re-validate, don't patch code around it);
refine-code fixes geometry/material/lighting that doesn't match a sound spec. Before making the
decision, MUST read the root-cause guide + fidelity scale in grimoire/review/self_correction.md,
record the decision, and re-run the local state gate.
Small features need a different instrument. Divine Eye's SSIM/tonal/edge signals run on a 64×64
luma grid, so a detail a few pixels wide is absent before any comparison happens. When fidelity
depends on individual tears, spars, fangs or eyes, use the four-tier microscope:
grimoire/review/divine_eye_microscope.md. Two empirically established rules from it: measure
fidelity on a component's visible footprint (full frame minus a component-hidden frame), never
on an isolation render; and never colour-gate a concave feature, where a dark ratio captures
cavity shading rather than material.
Transparency and Process Debugging
Report what changed each pass with evidence (exact values/coordinates), name what still doesn't
match, and never claim "done" when only "improved". A passing gate is not proof of 3D realism.
Full rule + examples: grimoire/review/self_correction.md.
Left and right
A left/right pair is a reflection, never a rotation: negate the lateral axis and nothing else,
(x, y, z) → (-x, y, z). With forward: +Z, Y up and a right-handed frame, the character's own
left is +X. The convention lives as code in forge/_shared/chirality.py
(CHARACTER_LEFT_SIGN), with two different gates for the two defects that shipped from getting it
wrong: validate_chirality catches a rotation-mistaken-for-reflection at spec time, and
medial_lateral_bias vs a reference catches a pair that is wrong the same way on both sides.
Reflecting also inverts triangle winding — flip it back on the mirrored side or flatShading
lights the limb as though lit from behind. Full write-up with the measured defects:
grimoire/scripts.md ("Left and right").
Hair
Hair has its own subsystem because it has failure modes no other gate can see. Full contract,
measurements and non-goals: docs/HAIR_PIPELINE.md. The hard rules:
- Roots bind to the scalp as
(u, v), never absolute positions (hard validation error).
standProud is enforced by the generator, not advisory.
scalp_exposure.py is a HARD gate on geometry before any render; a coverage shortfall never on
its own authorises widening the masses.
- Default representation tier is
shell, not locks; strand impression comes from faceting and
material (hair.human.code-only), since this skill emits no textures.
plane-card is rejected for hair (needs an alpha texture this skill cannot emit).
- Hair is rigidly parented, never smooth-skinned (the geodesic field runs through the skull).
Domain plugins
A domain plugin makes a run exact where this pipeline would otherwise infer. It contributes its own
checklist steps and gates, and publishes a spec-augmentation.json that this pipeline pulls at
spec-authoring. With no plugin serving the item, nothing here changes: author the skeleton and
infer the shape from the reference, as for any other object.
- The steps a plugin contributes appear in the checklist with their own ids and resolved paths. Read
what each step names -- a plugin ships its own contract, and it governs its own domain.
- A plugin may raise this pipeline's quality floors and can never lower one; the merge clamps.
- A plugin that does not serve an item publishes no augmentation. That is not an error and not a
blocked run: it is the generic path, and the reconstruction proceeds by inference.
- This pipeline names no domain. If a rule is domain-specific, it lives in that domain's plugin.
The img2 harness
Plugins are installed and managed by the img2 harness
(img2threejs/img2), a separate dependency-free CLI (Node
launcher, Python core). This skill
never installs anything itself: when state.py init names a profile as unavailable, name the
img2 add command that installs it and stop — never vendor domain logic instead.
img2 add img2threejs/plugin-<id> --ref <tag> # install a plugin at a tag (e.g. plugin-cs2)
img2 doctor # what each host resolves: base-skill path + version, plugin gates
img2 capabilities --from-kind image --to-kind glb # which installed plugin serves an edge
Setup and the full CLI reference live in the README quick-start and the harness repo's docs.
Forge Runtime Contracts
Subdivision runtime tests compile generated TypeScript against the showcase checkout. Set
IMG2THREEJS_SHOWCASE_ROOT to that checkout; without it, local runtime-only tests skip with an
actionable message while static contracts still run. CI should set IMG2THREEJS_REQUIRE_SHOWCASE=1
to turn a missing showcase checkout into a test failure.
IMG2THREEJS_SHOWCASE_ROOT=/path/to/img2threejs-showcase python3 forge/tests/test_subdivision.py
IMG2THREEJS_SHOWCASE_ROOT=/path/to/img2threejs-showcase python3 -m unittest discover -s forge/tests
IMG2THREEJS_SHOWCASE_ROOT=/path/to/img2threejs-showcase python3 forge/tests/test_showcase_tsc_smoke.py
Implementation Rules (brief)
TypeScript + plain Three.js unless the project uses a wrapper. Group factory
createObjectNameModel(spec, options), reconstruction data kept separate from renderer objects,
deterministic seeds for all procedural noise. Prefer primitives / Shape extrude / curve+tube /
instancing / displacement / generated canvas textures before any external art. Full geometry &
material recipes + hard-won failure patterns: grimoire/build/geometry_patterns.md.
Optional Python ↔ Three.js render bridge
When Python is requested for character rendering, use it as a deterministic job/evidence layer
around the browser Three.js runtime: camera-batch manifests, source/output hashes, readiness and
settle checks, screenshot persistence, masks, diagnostics, and comparison packaging. The target
Three.js browser route remains the rendering authority. Do not silently replace the procedural
TypeScript factory with Blender/VRM/GLB output. Full routing, manifest fields, and failure rules:
grimoire/build/python_threejs_render_bridge.md.
Standard character pipeline (merged 1.5 beta + alpha)
Use grimoire/readiness/standard_character_pipeline.md for character work. Beta owns the
strict sculpt/build/review gates; alpha owns deterministic camera manifests, browser screenshot
evidence and UniRig-shaped rig validation. CharacterGen, Tripo, VRM and other neural/asset
systems are opt-in adapters with source, checkpoint, license, coordinate conversion and output
hashes. They never silently replace the procedural TypeScript factory. Image-to-mesh systems emit a
static mesh with no skeleton, so their output is never animation-ready however good it looks.
Executable entry points: forge/stage4_review/render_bridge.py and
scripts/capture_threejs_playwright.py (init → browser capture → validate → diagnose; capture
must operate on the real showcase/browser route and leave readable PNGs in the workspace).
Output
- Analysis-only: suitability verdict + scores, object extraction, macro→micro hierarchy,
geometry strategy, material/lighting recipe, animation/destruction feasibility, plan + risks.
- Implementation: the above briefly, then edit code; verify with typecheck/build + a screenshot.
- Not feasible: name the blocker, ask for more views / cleaner image / accepted stylization /
a narrower target. "This cannot reach the requested fidelity from this image" is a valid result.
1---2name: img2threejs3description: Turn an object or character reference image into a quality-gated, animation-ready procedural Three.js model built in code. Use for image-to-3D reconstruction, detail-accurate object rebuilds, stylized/likeness-maximized human characters, sculpt specs, and staged code generation.4license: Apache-2.05---6
7# img2threejs — Image to procedural Three.js
8
9Rebuild the object visible in a reference image as a **code-only** procedural Three.js model,
10gated by a staged sculpting pipeline and an AI-vision self-correction loop. This is
11reconstruction-by-code, **not** photogrammetry, mesh extraction, or downloaded art packs. That
12promise governs how the model is *built* — it says nothing about which file formats it can
13subsequently be *exported* to; an explicitly-selected emission target (`--target <kind>`) is a
14terminal, whole-artifact transform of the already-built model, verified to its own stated limit,
15never a second way to build one.
16
17Agent-agnostic: works under Claude Code, Codex, or OpenCode. Wherever this doc says "agent
18vision" or "agent browser tool", use whatever the host provides — native image reading, a
19browser MCP (playwright/chrome-devtools), the project preview, or a user-supplied screenshot.
20
21This file is the always-loaded router: it holds the order of operations and every hard rule as one
22line. The full contract behind each rule lives in the `grimoire/` or `docs/` file that rule names —
23read the named file at the moment you reach that stage, not before.
24
25## Canonical shared checkout
26
27Keep one checkout of this repository and let every host enter it through a symlink, so Claude and
28Codex execute the same code instead of drifting apart:
29
30```text
31~/.claude/skills/img2threejs -> <your checkout>
32~/.codex/skills/img2threejs -> <your checkout>
33```
34
35## When To Use
36
37The user attaches/points to an object image and wants a procedural Three.js model, a
38reconstruction/animation/destruction plan, a sculpt spec, or code. Also for material studies,
39action-ready props, game objects, botanical/mechanical parts, and stylized reconstructions.
40
41## Core Promise
42
43Sculpt from a photo, in order — never one-shot a mesh:
441. **Run `python3 forge/next.py --state .img2threejs/state.json [<spec>]` first**, at every start,
45 resume, and before every correction iteration. It reports the ordered checklist, exact next
46 command, evidence status, and bounded correction-loop status; it never replaces the spec/pass
47 gates. Obey a hard stop; never continue from memory.
482. **Validate** the image is a suitable 3D target (`grimoire/intake/validation_rubric.md`).
493. **Assess** object class + complexity, then write a `qualityContract` before any code.
504. **Spec** it: component hierarchy, materials, lighting, pivots, sockets, action anchors.
515. **Build pass-by-pass** from blockout → structure → form → material → lighting → interaction → optimization.
526. **Verify** each pass with a screenshot compared against the reference; fail a pass if an
53 identity-defining feature is wrong even when the global score looks fine.
54
55State explicitly when output is approximate/stylized/low-poly. A single image cannot reveal
56hidden sides or guarantee exact geometry — say so instead of faking confidence.
57
58## Mandatory Local State Gate
59
60Conversation context is disposable; `.img2threejs/state.json` is the local checklist authority.
61Initialize once per reconstruction, then gate every step through it:
62
63```bash
64python3 forge/state.py init --state .img2threejs/state.json --reference <img> --profile <generic|character|installed-domain> --spec object-sculpt-spec.json
65python3 forge/next.py --state .img2threejs/state.json [object-sculpt-spec.json]
66python3 forge/state.py mark <step-id> --state .img2threejs/state.json --evidence <path>
67```
68
69- `next.py` prints the current step, pass, incomplete mandatory steps, exact next command, and
70 `loop/max`. Exit code 3 or `status=stopped` is a hard stop: report the reason and request input.
71 Never bypass it by reconstructing progress from chat history.
72- Every completed step needs evidence; mark a non-applicable step `skipped` only with `--reason` —
73 silent omission is forbidden. Loop counts derive from `reviewHistory` actions
74 (`refine-spec`/`refine-code`), not agent memory. Defaults: 3 corrections per pass, 6 total.
75- A domain profile's steps, gates and reference material come from the **registry**: in-repo
76 modules (`character`) and installed plugins (`cs2`, `animated-character` from plugin-character) register identically, and
77 `forge/state.py init` names what is available. A profile adds mandatory gates without changing
78 the core order -- a domain plugin typically requires an authoritative classification, an intake
79 manifest, and a machine-readable domain review before AI review; `character` requires the
80 character contracts and landmark evidence; `animated-character` (requires the installed plugin-character) adds all of `character` plus the
81 nine Stage R steps (`grimoire/readiness/animation_contract.md`). Pick it whenever the rig must
82 MOVE — on `character` the Stage R gates are absent and the build completes without ever running
83 them, which is how animation used to ship broken. Its order is load-bearing: repair the mesh,
84 freeze it, bind additively, then verify parity. Every profile records suitability, projection
85 applicability, and
86 material-evidence applicability. The state file is a resumability index, not visual evidence:
87 renders, specs, review history, and deterministic gates remain the authoritative artifacts.
88
89## Required Inputs
90
91- one image path / screenshot / URL / attached image (if missing or unreadable, ask)
92- intended use: prop, game object, hero render, playable/destructible object, animation rig
93 (default: real-time browser prop with interactive performance)
94- when a domain plugin serves the item, whatever authoritative record its intake step requires, or
95 an explicit request for the user/vision provider to supply one; heuristic detection alone is not
96 enough to select a geometry adapter
97
98## The Loop (scripts do enforcement; agent vision does judgment)
99
100Run scripts from the skill root (`forge/...`). Pure Python 3.10+ stdlib, no pip installs.
101Full flags: `grimoire/scripts.md`. Never let a script *score* visuals — that is the agent's job.
102
1031. **Analyze the image first** (agent vision, before any script): work the layered observation
104 protocol in `grimoire/intake/image_analysis.md` — identify/classify, decompose macro→meso→micro,
105 map part relationships, name materials in PBR terms, list identity-defining features, and flag
106 what the single view hides. Observation before inference; controlled 3D vocabulary; 3D
107 object-space not 2D image-space. Then probe local images:
108 `forge/stage1_intake/probe_image.py <image>` (metadata only, not a visual check).
1091a. **Local Spec Search** — after image analysis, before writing or refining a spec, pull local
110 domain evidence (anatomy/PBR/wear/geometry/runtime/physics) rather than inventing it:
111 `python3 forge/stage2_spec/new_pre_spec_assessment.py "Name" --image <img> --out assessment.json`
112 (auto-runs BM25 over the `core_3d` collection, or the collection a declared domain contributes
113 -- the collection is NEVER guessed from the target name; writes a `localSpecSearch` bundle that
114 `new_sculpt_spec.py --assessment` carries into the spec). Full query-expansion recipe
115 (bilingual terms, focused `search_specs.py` retrieval, cache rules):
116 `grimoire/intake/local_spec_search.md`. MUST read it before retrying an incomplete or
117 domain-specific query.
1181b. **Domain intake** — when a domain plugin serves the item, complete its intake steps before
119 pre-spec authoring (admission, heuristic signal, classification, family/route resolution).
120 MUST read the contract its step names, completely, before creating the manifest or
121 running pre-spec assessment.
1221c. **Optional fidelity evidence adapters** — only when they improve an observed weak point; the
123 stdlib core remains authoritative. Thin/complex masks → local SAM2; character face/pose →
124 MediaPipe; weak front/back cues → Depth Anything V2
125 (`forge/stage1_intake/run_vision_adapter.py <segment|landmarks|depth> ...`; every adapter emits
126 provenance; monocular depth is relative only). **MCP-only scene mutations never count as
127 implementation** — write the proven change back to the spec or TypeScript, rebuild, recapture.
128 Full adapter + MCP routing and authority boundaries:
129 `docs/integrations/reference_fidelity_tooling.md`.
1302. **Pre-Spec Assessment Gate** — classify + score complexity + write the quality contract:
131 `forge/stage2_spec/new_pre_spec_assessment.py "Name" --image <img> --complexity <simple|moderate|complex|ultra-complex> --out assessment.json`. Rules: `grimoire/intake/quality_contract.md`.
132 Set `objectClass.primaryDomain` (`object` | `character` | `hybrid`) and fill the seeded
133 `detailInventory` (its `targetMinDetails` scales with complexity). A domain plugin may **raise**
134 these floors through its augmentation -- the merge clamps, so a plugin can never lower one
135 (a skin's finish/wear/hardware IS the item, so such a domain is held to the
136 top fidelity bar. Author procedural GEOMETRY but route the FINISH through the projection path in
137 step 2c — a procedural finish for a patterned skin (Doppler/Gamma/Marble/Fade) reads visibly
138 wrong against the reference. A domain plugin ships its own finish rulebook and texture-acquisition
139 guide; read what its checklist steps name.
1402b. **Detail inventory** (do not skip for detailed subjects) — scan zones and enumerate every
141 identity-defining small detail (gloss, bevel, fasteners, linework, contours, stains):
142 `forge/stage1_intake/build_detail_inventory.py <image> --mode grid-3x3 --out-dir <dir> --out di.json`.
143 Each detail MUST map to a `component.localFeatures` or `material.localOverrides` entry — never
144 prose only. Taxonomy + 3D-term recipes: `grimoire/intake/detail_inventory.md`.
1452c. **Projection-first fidelity** (characters AND reference-matched surfaces — painted skins,
146 decals, painted patterns) — when the goal is matching a specific reference's surface, put the
147 photo's own pixels on the mesh instead of approximating them procedurally. This is the single
148 biggest fidelity lever; a procedural material for a patterned surface is the #1 reconstruction
149 failure. Recipe (`grimoire/character/likeness_maximization.md` — its two levers generalize past
150 characters): solve the camera (`stage1_intake/solve_camera_pose.py` → `referenceCamera`),
151 **de-light** the reference (`stage1_intake/delight_albedo.py`, hard requirement — de-lighting is
152 what makes projection safe), then project the de-lit crop and bake it into UVs
153 (`stage3_build/bake_projected_texture.py --mesh-id <id>`). For a painted skin the projected de-lit
154 crop IS the finish — no procedural Doppler material. For characters, first capture landmarks
155 (`stage1_intake/extract_landmarks.py --out anatomy.json`), fill `preSpecAssessment.anatomy`,
156 route `grimoire/character/reconstruction.md`. A single view cannot show hidden sides — report
157 per-region confidence and request more views when it matters.
158 Character sub-routes, in order — decide what parts exist before shaping any, and shape the head
159 before the hair that sits on it:
160 - **Parts** — `grimoire/character/structure_decomposition.md`
161 - **Head** — `grimoire/character/head_construction.md` (what the likeness gate reads against)
162 - **Hair** — `grimoire/character/stylized_hair_threejs.md` + parameter contract in
163 `grimoire/character/threejs_hair_parameter_contract.json`. Lock topology only after the
164 silhouette review passes: material tuning cannot repair wrong lock topology.
1652d. **Reference-free humanoid** — a generic figure with no reference image has nothing to measure,
166 so fill anatomy from public canon:
167 `forge/stage2_spec/humanoid_proportions.py <spec> --style-heads 8 --in-place`. It writes
168 `anatomy.source: "canon-table"` so canon is never mistaken for measurement, refuses to run when
169 the spec names a reference image, and names anything the corpus does not supply rather than
170 interpolating it.
1713. Author the spec from the assessment:
172 `forge/stage2_spec/new_sculpt_spec.py "Name" --image <img> --assessment assessment.json --augmentation spec-augmentation.json --domain <profile> --out object-sculpt-spec.json` (the checklist step carries the resolved flags).
173 Replace generic starter `featureReviewTargets` with the object's real identity-defining
174 systems (≤5 critical, ≤3 important per pass); for characters add `anatomy-proportion`,
175 `face-landmark-placement`, `pose-silhouette`, `outfit-and-palette`. Use 3D-graphics terms only
176 (`grimoire/glossary/3d_vocabulary.md`), never "nice/smooth/shiny". Classify every component's
177 `topologyClass`/`topologyRationale` per `grimoire/intake/surface_topology.md` before picking a
178 `primitive` — this is what prevents a continuous organic form from being picked as a box.
1794. When material fidelity matters and a source image exists, analyze each material's **finish** then
180 extract reference PBR evidence, both per crop (verify the crop is on the part you think it is):
181 - `forge/stage1_intake/analyze_texture.py <crop> --spec spec.json --material-id <id> --in-place`
182 classifies the finish, extracts the gradient palette, and writes doc-grounded
183 MeshPhysicalMaterial scalars onto the material. Recipes + Three.js texture/PBR rules:
184 `grimoire/build/threejs_texture_reference.md`. Rule of thumb: **solid albedo for flat paint,
185 real reference crop for patterned finishes**.
186 - `forge/stage1_intake/extract_pbr_evidence.py <crop> --out-dir <dir> --material-id <id> --target-threshold 0.7`.
187 Confidence < 0.7 is a stop/refine-input signal, not a pass. It is inference, not inverse rendering.
188 - For multiple named regions: `forge/stage1_intake/material_region_analysis.py --manifest regions.json --out-dir material-evidence --out material-analysis.json`,
189 resolve each assignment from `docs/materials/material-reference.json`, wire it in with
190 `forge/stage2_spec/apply_material_analysis.py`.
191 - Emit the controlled material camera/crop contract (`forge/stage4_review/material_views.py`),
192 compare visible-footprint crops (`material_comparator.py`), apply only bounded material-scoped
193 corrections (`material_feedback.py`), and record the blocking result (`material_gate.py`).
1945. Validate, then strict-validate before generating code:
195 `forge/stage2_spec/validate_sculpt_spec.py object-sculpt-spec.json` then `--strict-quality`.
196 Strict blocks shallow specs (a complex object with one root, no repetition systems, no
197 local overrides, no micro groups is NOT implementation-ready even if JSON validates).
1986. **Locked build passes** — only touch the currently unlocked pass:
199 `forge/stage3_build/orchestrate_passes.py status object-sculpt-spec.json`
200 `forge/stage3_build/generate_threejs_factory.py object-sculpt-spec.json --out src/createObjectModel.ts`
201 The generator is fail-closed: `strict-quality` must pass before it writes any factory, and a
202 future `--pass-id` fails until prior passes are reviewed `continue`. If blocked, preserve the
203 `BLOCKED` artifact and refine the subject-specific spec; do not substitute a generic template.
204 The local state adds `--force` only for a new pass or `refine-spec`; `refine-code` edits the
205 current artifact without regenerating it. Before overwriting, carry valid hand refinement back
206 into the spec; generated code must not be the only copy of reconstruction decisions.
2076a. **Hitting a triangle budget.** `performanceBudget.targetTriangles` selects a tessellation tier
208 for every primitive with segment counts (low ≤6k, standard ≤60k, else hero) and caps
209 implicit-surface sampling grids. Where a tier is not precise enough, add
210 `geometryDescriptor.decimate: {"targetRatio": 0.4}` to that component — a quadric collapse in
211 the generated factory, run **before** skin binding so weights are computed on surviving
212 vertices. It keeps `position` only (normals recomputed), so it is refused on an
213 authored/unwrapped `uvStrategy`. Offline LOD tiers:
214 `forge/stage3_build/decimate.py <mesh.json> --ratio <r> --json`.
2157. Render the current pass in a browser/preview, capture a screenshot at a review viewpoint.
2167a. **Off-axis and placement gates — a single review viewpoint is not evidence about the model.**
217 Capture a turntable, not one frame, and run all three; each catches a defect class the older
218 gates pass by construction (a hole through a skull, a hat at hip height and a floating charm all
219 survived eight front-only review rounds):
220 `forge/stage4_review/turntable_gate.py --capture 0=front.png --capture 90=right.png --capture 180=rear.png --capture 270=left.png --json`
221 `node runtime/scripts/export_mesh_geometry.mjs --url <preview> --out meshes.json` then
222 `forge/stage4_review/self_intersection.py meshes.json --json`
223 `forge/stage4_review/attachment_anchor.py object-sculpt-spec.json --measured measured.json --json`
224 All three exit `0` clean / `1` gate failure / `2` error. A failure blocks `continue` even when
225 the global fidelity score passes. Read `sampledVertexCount` / `unmeasuredAttachments` /
226 `missingAzimuths` before believing a clean verdict: each names what the gate did not look at.
2278. **Run deterministic gates before AI vision.** MUST read
228 `grimoire/review/gates_reference.md` and `grimoire/review/self_correction.md` completely. Run
229 `forge/stage4_review/diagnose_render.py` and record the passing Tier 1 result with
230 `--spec object-sculpt-spec.json --pass-id <pass> --in-place`; for non-planar forms also run
231 `forge/stage4_review/diagnose_render_multi_angle.py` with the fixed view and at least two
232 meaningful orbit views. Then run
233 `forge/stage3_build/orchestrate_passes.py check object-sculpt-spec.json --pass-id <pass>`.
2349. Package one side-by-side sheet, then inspect it with agent vision:
235 `forge/stage4_review/make_comparison_sheet.py --reference <img> --render <shot> --out cmp.png --json`.
23610. Record the review (overall + per-layer + per-feature scores + decision):
237 `forge/stage4_review/append_review.py object-sculpt-spec.json --pass-id <pass> --fidelity <0-1> --action <continue|refine-spec|refine-code|request-input|stop> --summary "..." --render-screenshot <shot> --comparison-image cmp.png --ai-vision-score <0-1> --layer-scores-json '{...}' --feature-reviews-json <f.json> --in-place`.
238 When a domain plugin contributes a review gate, produce its versioned report first with
239 the command that plugin's review step names, then attach it with
240 `--domain-review-json <report>.json --review-scene-json <the plugin's scene fixture>`. The
241 checklist step carries the resolved paths.
242 A failed family, painted-region, projection-coverage, critical-detail, or orbit gate blocks
243 `continue` even when the global score passes. See the plugin's own review-gate documentation.
24411. Sync pipeline state after manual review edits, record checklist evidence, then re-run the local
245 state gate before another correction or pass:
246 `forge/stage3_build/orchestrate_passes.py sync object-sculpt-spec.json --in-place`
247 `python3 forge/next.py --state .img2threejs/state.json object-sculpt-spec.json`.
24812. Before declaring completion, run
249 `forge/stage4_review/check_part_coverage.py --spec object-sculpt-spec.json --manifest parts.json`
250 and verify the action-ready hierarchy. Mark `part-coverage` and `action-ready` only with evidence.
251
252## GLB-mediated v2 render-fidelity track (1.5 alpha)
253
254When the user supplies a GLB as an intermediate reference, the browser-rendered GLB is the
255structural and visual baseline for an independently authored procedural factory. The raw GLB is
256never pixel evidence and its topology/materials are never copied into the factory. Before any
257factory edit — full contract in `grimoire/build/python_threejs_render_bridge.md`, machine-readable
258schema in `docs/specs/render-profile.v2.schema.json` (+ example; fail-closed validation):
259
2601. `forge/stage1_intake/probe_glb.py` first. A merged one-node/one-mesh asset is `insufficient`
261 for semantic labels; request a multipart GLB or a browser semantic-ID pass before claiming
262 exact regions.
2632. Author ONE shared `render-profile.v2` (`forge/stage4_review/validate_render_profile.py`) used by
264 both the GLB and procedural routes. Region IDs are subject-specific, never inherited from the
265 example profile; declare the required set in `extensions.requiredSemanticRegions` so omission is
266 a hard validation error.
2673. Capture six passes per admitted view (`beauty`, `alpha-silhouette`, `semantic-id`, `depth`,
268 `normal`, `roughness-material-id`); score with `forge/stage4_review/compare_region_passes.py`.
269 Missing semantic-ID data blocks per-region confidence rather than falling back to whole-image
270 scores.
2714. Use region-specific continuous geometry — never replace a face/head volume, cloth shell, or tail
272 with floating primitives when the region's silhouette requires a continuous surface.
2735. Run ONE correction group per loop, in order: `camera → silhouette → face → clothing → accessory
274 → materials → lighting`; recapture the full pass set after each group and record the changed
275 group, hashes and score. Never combine groups when diagnosing improvement.
276
277## Gates (do not skip)
278
279Before any visual review or `continue` decision, MUST read the full gate-by-gate contract in
280`grimoire/review/gates_reference.md` (Divine Eye, VLM rescue, multi-angle, interior difference,
281chirality, hair, domain review, bounded correction, Divine Eye fitting, screenshot feedback, assembly,
282attachment, material, detail inventory, rig payload, character track). In short:
283
284- Validate references first (`grimoire/intake/validation_rubric.md`, `check_reference_admission.py`).
285- `divine_eye.py` is deterministic-first; the VLM (`vlm_gate.py`) is a gated last layer, never
286 consulted on a hard-gate failure.
287- A non-planar form must hold from ≥2 angles (`diagnose_render_multi_angle.py`).
288- Measure INSIDE the silhouette every visual pass (`interior_difference.py`). Silhouette IoU reads
289 ~11% of figure cells: a model with its face deleted scored the same 0.8803 as the finished face.
290- Every `-l`/`-r` pair is a MIRROR, not a rotation — hard at spec time (`validate_chirality`). A pair
291 wrong the same way on both sides still passes, and needs `medial_lateral_bias` vs a reference.
292- Hair subjects: `scalp_exposure.py` is HARD and runs on geometry before any render; `hair_gate.py`
293 is soft and subordinate to it. A coverage shortfall never authorises widening the masses.
294- Flat colour regions with hard boundaries (blaze/bib/socks, livery stripe, painted marking) are an
295 identity feature, so their boundaries are gated on geometry: `vertex_region_gate.py`. Never a
296 texture — this pipeline emits code; the shape predicates live in `_shared/vertex_paint.py`.
297- A curve claim ("curled into a hook, not a straight cone") needs `swept_arc_gate.py`: silhouette IoU
298 passes a straight cone occupying roughly the right cells.
299- Character builds validate the rig payload (`stage5_rig/validate_rig_payload.py`) before binding a
300 `THREE.Skeleton`; it proves payload integrity only, never pose stress or likeness.
301- A rig that must MOVE runs the animation gates too (`grimoire/readiness/animation_contract.md`;
302 the checklist steps and gate come from the installed plugin-character -- `stage5_rig/` remains in
303 this repo as the emitter's library, not the checklist authority). A clip that exists is not a
304 clip that plays: only G1
305 (`maxSampledBindingDelta <= 2^-23`) separates the two, and a gate whose input is missing reports
306 `unevaluated`, never a pass. Bind at IDENTITY in attached mode and take the display offset from
307 the mesh bounds alone; loop is decided by `poseReturn`, never by travel.
308- A domain plugin's review gate also runs against its versioned scene fixture.
309- Local state enforces 3 corrections per pass and 6 total by default; reaching either limit is a
310 hard stop. `correction_loop.py` may stop earlier on repeated defects, oscillation, or plateau.
311- `continue` requires a render + comparison sheet + AI-vision score ≥ threshold, every critical
312 feature ≥ its own threshold (`grimoire/feedback/render_capture.md`).
313- Every model ships explodable AND clickable — a structure gate, not pixels
314 (`check_part_coverage.py`, `grimoire/build/geometry_patterns.md`).
315- Action-ready, attachment, material/lighting, detail inventory, and character-track requirements:
316 `grimoire/readiness/action_rigging.md`, `grimoire/readiness/joint_attachment.md`,
317 `grimoire/feedback/shading_realism.md`, `grimoire/intake/quality_contract.md`,
318 `grimoire/intake/validation_rubric.md`.
319
320## Self-Correction
321
322After every pass, decide exactly one: `continue | refine-spec | refine-code | request-input | stop`.
323`refine-spec` fixes a wrong/missing/shallow spec (re-validate, don't patch code around it);
324`refine-code` fixes geometry/material/lighting that doesn't match a sound spec. Before making the
325decision, MUST read the root-cause guide + fidelity scale in `grimoire/review/self_correction.md`,
326record the decision, and re-run the local state gate.
327
328**Small features need a different instrument.** Divine Eye's SSIM/tonal/edge signals run on a 64×64
329luma grid, so a detail a few pixels wide is absent before any comparison happens. When fidelity
330depends on individual tears, spars, fangs or eyes, use the four-tier microscope:
331`grimoire/review/divine_eye_microscope.md`. Two empirically established rules from it: measure
332fidelity on a component's **visible footprint** (full frame minus a component-hidden frame), never
333on an isolation render; and never colour-gate a **concave** feature, where a dark ratio captures
334cavity shading rather than material.
335
336## Transparency and Process Debugging
337
338Report what changed each pass with evidence (exact values/coordinates), name what still doesn't
339match, and never claim "done" when only "improved". A passing gate is not proof of 3D realism.
340Full rule + examples: `grimoire/review/self_correction.md`.
341
342## Left and right
343
344A left/right pair is a **reflection**, never a rotation: negate the lateral axis and nothing else,
345`(x, y, z) → (-x, y, z)`. With `forward: +Z`, Y up and a right-handed frame, the character's own
346left is **+X**. The convention lives as code in `forge/_shared/chirality.py`
347(`CHARACTER_LEFT_SIGN`), with two different gates for the two defects that shipped from getting it
348wrong: `validate_chirality` catches a rotation-mistaken-for-reflection at spec time, and
349`medial_lateral_bias` vs a reference catches a pair that is wrong the same way on both sides.
350Reflecting also inverts triangle winding — flip it back on the mirrored side or `flatShading`
351lights the limb as though lit from behind. Full write-up with the measured defects:
352`grimoire/scripts.md` ("Left and right").
353
354## Hair
355
356Hair has its own subsystem because it has failure modes no other gate can see. Full contract,
357measurements and non-goals: `docs/HAIR_PIPELINE.md`. The hard rules:
358
359- Roots bind to the scalp as `(u, v)`, never absolute positions (hard validation error).
360- `standProud` is enforced by the generator, not advisory.
361- `scalp_exposure.py` is a HARD gate on geometry before any render; a coverage shortfall never on
362 its own authorises widening the masses.
363- Default representation tier is `shell`, not locks; strand impression comes from faceting and
364 material (`hair.human.code-only`), since this skill emits no textures.
365- `plane-card` is rejected for hair (needs an alpha texture this skill cannot emit).
366- Hair is rigidly parented, never smooth-skinned (the geodesic field runs through the skull).
367
368## Domain plugins
369
370A domain plugin makes a run exact where this pipeline would otherwise infer. It contributes its own
371checklist steps and gates, and publishes a `spec-augmentation.json` that this pipeline pulls at
372`spec-authoring`. With no plugin serving the item, nothing here changes: author the skeleton and
373infer the shape from the reference, as for any other object.
374
375- The steps a plugin contributes appear in the checklist with their own ids and resolved paths. Read
376 what each step names -- a plugin ships its own contract, and it governs its own domain.
377- A plugin may **raise** this pipeline's quality floors and can never lower one; the merge clamps.
378- A plugin that does not serve an item publishes no augmentation. That is not an error and not a
379 blocked run: it is the generic path, and the reconstruction proceeds by inference.
380- This pipeline names no domain. If a rule is domain-specific, it lives in that domain's plugin.
381
382### The img2 harness
383
384Plugins are installed and managed by the `img2` harness
385([img2threejs/img2](https://github.com/img2threejs/img2)), a separate dependency-free CLI (Node
386launcher, Python core). This skill
387never installs anything itself: when `state.py init` names a profile as unavailable, name the
388`img2 add` command that installs it and stop — never vendor domain logic instead.
389
390```bash
391img2 add img2threejs/plugin-<id> --ref <tag> # install a plugin at a tag (e.g. plugin-cs2)
392img2 doctor # what each host resolves: base-skill path + version, plugin gates
393img2 capabilities --from-kind image --to-kind glb # which installed plugin serves an edge
394```
395
396Setup and the full CLI reference live in the README quick-start and the harness repo's docs.
397
398## Forge Runtime Contracts
399
400Subdivision runtime tests compile generated TypeScript against the showcase checkout. Set
401`IMG2THREEJS_SHOWCASE_ROOT` to that checkout; without it, local runtime-only tests skip with an
402actionable message while static contracts still run. CI should set `IMG2THREEJS_REQUIRE_SHOWCASE=1`
403to turn a missing showcase checkout into a test failure.
404
405```bash
406IMG2THREEJS_SHOWCASE_ROOT=/path/to/img2threejs-showcase python3 forge/tests/test_subdivision.py
407IMG2THREEJS_SHOWCASE_ROOT=/path/to/img2threejs-showcase python3 -m unittest discover -s forge/tests
408IMG2THREEJS_SHOWCASE_ROOT=/path/to/img2threejs-showcase python3 forge/tests/test_showcase_tsc_smoke.py
409```
410
411## Implementation Rules (brief)
412
413TypeScript + plain Three.js unless the project uses a wrapper. `Group` factory
414`createObjectNameModel(spec, options)`, reconstruction data kept separate from renderer objects,
415deterministic seeds for all procedural noise. Prefer primitives / `Shape` extrude / curve+tube /
416instancing / displacement / generated canvas textures before any external art. Full geometry &
417material recipes + hard-won failure patterns: `grimoire/build/geometry_patterns.md`.
418
419### Optional Python ↔ Three.js render bridge
420
421When Python is requested for character rendering, use it as a deterministic job/evidence layer
422around the browser Three.js runtime: camera-batch manifests, source/output hashes, readiness and
423settle checks, screenshot persistence, masks, diagnostics, and comparison packaging. The target
424Three.js browser route remains the rendering authority. Do not silently replace the procedural
425TypeScript factory with Blender/VRM/GLB output. Full routing, manifest fields, and failure rules:
426`grimoire/build/python_threejs_render_bridge.md`.
427
428### Standard character pipeline (merged 1.5 beta + alpha)
429
430Use `grimoire/readiness/standard_character_pipeline.md` for character work. Beta owns the
431strict sculpt/build/review gates; alpha owns deterministic camera manifests, browser screenshot
432evidence and UniRig-shaped rig validation. CharacterGen, Tripo, VRM and other neural/asset
433systems are opt-in adapters with source, checkpoint, license, coordinate conversion and output
434hashes. They never silently replace the procedural TypeScript factory. Image-to-mesh systems emit a
435static mesh with no skeleton, so their output is never animation-ready however good it looks.
436Executable entry points: `forge/stage4_review/render_bridge.py` and
437`scripts/capture_threejs_playwright.py` (`init → browser capture → validate → diagnose`; capture
438must operate on the real showcase/browser route and leave readable PNGs in the workspace).
439
440## Output
441
442- **Analysis-only**: suitability verdict + scores, object extraction, macro→micro hierarchy,
443 geometry strategy, material/lighting recipe, animation/destruction feasibility, plan + risks.
444- **Implementation**: the above briefly, then edit code; verify with typecheck/build + a screenshot.
445- **Not feasible**: name the blocker, ask for more views / cleaner image / accepted stylization /
446 a narrower target. "This cannot reach the requested fidelity from this image" is a valid result.