Three ways to improve a character's expressions

Make two VRoid-based anime characters' facial expressions (9 expressions and 5 lip-sync mouths) read better in our own three.js painted renderer, studying two commercial reference rigs without copying them, and decide whether brows draw over the bangs. Same starting commit for every track; at most 6 rounds and 90 minutes each.

2026-10-30

01tracks

trackverdictmodelreviewerharnesstookrounds
Track A1st, tiedclaude-opus-5-5noneClaude Code40 min4
Track B1st, tiedclaude-opus-5-5gpt-6.1-terraClaude Code64 min6
Track C3rdgpt-6.1-terranoneCodex42 min5

judged by the owner, all 3 tracks

02the brief

Benchmark brief: improve our characters' expressions

You are one of three tracks given this same task from the same starting point. Work alone; do not look at the other tracks' folders (~/Projects/lowmarch-bench/{a,b,c} and work/{a,b,c} other than your own).

The project

Lowmarch is a code-built anime character pipeline for a tactics game. Characters are VRoid models reshaped in code, exported as GLBs and drawn in the browser by our own "painted" renderer (a three.js page). Everything is drawn with an orthographic camera. The owner reviews characters in that page, the "armory".

The task

Make our two test bodies' facial expressions read better, in the armory's painted renderer:

  • the slender woman (arm_f_slender);
  • the youth (arm_m_youth).

The expressions are baked from VRoid's 57 expression shapes at build time.

The reference is two commercial anime rigs, Yin and Yang. They're for study only, and nothing of theirs may be copied into the project.

  • In brief/materials/:
    • expressions-vs-yin-yang.png: their expressions beside ours at the starting point, 9 expressions and 5 lip-sync mouths at 0 and 30 to 35 degrees;
    • their individual frontal renders (yin-expressions/, yang-expressions/);
    • frames from their product page;
    • yin-yang-rig-review.md: notes on how their rig and shading work;
    • vroid-shapes.txt: the VRoid shape list.
  • Study what makes theirs read, then improve ours with our own code and recipes.

The 14 expressions are in $WORK/expr_list.txt: neutral, joy, smile, angry, sad, surprised, shout, wink, smug, and the lip shapes A, I, U, E, O.

  • You may change the recipes, the build code and the renderer.
  • Keep the names.
  • Neutral is every character's default face in the armory, so it must still look right.

Decide one open question and justify it: should eyebrows be drawn on top of the hair (bangs) when the hair covers them? Some anime styles do this and many don't, and the reference rigs keep their bangs above the brows instead. Decide for our characters, implement your decision, and explain it.

Your setup

  • Before every shell command, run source ~/Projects/lowmarch-bench/kit/env-<track>.sh (or chain it with &&). It sets:
    • LOWMARCH_REPO: your git worktree. Edit code only here; it's on branch bench-<track>.
    • WORK: your builds, shots, sheets and notes.
    • KIT: the shared tools. Don't edit them.
  • Where the code is, in your worktree:
    • review/vroid-02/index.html: the painted renderer. The shaders are PAINT_VS, the material fragment shader and PAINT_COMP (the composite pass with lines). Roles: face=1, body=2, hair=3, line=4, iris=5, white=6, highlight=7, brow=8, mouth=9.
    • tools/vroid_portrait.py: the Portrait class builds a face. It applies the expression shapes, then reshape, face_normals, shadow_cards and more.
    • tools/vrm.py: the VRM loader. Shapes are applied in VRM.primitives.
    • tools/export_glb.py: the GLB export and the page's material data (scene_info).
    • docs/portrait-guide.md: our accumulated lessons.
  • A round:
    1. Change code or recipes.
    2. If you edited tools/vroid_portrait.py (or cage.py, fit.py, proportions.py, makehuman.py, anthro.py, heads.py, collide.py), run cd $LOWMARCH_REPO && python3 -I tools/cage.py arm_f_slender arm_m_youth (about 2 minutes). Those files key the body cache, and the bodies come out identical.
    3. Build: $KIT/build_expr.sh $WORK/expr_list.txt [names...] (about 25 seconds per expression). Page-only changes need no rebuild.
    4. Shoot: $KIT/shoot_expr.sh $LOWMARCH_REPO/review/vroid-02/index.html r<N> (about 4 minutes for all 14). Shots land in $WORK/shot_r<N>/<expr>/painted_<body>.png: a frontal tile, then a 30 degree tile.
    5. Sheets: $KIT/sheets.sh r<N> "Round N: what changed" writes $WORK/sheet_r<N>_woman.png and sheet_r<N>_youth.png (before vs this round, frontal).
    6. Look at the sheets, and at the 30 degree tiles too: an expression must hold off-front.
  • Rules for running things:
    • Run long commands in the foreground with generous timeouts, not as background jobs.
    • Work only in your worktree and $WORK.
    • Don't touch ~/Projects/lowmarch, the other tracks, the shared kit or anything published.
    • Don't install anything. Headless only: no GUI windows.
    • You may commit to your branch.

Budget

At most 6 rounds and 90 minutes of wall-clock time, whichever comes first. Note your start time in $WORK/NOTES.md.

Deliverables

  • $WORK/FINAL_woman.png and $WORK/FINAL_youth.png: copies of your best round's sheets.
  • $WORK/NOTES.md:
    • each round: what you changed, why, what the sheets showed, and how long it took;
    • your eyebrow decision and why;
    • what still reads weakly.
  • Your code committed on your branch.
  • A final message under 200 words: what you changed, and the rounds and time you used.

03redaction

Redaction notes

Bundle expressions-2026-10, built 2026-10-30 from private raw copies by transcripts/tools/convert.py and checked by transcripts/tools/leakscan.py. Every removal reads [redacted: <reason>] in place.

Sources

  • track-a, track-b: Claude Code subagent JSONL from the lead session.
  • track-b-reviewer: the six Codex rollouts made by kit/terra_review.sh, one per round, joined in time order. Each was confirmed to be a critique call by its first user message. Each critique is also inlined in track-b's transcript.json as a reviewer turn, right after the tool result where B read it.
  • track-c: one Codex rollout.
  • Other Codex sessions from that day (including a site design critique at 13:46) are not part of the bench and were left out.

Removed by rule

  • Injected harness context. The owner's private instructions and memory files, the environment, skill, agent and tool listings, and Claude Code system reminders: each whole block becomes [redacted: harness context]. Codex base instructions, developer messages, environment context and world state get the same treatment. Task prompts and the lead's messages are kept.
  • Claude Code request logs (api-request, api-request-blob, api-request-shape records) are dropped from original.jsonl. They repeat the system prompt and injected context and carry no conversation.
  • Home paths become ~.
  • Thinking signature fields and Codex's encrypted reasoning payloads are stripped. The Claude thinking blocks were empty (no visible summaries), and the Codex reasoning summaries were empty, so transcript.json has no reasoning blocks.
  • Codex account ids in session_meta become [redacted: account id].
  • Employer and client names, the owner's work login, the Windows username, private email addresses, tokens and tailnet IPs: rules are in place; none were left once the harness context was cut.
  • Reference rigs. Yin and Yang (DillonGoo, personal-use licence) are cut wherever an image of them was read: 2 in track-a, 2 in track-b, 1 in track-c. Those were expressions-vs-yin-yang.png (which shows theirs beside ours) and one frontal render from brief/materials/yin-expressions/ per Claude track. No track read the product-page frames. Each becomes [redacted: reference rig image (Yin/Yang by DillonGoo, personal-use licence)]. Text that names the rigs, describes their techniques or links their store pages is kept.
  • Inline base64 images are written to each run's images/ folder. PNG text chunks and EXIF are dropped losslessly; Claude Code's JPEGs keep their encoding and lose only their APP1 to APP15 and COM segments. In original.jsonl, the image data becomes a relative images/... reference. Images are deduplicated by content within a run.
  • final_images are the tracks' own FINAL_woman.png and FINAL_youth.png, stripped the same way.

Removed by judgment

  • Claude Code serverClassifierContext. The permission classifier's git context, which names an unrelated private repository.
  • Claude Code [Image: original WxH ...] meta lines are left out of transcript.json (kept in original.jsonl).
  • Private Claude session links in commit trailers become [redacted: session link].
  • Codex rate_limits (subscription plan, credits and usage) become [redacted: account plan and usage].
  • One code line in track-c, a regex for a PNG data-URI prefix in a kit script it read, has the prefix replaced by [redacted: data-URI prefix] so the site's scan stays clean. It held no image data.

Kept on purpose

  • yo@severian.dev as the commit author (allowed by the owner) and noreply@anthropic.com in co-author trailers.
  • The severian user and group in ls -l output, and severian.dev: the public persona.
  • Codex's built-in tool definitions in original.jsonl (generic, no personal content).
  • A ps listing in track-c that shows the Codex sandbox wrapper. It lists proxy variable names, never their values.

Checks

  1. python3 -I tools/leakscan.py: zero hits over every file in public/, rendered JSON and JSONL alike. It covers SCHEMA's targets (home paths, Windows and macOS user paths, signatures, encrypted reasoning, data URIs, base64 runs of 200 or more characters, token and key shapes, emails other than the two allowed, tailnet IPs), the bundle's name list, the patterns of the site's leakscan.sh, leftover reminder or harness tags, unrelated private project names, and image metadata. A file with planted leaks was flagged on every rule tried. The site's own leakscan.sh finds only yo@severian.dev, which is allowed.
  2. Every image src, tool_result.images entry and manifest final_images path resolves, and there are no orphan images. Every kept image was viewed on contact sheets: all are our own renders, sheets, crops, VRoid texture dumps or debug crops, and none shows the reference rigs.
  3. manifest.json and each transcript.json were checked against SCHEMA v1's field names and types. Every tool_result matches an earlier tool_call.
  4. Read-through of each normalised transcript: the brief, the lead's prompts, the agents' messages and the reviewer critiques are bench-only. Paths outside the bench are limited to the body cache (~/.local/share/lowmarch), the Codex install and its shell snapshots, and Claude Code's task output folder under /tmp.