RFD 1122 details: the checkpoint survey, the schema, and the inventory

Why the head cannot be bought

checkpoint why it fails
Sapiens CC-BY-NC, the exact class filter_coco_licenses.py drops
DWPose Apache-2.0 weights, trained on UBody, distributed only behind a registration form
RTMW the same UBody dependency, and not independent of DWPose, which distils from an RTMPose teacher
OpenPose non-commercial academic licence, and CMU is blocklisted for provenance
AlphaPose commercial use needs a separate licence
MediaPipe Apache-2.0 and verified, but 33 plus 468 plus 42 landmarks, which is not a COCO-compatible wholebody head

Five of the six fail on terms. The sixth answers a different question.

A permissive licence on the weights is not sufficient. A permissively licensed checkpoint trained on a form-gated corpus re-exports terms nobody has read. DeepFashion is already blocklisted for that pattern.

DWPose and RTMW are also not two opinions. RTMW supplies the teacher DWPose distils from, so picking both gives one lineage wearing two names.

The corpus schema

Essential Tuple Normal Form. Interned vocabularies, satellite relations rather than nullable columns, no NULLs, no derivable columns.

Authored relations: topologies, identities, bones, pose_rotations, rest_mesh, constraints. Emitted relations: keypoints_2d, segmentation, depth_map, meshes, each reached by foreign key from renders.

Three attributes carry a decision rather than data.

rotation is 3x2, not a quaternion and not 3x3. A quaternion double-covers, so one rotation has two tuples and the table would hold two rows for one fact. A 3x3 carries a third column derivable as the cross product of the first two, which is a derivable column.

visibility is int8, not bool. Three separate reasons, and each alone is sufficient.

  1. COCO has three states. Masked training must tell not annotated from annotated as occluded. A boolean cannot, so you skip the first and learn the second.
  2. Multi-view labels are computed rather than annotated. Z-test each projected joint against the rendered depth. The answer is 2 for visible, 1 for projects inside the silhouette but fails the test, and 0 for outside the frame.
  3. Real datasets guess at that middle state or omit it. A render knows it, and a boolean cannot carry the state that makes occlusion learnable.

topology_id is a foreign key. A vertex_id means nothing without it. The two ANNY topologies share zero vertices, measured in both directions.

Root translation is absent on purpose. It follows from constraints and the chain, so storing it would be a derivable column.

erDiagram
  topologies    ||--o{ rest_mesh      : topology_id
  identities    ||--o{ renders        : identity_id
  bones         ||--o{ pose_rotations : bone_id
  pose_rotations||--o{ renders        : pose_id
  pose_rotations||--o{ constraints    : pose_id
  renders       ||--o{ keypoints_2d   : render_id
  renders       ||--o{ segmentation   : render_id
  renders       ||--o{ depth_map      : render_id
  renders       ||--o{ meshes         : render_id
  bones         ||--o{ keypoints_2d   : bone_id

  topologies {
    int16 topology_id PK
    string name "anny is 13,718 vertices"
  }
  identities {
    int16 identity_id PK
    float32 phenotype "11"
    float32 local_change "256"
    float32 facial_action "52"
  }
  bones {
    int16 bone_id PK
    int16 parent_bone_id "104 rows, -1 is a value not a NULL"
  }
  pose_rotations {
    int64 pose_id PK
    int16 bone_id FK
    float32 rotation "3x2, never a quaternion"
  }
  rest_mesh {
    int32 vertex_id PK
    int16 topology_id FK
    float32 xyz
    int16 group_id FK
  }
  constraints {
    int64 pose_id FK
    int16 bone_id FK
    string kind "connect or limit, MJCF"
  }
  renders {
    int64 render_id PK
    int16 identity_id FK
    int64 pose_id FK
    int16 camera_id FK
    int8 appearance "1 of 4"
  }
  keypoints_2d {
    int64 render_id FK
    int16 bone_id FK
    float32 xy
    int8 visibility "not bool, three states"
  }
  segmentation {
    int64 render_id FK
    bytes mask "from hm08 groups"
  }
  depth_map {
    int64 render_id FK
    float32 z
  }
  meshes {
    int64 render_id FK
    bytes geometry "for Pixal3D"
  }

Authored relations are on the left of renders and emitted relations on the right. Everything right of renders is exact by projection rather than annotated, which is the property the whole corpus is built to have. Root translation appears nowhere on purpose: it follows from constraints and the chain, so a column for it would be derivable.

Correspondence is 14 of 17, and it fails informatively

Shoulders, elbows, wrists, hips, knees, ankles and eyes map to ANNY bones. The nose and both ears have no bone and need fixed mesh vertices. Those are topology-dependent, which is the reason topology_id became a foreign key rather than a label.

Note the keypoint asset itself carries 23 points, not 17. anny/data/keypoints/coco.pth is COCO-17 plus the six foot points. Each entry is a weight vector over the mesh that sums to 1, so a keypoint position is a weighted sum of posed vertices. The label is computed, never detected.

The vectors are 19,158 wide, so the render must pin the topology that returns that count. The default returns 13,718 and would fail to multiply.

Layer extraction in 3D

Every piece is MIT and already packaged. They share one backbone, so the latent passes between them without conversion.

step does why it is that one
Pixal3D image in, SLAT out it emits SLAT natively, so nothing is inverted. An ANNY-first order would pay a lossy a_invert on every use
fit ANNY supplies the part semantics Pixal3D gives geometry and not labels. A fitted ANNY carries hm08 groups, so masks come from the fit rather than from a segmentation model
mask by set operation tokens, not meshes ANNY and Pixal3D is body. Pixal3D without ANNY is garment or hair, because ANNY models anatomy and nothing else. No decode and no correspondence
VoxHammer fills what was never seen the 3D inpainter. It replaces LaMa rather than calling it
a_splice preserves everything outside the mask inversion is lossy, so without it every extracted layer perturbs the ones you did not touch

VoxHammer replaces LaMa because it is consistent across views. A 2D inpaint answers for one view. Fill behind the hair for a front view, then ask for a three-quarter view, and the two disagree. A masked fill in the latent is one piece of geometry, so every view of it agrees. See-Through’s 2D path keeps LaMa, because there is no geometry to fill.

The honest claim is smaller than it first sounds. Where the image saw a surface and a nearer layer covered it, 3D wins, because that content is rendered rather than invented. Where nothing ever saw it, the back of a head being the obvious case, it is still invented. It is invented earlier, by the reconstruction rather than by the inpainter.

Occlusion becomes rendering. Hallucination becomes verifiable. The second half is the real gain. An inpainted patch cannot be checked against anything. A reconstructed surface can be checked against a second view.

Helmet hair removes the resolution problem. Individual strands and one-pixel edges are the cases a sparse latent cannot resolve, and they are out of scope by construction. What remains is a front mass against a back mass, separated by the width of a head. That resolves, so there is no densifying to 256 or 1024 and no retraining at a resolution the checkpoints never saw. It is also what VRM and VR avatars already are.

A hairline pixel holds two layers. Split it fractionally so the alphas sum to one. That leaves no seam and makes no choice. The soft rasteriser already computes this, because the sigmoid of distance over temperature is fractional boundary coverage. Anti-aliased mattes come free from the renderer built for the fit.

Bleeding is a separate and smaller thing. Dilate each layer’s colour under its alpha, so downstream resampling finds colour rather than background. One fills a layer’s interior and the other protects its edge. Both are called bleeding, which is how they get conflated.

What this does not cover. Multi-view refinement has to run before a 360 degree decomposition is trustworthy. Until then the rear layers are confident fiction, and the greedy version of that refinement is order-dependent by construction.

What Pixal3D’s toolkit demands

Read from data_toolkit/README.md rather than assumed.

toolkit step who does it state
download 3D assets skipped, because ANNY is the asset not applicable
process mesh, extract PBR our renderer build
render multi-view and cameras our renderer build
voxelize to O-Voxels upstream theirs
encode shape, PBR and sparse latents upstream theirs

The last row is why decode-only still holds for us. Supplying geometry is not owning an encoder.

MakeHuman already has the material. node_trees/enhanced_skin.json is a Principled skin shader, a thin wrapper over a shader group. It drives Roughness with 3 references, Normal with 4 and Clearcoat with 2, and it sets Metallic nowhere, because skin is dielectric. sss.png ships alongside it, with per-region maps for face, ears, lips, eyelids, fingernails and toenails.

The toolkit wants a flat texture set and this is not one. So the job is a bake. Run the node group once over the hm08 UV layout and write albedo, roughness and normal. Metallic is a constant zero and needs no map. Bake once and reuse it in every render.

Do the bake in Blender. MPFB2 is a Blender addon, so the bake happens in the tool the material was authored for rather than in a reimplementation of it.

The bake needs UVs, and they exist. texture_coordinates is (21334, 2) under topology="anny". An earlier version of this file called the bake blocked, which the section below retracts.

Retracted: the topology loads

An earlier version of this file marked the UV claim UNVERIFIED and reported that the topology the corpus depends on does not load. Both were wrong. The retraction stays here beside what replaces it.

TopologyConfig(base_mesh=...) and the topology= spec string take different vocabularies. AlternativeTopology is smplx, smpl, soma, anny_from_soma, notoes and three collapse variants. "anny" is not among them, so base_mesh="anny" falls through to data/topology/anny.obj. That file was never meant to exist, and the resulting FileNotFoundError names a missing asset rather than a bad argument. The failure mode invites the wrong reading, and it got one.

The claim re-derives exactly through the spec string.

call vertices texture_coordinates
topology="anny" 13,718 (21334, 2)
topology="soma" 18,056 None

The PBR bake is not blocked.

The helper geometry was recovered too. Anny.faces is the body submodel: 27,420 triangles reaching vertex index 14741, and no higher under any of the eight combinations of eyes, tongue and nudity_edits. Read as the whole mesh, it says helper-hair, helper-tights and helper-skirt have no faces.

They do. basemesh_face_to_vertex_table.json.gz holds 18,486 quads reaching 19,157, referencing all 19,158 vertices with none unreferenced. Every group has faces. A proxy surface with no appearance is a different thing from absent geometry, and RFD 1121 means the first.

OpenUSD is where the topology now lives

RFD 1053 makes OpenUSD the internal format. export_hm08_usd.py in 2-contract/hm08-partition is the body’s entry point.

/Hm08/Basemesh   19,158 points, 18,486 quads, all 12 groups, partition
/Hm08/Body       13,718 points, 27,420 triangles, UVs (21334, 2)

The map between the two spaces is measured rather than assumed. ref is the ascending unique index set of Anny.faces, the maximum positional difference between basemesh[ref] and the body mesh is 0.0 exactly, so body_index = searchsorted(ref, basemesh_index).

UsdGeomSubset carries a partition family type and USD validates it, so three_groups_cover is enforced by the format rather than argued in a comment. groups_by_range is written as nonOverlapping on purpose, because the Lean file proves it is not a partition.

Four negative controls ship with it. Two found real defects in the validator while being written. A reused temporary filename handed each control the previous one’s stage, because USD caches stages by identifier, so three controls reported the second one’s defect while all four still printed FAIL. And GetFamilyType read the wrong prim, so relaxing the basemesh family type to unrestricted was invisible and the layer validated while claiming no partition.

Colour sketch: Qwen-Image-Edit

The fourth appearance is a colour sketch, produced by Qwen-Image-Edit. It is Apache-2.0, independently checked, and it is already in the catalog as qwen_q4_k_m_image_edit, so the style needs no training run and no new domain corpus.

Two costs come with that choice. Both are accepted rather than absent, and both are stated here so nobody rediscovers them.

The photoreal branch is the same model. Two of the four appearances now come from one model, so their errors correlate. Qwen’s idea of a hand appears in two domains rather than one, and the four-way spread narrows to something closer to three. The purpose of four appearances is independent error modes, so this is a real reduction in what step 2 buys.

Report the two Qwen-derived domains together when scoring, not as two independent columns. An average over four domains where two share a model overstates the spread.

There is no depth control on this path. The packaged interface takes image, instruction, strength, steps and seed. Geometry preservation therefore rests on strength alone. RFD 1121 requires a depth control wherever generated geometry must match an authored pose, and this path has none, so the guarantee is weaker than the stylised branch had before.

That matters most at the setting a real sketch needs. A low strength preserves the pose and barely changes the picture. A high strength gives a convincing sketch and is free to move a limb. The usable window is an empirical question and it may be empty. Measure it before scaling, and treat an empty window as the signal to reconsider rather than as a tuning problem.

The default is 0.8, which is high for this purpose. Do not inherit it.

The server must return its checkpoint hash

interactor-qwen-image-edit returns {image, seed, stub}. It does not return the checkpoint hash or echo the instruction.

That is sufficient for interactive editing and insufficient here. Condition 1 requires the generating model, checkpoint and conditioning recorded with the data, and the CycleGAN image already returns checkpoint_sha256 per result for exactly that reason, because a hash in a server log is not with the data.

So this is a change to qwen-image-edit’s server.py, not a note. Return the checkpoint hash and the instruction with every result before the corpus uses this path.

Note also RFD 1043’s open question. No measurement compares Q4_K_M against bf16 for this model, and Q4_K_M is the only format the image ships. A quantisation artefact in a corpus is harder to find later than a quantisation artefact in one edit.

Why not a GAN, recorded because it was nearly the answer

The GAN route was worked through and is kept here, because it explains what the alternative would have cost and because one of its findings survives the change of direction.

Full CUT is disqualified by its own headline feature. The authors report:

Our full method CUT has the flexibility to enlarge the horses, as a means of better matching of the training statistics than CycleGAN. FastCUT behaves more conservatively like CycleGAN.

Changing object size to match target statistics is the advertised advantage, and it is the one behaviour a labelled corpus cannot tolerate. A stylizer that enlarges a body moves every joint, and the keypoint label then describes a picture that no longer exists. That finding stands whatever tool is chosen, so CUT stays excluded for any labelled corpus, not only this one.

FastCUT was the strong candidate. BSD-2, read from the LICENSE file rather than the NOASSERTION badge, which reports that only because two licence texts share one file. Same ResNet generator as CycleGAN, so the server and hash discipline carry over. About half the training memory and about twice the speed. And single-image training, where each domain is one image, so the colour-sketch domain needed one licence-clean sketch rather than a corpus.

It costs a training run, which Qwen-Image-Edit does not. That is the trade that was made.

StyTR2 remains excluded for carrying no licence. AdaAttN is Apache-2.0 and exemplar-driven, CAST is Apache-2.0, EFDM is MIT. Any of them remains available if the common-mode cost above turns out to matter, and picking one then is cheaper than regretting it later.

What exists, and what does not

piece note state
licence-clean mocap 810 clips, CC-BY-4.0 and Apache or MIT, with CITATION.cff exists
extract_poses.py world-space joint positions, convention-free, avoiding the Euler trap exists
AnnyInverter and LBFGS solves pose, phenotype and local changes jointly exists
renderer image, keypoints, masks, camera parameters. Nothing turns a posed mesh into a labelled frame build
appearance generators Qwen-Image-Edit, CycleGAN monet and ukiyoe, algorithmic corruption, all licence-cleared exists
colour sketch Qwen-Image-Edit, Apache-2.0, already in the catalog exists
checkpoint hash on the Qwen path server.py returns no hash and no instruction. Condition 1 needs both build
drift and hand verification silhouette.py, depth_term.py, soma_referee.py, controls passing exists
schema KEYPOINTS_2D, SEGMENTATION, RENDERS defined. visibility int8 and topology_id pending exists
COCO-format bridge gen_coco_dataset.py, gen_reference_keypoints.py, gen_reference_loss.py exists
training loop upstream RF-DETR is Apache-2.0. The masked-loss run does not exist build
GGUF conversion convert_keypoints_to_gguf.py exists
rf-detr-cpp inference working port, head is COCO-17 exists
flowchart LR
  P["ANNY pose<br/>authored or licence-clean mocap"] --> R
  R["RENDER<br/>104 joints, 52 blendshapes<br/>masks, cameras, mesh and PBR"] --> A
  A["4 appearances<br/>Qwen-Image-Edit, CycleGAN<br/>algorithmic corruption"] --> V
  V["verify<br/>drift, hands, reachability<br/>only verified frames pass"] --> T
  T["MASKED TRAIN<br/>104 out, COCO masks 90<br/>plus licence-filtered COCO"] --> G
  G["GGUF<br/>convert_keypoints_to_gguf"] --> C
  C["rf-detr-cpp<br/>inference port"] --> E
  E["evaluate<br/>blinded val2017, real photographs only"]
  V -. "second student, same frames" .-> X["FINE-TUNE PIXAL3D<br/>image and mesh"]
  S["SLAT stays decode-only<br/>upstream encodes internally"]
  classDef build stroke-dasharray:6 3,stroke-width:2px;
  classDef aside opacity:0.55,stroke-dasharray:3 3;
  class R,T,X build
  class S aside

Dashed boxes are the build rows of the table above: RENDER is the renderer and MASKED TRAIN is the masked-loss run. The table carries a third build row the diagram cannot show as a box, because it is not a stage: the Qwen path’s server.py returns no checkpoint hash, so condition 1 is unsatisfied on an edge rather than at a node. SLAT stays decode-only sits off the path deliberately, as a reminder that supplying geometry is not owning an encoder.

What the chain is for

The head recovers a body from a picture. Each stage is blind to something another stage covers.

step recovers blind to
RF-DETR wholebody 104 keypoints in 2D depth, shape, anything unlabelled
AnnyInverter and LBFGS the pose that puts those joints there whether its correspondence is right
silhouette fit the 11 phenotypes no keypoint constrains depth, and the interior
depth fit and Marigold interior surface, limb ordering absolute scale
MediaPipe 51 of 52 blendshape coefficients the tongue, verified absent from the model
hm08 groups on the fit body layers in 3D, depth-ordered, nothing inpainted hair and garments, which ANNY does not model

The estimator panel never had that property, because its members were all COCO-trained and their errors correlated. Every fit here is descent through a forward we own. Where the forward is differentiable there is no inverse model to license, train or trust.

Qualified, not retracted, and the qualification stays here beside the sentence. An earlier version of this note said RFD 1126 retracted half of it, because that RFD replaced the fit with a learned student and a student is an inverse model. RFD 1126 now unrolls the descent instead, so the sentence largely stands and the earlier retraction is withdrawn.

What changes is smaller. An unrolled fit is still descent through a forward we own, with the iteration count frozen rather than decided at run time, so nothing is licensed and nothing replaces the parametric model. Two things do become measurements. The step sizes are learned, so they are trained. And a fixed step count stops short of convergence by construction, so “trust” becomes the residual that K steps actually reach, reported as a percentage of stature.

The reason the wording matters is in the row above about correlated members. A regressor would have put a discriminative estimator back where the parametric model was, and lost the property that a parametric model cannot represent an impossible skeleton. Unrolling with a per-step joint-limit clamp keeps that property in a graph an accelerator will take.

The last row is what RFD 1121 answers.

The corrected hm08 claim

The plan leaned on one sentence: the mesh already labels its own parts, so the layer masks have no gaps. 2-contract/hm08-partition states it in Lean 4 with Mathlib, 847 build jobs.

The first version of that file reported 992 unassigned vertices, 124 cubes and a 19,150-vertex mesh. All three were wrong, and the file was wrong in the class of error it was written to catch. It counted the ranges in one JSON field and took that count for the mesh.

statement result
segs_disjoint holds. No vertex is in two groups
not_a_partition the twelve-range claim fails. Vertex 13606 is in the mesh and in no group
jointCubes_card the block is 1,000 vertices, which is 125 cubes of 8
covered_or_jointCubes the corrected claim, and it holds
three_groups_cover body and HelperGeometry and JointCubes are the whole mesh
three_groups_disjoint and no vertex is in two of them

JointCubes is [[13606, 14597], [19150, 19157]]. The missing eight are joint-ground. HelperGeometry excludes the cubes exactly, so select_groupsHELPERS entry never named them.

The file uses interval sets rather than finite sets on purpose. A finite set over 19,158 vertices would tempt the checker into listing every one, and a single counterexample settles it.

check_hm08_claims.py now re-derives every constant from the installed ANNY package, with six negative controls that each must fail. RFD 1121 records the full measurement.

The order, by critical path

The ten tasks and their edges are the USD stage in the logbook, not this list – the list is read off the graph so the two cannot disagree. What the graph does not carry is durations, so the path below is computed on unit durations, one step per task.

State that assumption plainly, because it decides the answer. On unit durations the project is six layers deep and nine of the ten tasks are critical: three chains tie for longest and only one task has any slack. That is not a finding about this plan, it is what equal durations always produce, and it is the reason the list below is more useful read as layers than as a ranking.

The critical chain, in the order it has to happen:

  1. Check the pose library is worth rendering. It starts at zero, everything else waits on it, and it is the one task flagged as able to redirect the plan. Cheapest thing here, and first.
  2. Render the labels rather than annotate them. The renderer is the RFD’s subject and the only step that supplies image, keypoints, masks, camera and depth from a posed body.
  3. Verify before training, not after. Three branches converge here, so it cannot start until the slowest of them lands.
  4. One head on heterogeneous annotation.
  5. GGUF, and the head from 17 to 104.
  6. Score on real photographs, shared points apart from render-only.

Two branches are co-critical and converge at step 3, so neither has slack either. The PBR bake over the hm08 UVs hangs off the pose gate; recording the Qwen checkpoint hash starts at zero alongside the gate, and the strength window follows it.

One task has slack, and it is the schema completion – visibility as int8 with topology_id as a foreign key. It is reachable one layer after the pose gate and not needed until masked training, so it can float by a full layer without moving the finish.

What this ordering does not capture, and it is the thing most likely to bite. The PBR bake is co-critical by structure and has no method at all: it was specified as a Blender bake, and Blender is blocklisted for reproducibility, so its duration is not one step but unknown. A critical path computed on unit durations reports the longest chain; the actual risk is the branch whose estimate does not exist. Costing a replacement renderer that a lockfile can pin is therefore the first thing that would change this table, ahead of any resequencing.

SUPERSEDED 2026-08-24 by the reranked table below, which prices the tasks instead of counting them. Kept because knowing which reading was in force is worth more than a tidy document.

RESOLVED, AND IT DID CHANGE THE TABLE — TWICE. The paragraph above stands as written because it was correct and because knowing which risk was named is worth more than a tidy document. Two of its premises have since moved.

The bake has a method: Mitsuba 3.9.1, pinned in anny-render-corpus’s pixi.toml, with the determinism measured rather than asserted — llvm_ad_rgb at one thread is byte-identical over two runs by sha256, while the default thread count drifts by up to 1/255 on a dozen pixels through film accumulation order. So the branch with no estimate has one, and it is one step like the others.

And the pose gate has run. That is what actually resequences this, because the table above is computed as though nothing is done. Recomputed with T01 complete, nine-of-ten-critical collapses to one chain of six, and it is not the chain the list above names:

T04  record the checkpoint on the editor path   slack 0
T05  find the conditioning window               slack 0
T07  verify before training                     slack 0
T08  one head on heterogeneous annotation       slack 0
T09  GGUF, and the head from 17 to 104          slack 0
T10  score on real photographs                  slack 0

T02  render the labels                          slack 1
T03  bake albedo, roughness, normal             slack 1
T06  finish the corpus schema                   slack 2

The renderer is the RFD’s subject and it is no longer on the critical path. The reason is structural rather than surprising: T04 → T05 → T07 is two layers where T01 → T02 → T07 was two layers, so the two chains tied only while the gate was outstanding. Finishing the gate broke the tie in favour of the editor branch, and the subject of the document acquired a step of slack without anybody deciding it should.

That is the argument for keeping durations out of the graph and the state of play out of the prose. Both readings are correct; they answer different questions, and only one of them moves when a task completes.

Reranked on durations, Hailo-first, 2026-08-24

The reading above counts tasks. This one prices them, and the two disagree about what the plan is mostly made of.

What forced it. Every duration the plan inherited came from the 4090, and the 4090 is unplugged. The load-bearing figure is the soft renderer entry’s Mitsuba throughput — 1.79 ms/image, 0.4 GPU-hours over 800k — and it is doubly inapplicable: mi_bench.py:19 and mi_bench2.py:20 both open with mi.set_variant('cuda_ad_rgb'), which is neither the variant that ships nor one this fleet can run outside that card. Retracted as a corpus-render estimate; it remains correct about what it measured.

scripts/mi_bench_llvm.py replaces it for the pair that ships, llvm_ad_rgb at one thread, which no benchmark here had ever timed. On the M2 Pro, at ANNY’s face count, 1024², one sample:

BENCH film -- aov integrator, box filter, 1 sample. A DEPTH PASS.
llvm_ad_rgb, 1 thread             73.00 ms/img   800k = overnight
metal_ad_rgb, default threads      9.01 ms/img   800k = an afternoon
cuda_ad_rgb (4090, UNPLUGGED)      1.79 ms/img   800k = half an hour

SHIPPING film -- path integrator max_depth 6, gaussian filter, 128 samples.
THIS IS WHAT RENDERS THE CORPUS, and a second matte pass follows it.
llvm_ad_rgb, 1 thread          32,592.00 ms/img   800k = a month of wall-clock
llvm_ad_rgb, default threads    4,363.90 ms/img   differs run to run
metal_ad_rgb, default threads     545.00 ms/img   differs run to run

ms/image is the record and keeps its decimals; the 800k column is a projection and gets a span. Two rows landing on the same span are not being claimed equal — they are indistinguishable at the resolution a plan can act on, and the ms/image column is where the difference lives.

Determinism is per-image, so eight single-threaded processes reach 2.2 hours without touching the guarantee the one-thread rule buys. That is the whole corpus in an afternoon, on the weakest device in the fleet, and it settles the question the ordering turned on: the renderer is not the expensive thing.

A result that is deliberately not used. Multithreaded llvm_ad_rgb and metal_ad_rgb both came back byte-identical across two processes, and pixi.toml records the multithreaded case drifting by up to 1/255 on a dozen pixels. The negative control went looking for that drift at 1, 4, 16 and 64 samples per pixel and never reproduced it, so nothing has shown the check can fail — which makes its identical column decoration rather than evidence. The one-thread constraint therefore stands unchanged, and it costs nothing: the process-scaling row reaches the same throughput with every frame single-threaded.

Hailo-first reaches backwards into the training run. RFD 1126 already decided the backbone compiles at num_windows=1 — 825 ONNX nodes parse, 868 are rejected — and that it “costs 1.35x wall-clock and needs retraining”. T09 converts and ports, and it sits after T08 trains. A plan followed in its own numbered order therefore trains at the wrong windowing, finds out at T09, and pays for the run twice. This is recorded as a standing constraint, EdgeCompileGatesTraining, rather than a new edge: an edge from T09 back into T08 is a cycle, and 107e’s decision is already made, so what is owed is that T08 honours it. One quantization schedule comes out of DFC 5.3.0 and the rest of the fleet runs that same schedule, because a detector quantised three ways is three detectors and no measurement transfers between desks.

Not to be confused with condition 5, which forbids a quantised generator from writing corpus data. The detector’s quantization is deployment; only T05’s generator path is bound by it.

The reranked path: 20.0 size points. Computed by check_rfd1122_plan.py from the sizes in the stage and asserted against this paragraph so the two cannot drift. The formulas are the standard PERT pair, already in use in this workspace at 2-contract/manuals-multiplayer-fabric/rfd/204d-pert-critical-path-zonefabric — cited by path rather than by number, because that document lives in another repository and the citation gate here correctly refuses a number it cannot resolve:

TE = (O + 4M + P) / 6        sigma^2 = ((P - O) / 6)^2

Sizes rather than days, and the swap is an admission. An earlier revision of this section carried optimistic, likely and pessimistic figures in engineering days and printed a total of “38.8 engineering days”. Not one of those numbers was measured — they were judgement, and the decimal point made them read as something else. A t-shirt size cannot be mistaken for a calendar. The spread is kept because it carried a real signal: T08’s pessimistic sits four times its optimistic, which is a different fact from T08 merely being large. Points are Fibonacci and relative; they order tasks against each other and convert to no duration at all. Anything wanting a calendar has to go and measure one.

task O M P TE slack
T07 verify before training S M L 3.2 0
T02 render the labels M L XL 5.2 0
T08 masked training L XL XL 7.5 0
T09 GGUF, and 17 to 104 S M L 3.2 0
T10 score on real photographs XS XS XS 1.0 0
T03 bake albedo and normal XS S M 2.0 3.2
T04 record the checkpoint XS XS S 1.2 1.7
T05 the conditioning window XS S L 2.3 1.7
T06 finish the corpus schema done

T01 and T06 are complete and carry no size. They are counted and named in the checker’s output rather than folded in as a zero — it reports 2 complete beside the path.

T06 closed on 2026-08-24 (anny-render-corpus#6), and it was larger than the two attributes it named. KEYPOINTS_2D keyed on bone_id, which asserts every keypoint follows a bone; the nose and both ears have none, so three of the 104 points were not merely unvalidated but inexpressible. Anchoring moved into satellite relations, topologies interns the vocabulary, and topology_id is a foreign key because ANNY’s two topologies share zero vertices — 19,158 basemesh against 13,718 body — so a vertex_id carrying the wrong one is wrong at an index where nothing notices.

Completing it does not move the path: T06 held 7.3 slack and the total stays 20.0. And it does not mean the corpus can be written — the renderer emits npz, json and png rather than parquet, which is T02’s output format. The schema is finished and there is still nothing to put in it.

The chain is now five tasks, and it is not the one either earlier reading named. Correcting the render measurement put T02 back on the critical path and took T04 → T05 off it, which is the reversal the first rerank got wrong in the other direction: it demoted the renderer on the strength of a depth pass. T08 is still the single heaviest at 7.5 points, 38% of the path, and no resequencing touches it. T06 gains slack rather than losing it — 7.3, over seven times its own size — and it is the one outstanding task that runs on any desk in the fleet.

The bottleneck is a device, not a task, and the graph cannot see it. T05, T07 and T08 are all gpuBound and all want the one desk with native bf16. Their TE sums to 13.0 of the 20.0 points, 65% of the path, on the PC — a serial floor imposed by contention that no dependency edge expresses.

Devices are machines now, not card models. The plan named an RTX3090 and an RTX4090 until 2026-08-24. Which silicon sits in the PC changes without the plan changing, so pinning to a model made the schedule depend on an inventory fact. The desks are the Mac and the PC, and the PC’s peak is recorded as unmeasured rather than derived from a model nobody committed to.

What survives that is the constraint that actually binds: the PC is the only desk with native bf16. Condition 5 requires a generator to run at its published precision, and the Mac’s bf16 is emulated — 2.96 TFLOP/s, 0.48x its own fp16 and below fp32. Whether emulated bf16 is numerically the same bf16 is unmeasured, and “probably” does not satisfy a condition about provenance. So corpus generation is pinned to the PC by rule, not by speed.

RETRACTED: “plugging in the 4090 is worth 30%”. That figure scaled the gpuBound tasks by the ratio of two card models’ derived peaks, and both halves were derived rather than measured. It read as a schedule decision and was really an inventory one, so it goes with the card rows it came from.

Nothing replaces it, deliberately. The contention is real and its relief is unquantified until somebody measures the desk that would relieve it. What is left is the shape: three tasks, one desk, 65% of the remaining work.

The cheapest thing that could change the plan

All 810 clips are locomotion. Walking, running, turning and standing still. Character drawings are rarely mid-stride, so walk cycles teach a keypoint detector little about sitting or leaning.

The check needs no renderer, no GPU and no install. Project the 104 joints with the camera arithmetic, draw the stick figure, and look at twenty. Ask whether a character artist would draw that pose.

If the answer is no, author poses by hand. The pose-source rule already permits that, and a hand-authored pose is constructed rather than generated.

Run this first. It is the one result that could redirect the work before any of it is built.