Part of Building the 3D Club Scene
Two philosophies, one take, and the metric that ranked them on noise


Two sessions attacked the same 30-second pole routine with opposite instincts about what a recorded performance even is. Violation counts said one won, 187 to 260, though only one of the three bodies looked like a rout at 2.5:1. Re-scored on magnitude and duration, they sat on top of each other around a millimeter wherever the pose was achievable, and both had hit the same wall from opposite directions: the hardest body's arms are 50 mm too short, and no amount of solver quality invents reach.
Why there were two of them
We retarget one purchased pole-dance take onto characters the performer has never met. The retargeting pipeline works; what it does not do is degrade gracefully. The further a body departs from the performer's proportions, the harder absolute-position constraints fight the skeleton, and the harness said so in the plainest possible way — engaged-wrist violations running 17 → 125 → 275 across three characters in increasing order of mismatch.
That account stayed open long enough to get attacked twice, in parallel, by two sessions that did not share a philosophy.
Approach A — repair the recording. Keep the performer's trajectory as the source of truth and deform it onto the new body. Grip heights become offsets from the shoulder that reaches them, replayed as a fraction of the performer's reach against the avatar's own baked shoulder path, and a prioritized root solve absorbs whatever residual is left. Deterministic, debuggable frame by frame, minimally invasive.
Approach B — regenerate from meaning. Throw out the premise that the path is the thing worth preserving. Extract a semantic contact script — which grips and plants, in pole coordinates, in what order and for how long — plus style curves, and let each body solve its own motion to satisfy that script, with the original clip demoted to a style prior. Offline, baked per body, played back verbatim. (That representation is its own post.)
Same problem, same three bodies, same afternoon, opposite instincts about what a performance is.
The first comparison, and why it was worthless
The first side-by-side used the metric both sessions already had: a count of frames where an engaged wrist sits further than 5 mm from where it should be.
| engaged-wrist violations | medea | ch02 | ch33 |
|---|---|---|---|
| A | 0 | 42 | 145 |
| B | 8 | 103 | 149 |
On that number A wins convincingly, and for a few hours that was the finding.
A third session, reading both sets of numbers cold, made one objection: a count says nothing about magnitude, and nothing about whether the time it does measure is one long miss or a hundred scattered short ones. 145 violations of 2 mm each are not the same defect as 42 that put a hand 60 mm off the pole for half a second. One of those is invisible and the other ends a shot.
So both sessions were asked to re-score, on identical parameters (tolerance 5 mm, engagement weight ≥ 0.98), against six statistics instead of one: max, mean-while-engaged, p95, longest continuous run over tolerance, total time over tolerance, and a duration-weighted integral.
The re-score inverted the reading
B built the scorer. A then ran B's scorer verbatim rather than reimplementing it, which is the detail we would keep if we could only keep one: the tool does not care who wrote the code it is grading, and a shared scorer is the cheapest possible way to stop two implementations arguing past each other.
The fair comparison is a near-tie on the distributions, and not a clean comparison at the top end.
- B, hardest body, raw: left max 162.4 mm, p95 61.1 mm, 0.97 s over tolerance out of 11.5 s engaged. Excluding the windows the solver tagged as out of reach: left max 0.1 mm, right max 5.8 mm, with mean and p95 both 0.0 mm on both hands and one blip lasting 0.02 s.
- A, same body: max 32.1 / 30.6 mm, but p95 1.4 / 0.0 mm and only 0.37 / 0.05 s over tolerance across the whole 30 seconds. A handful of brief transients while a pin is in motion, not sustained slip.
- Performer-matched body: a wash. A 1.8 / 3.7 mm, B 0.1 / 4.1 mm.
Where the pose is achievable, both land around a millimeter on the statistics a viewer would feel: mean and p95 at zero for B, p95 1.4 and 0.0 mm for A, well under a second over tolerance either way. The maxima are not sub-millimeter on either side. And the two columns are not the same measurement. B is quoted both raw and with its unreachable windows removed; A was never re-run through that mask, so those maxima are computed over different sets of frames. The p95 and time-over-tolerance rows are the like-for-like ones, and on those the counts had been ranking the two systems on noise.
A threshold count is one measurement wearing the name of the whole distribution. At a fixed 60 Hz it is exactly total time over threshold and nothing else: in these same dumps 101 frames reads out as 1.68 s, 22 as 0.37 s, 1 as 0.02 s. What it hides is magnitude and event structure, whether those frames are one sustained slip or a hundred scattered singles. Two systems can look a factor of two apart on counts and be indistinguishable on the thing a viewer would actually notice. If you have ever shipped a dashboard whose headline number is "errors," this is the same bug.
A's own dump documented two caveats against itself, which is the part worth copying. B's scorer has no settle exclusion, and A's scans open with a ~330 mm first-frame wind-in transient at page init — so the dump carries both a raw run and a settled run (from 1 s) through the identical scorer. And the duration-weighted integral is blend-dominated by construction, so max, p95 and time-over-tolerance are the solver-quality rows; the integral is not.
Both hit the same wall, from opposite directions
This is the strongest result of the day, and neither approach was looking for it.
B formalized the wall as a test (|shoulder → pin| > 0.96 × armLength,
evaluated at the solved root) and tagged every frame that fails it. A had been
meeting the same thing from the other side, as runtime corrections firing in
exactly those places.
The performer's arms are 0.516 m. The hardest character's are 0.466 m. At 1.1–1.5 s and again around 4.3 s she holds both grips at once, 25 cm apart on the pole, while a leg sweeps past it. The diagnostic dump shows the converged compromise: both shoulders about 30–40 mm short of simultaneously satisfying both reach constraints. The solver found no root position that closes that gap, and nothing built against it afterwards closed it either. Not a tuning problem.
It is a residual, though, not a proof, and the test that produced it turned out to be partly circular. Measuring reach at the solved root lets a root the solver moved for some unrelated reason mint its own amnesty. Two days after this comparison an audit re-derived the same test at an anchored root and found 27 percent of the hardest body's tagged frame-instances came back reachable, so the tag now ships in the anchored form. Every excluded-window number above is the older, solved-root version, and I have not re-run the comparison against the corrected one.
Past that line the question stops being "how accurate is the retarget" and becomes "what should this body do instead" — which is a choreography decision wearing an engineering costume. Something must yield: grip height, grip azimuth, reach effort, or the leg line.
One more fact from B's yield work, stated carefully because it is easy to state backwards: on this take no freedom bought much. Vertical relief eliminated no infeasible window at all. Reach fraction and grip azimuth, built and measured after it, took the hardest body's engaged-wrist frames inside the infeasible windows from 125 to 114, with reach pinned at its 19 mm ceiling and azimuth finding room in one window of three. On the three worst double-grip windows the measured shortfall went 224 to 214 mm, 175 to 175, and 176 to 154. Torso lean measured inert, then destabilizing. That is a property of this specific choreography's geometry, not a rule about how shorter dancers should grip. The tempting generalization, inventing biomechanical advice out of an IK residual, is precisely the mistake to avoid.
What actually decided it
Grip accuracy did not. They tied.
Two other things did. First, past the infeasibility line the required output is choreography substitution — a different, reachable grip — and only a contact script can express that. "The recorded path is truth" fails hardest exactly where the hardest body lives. Second, the product replays baked per-body routines, which makes an offline per-body solve with QA gates the natural shape rather than a compromise.
The tiebreaker was a cost measurement. Both approaches, and all three outside models we consulted, converged on the same remaining work: consolidate several sequential correction mechanisms into one constrained solve. This project's notes had carried that as "the remaining work" for months. It was finally attempted and experimentally bounded — a staged multi-session rewrite, with counter-evidence logged. Once the shared next step has a price tag that large, the question stops being which approach is better and becomes where to spend the money once.
So the essence architecture became the base, and the geometric-fit implementation was frozen — not deleted. It is still runnable, still committed, explicitly resumable, and it keeps producing scans in the shared format. Which means a frame-level disagreement between two implementations of the same choreography remains available as a cheap anomaly detector. Disagreement, note, not agreement: the two share the take, the three rigs, the collision geometry, the check harness and the scorer, and B ported A's plant lifecycle and hand-wrap code outright, so anywhere they agree they may simply be wrong together.
Three things we would tell anyone running an A/B between architectures
- Fix the scorer before the comparison, and let one side write it. Not two scorers agreeing; one scorer, run by both. The re-score inverted our reading, and we would never have found that if each side had graded itself. But one scorer only removes implementation divergence, not the author's judgment calls, and ours carried two: no settle exclusion, which cost the side whose scans open with a wind-in transient, and an exclusion mask only the author's own runs were ever scored against. Agree the definitions separately from the code.
- Score the distribution, not one point on it. A threshold count is total time over threshold and nothing else. The moment you rank two systems with it, you have thrown away magnitude and event shape, which is where the difference between "invisible" and "ends a shot" lives.
- Look for the wall both sides hit. The convergence was more informative than the winner. When two opposite architectures fail in the same 400 ms of a 30-second take, that window is not an implementation problem, and no further engineering will make it one.
And the honest epilogue: the whole day had been arguing on the wrong axis anyway. While two sessions drove wrist error toward zero, the defect a human saw in five clips was the body passing through the pole — a category our collision check had been quietly passing all day.