Track A: Opus 5.5 alone
agent (cont.)
+0:41Bashsource ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && grep -n -i "brow\|mouth\|role" tools/export_glb.py | head -60; echo ----; grep -n "def \|brow\|mouth" tools/vroid_portrait.py | head -120
command
source ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && grep -n -i "brow\|mouth\|role" tools/export_glb.py | head -60; echo ----; grep -n "def \|brow\|mouth" tools/vroid_portrait.py | head -120
result, 156 lines
2:browser 3D viewer. 5:the viewer can toggle and x-ray them. Face, eye, line, brow, mouth and hair keep VRoid's textures 18:TEXTURED = ("face", "iris", "white", "highlight", "line", "brow", "mouth", "hair") 19:ROLE_TONE = {"face": "5", "body": "4", "shadow": "3", "hair": "D", "brow": "B", "coat": "M", "cape": "W", "metal": "8", 22: "grass": "c", "dirt": "f", "stone": "l", "mouth": "2", "line": "o", "iris": "s", "white": "w", 26:def group_of(p, role): 31: if role in ("face", "body", "shadow", "iris", "white", "highlight", "line", "brow", "mouth"): 33: if role == "hair": 111: for k, (p, role) in enumerate(zip(portrait.prims, portrait.roles)): 112: if role == "skip" or not len(p["tri"]) or (only_world and not p.get("world")): 120: mat = dict(name=f"{group_of(p, role)}:{role}", doubleSided=True, 122: tone = ROLE_TONE.get(role) 125: if role == "face": 127: if role in TEXTURED and tex is not None: 130: src = portrait._hair_v if role == "hair" and getattr(portrait, "_hair_v", None) is not None \ 133: mat["pbrMetallicRoughness"]["baseColorTexture"] = dict(index=add_texture((id(src), p["mi"], role), raw)) 134: if role == "hair": # strands are grey in the texture: tint them 136: if role in ("hair", "line", "brow", "iris", "highlight"): 141: if role in ("metal", "steel", "gold"): 144: meshes.append(dict(name=group_of(p, role), primitives=[dict( 146: nodes.append(dict(name=f"{group_of(p, role)}/{p['material']}", mesh=len(meshes) - 1, 147: extras=dict(group=group_of(p, role), role=role, world=bool(p.get("world")), material=p["material"]))) 170:# simulated cloth: piece -> (its static trims, hidden while the cloth simulates; the role whose colour 184: """Everything the browser's pixel renderer and cloth solver need besides the meshes: palette 185: bands per role, the lights, body colliders, cloth grids and the mouth curve (model space, 191: roles = {"face": dict(mode="bands", bands=bands(vp.SKIN_BANDS)), 196: "line": dict(mode="flat", color=hexp("o")), "brow": dict(mode="flat", color=hexp("A")), 197: "mouth": dict(mode="flat", color=hexp("1")), "white": dict(mode="flat", color=hexp("w")), 201: roles[r] = dict(mode="bands", bands=bands(tbl), world=r in vp.WORLD_ROLES) 204: body = [p for p, r in zip(portrait.prims, portrait.roles) if r == "body"][0] 227: if getattr(portrait, "mouth_curve", None) is not None: 228: face = [p for p, r in zip(portrait.prims, portrait.roles) if r == "face"] 232: _, i = cKDTree(allp).query(portrait.mouth_curve) 233: curve = ((portrait.mouth_curve + alln[i] * 0.0015) * FLIP).tolist() 237: img = Image.fromarray(np.where(m == 1, 128, 0).astype(np.uint8), "L") # nose mark only; the mouth is a line 242: # painted renderer: a material kind and one base colour per role; the shader builds the ramp 259: for role, val in pc.items(): # extra roles a cast entry names (cloth unless it says) 260: if role not in paint and isinstance(val, str) and val.startswith("#"): 261: paint[role] = dict(kind="cloth", base=val) 262: elif role not in paint and isinstance(val, dict) and "base" in val: 263: paint[role] = dict(kind=val.get("kind", "cloth"), base=val["base"]) 264: for role, g in (pc.get("grain") or {}).items(): # optional per-role brush grain (big flat cloth mottles) 265: if role in paint: 266: paint[role]["grain"] = g 267: return dict(roles=roles, light=vp.LIGHT.tolist(), sun=vp.SUN.tolist(), capsules=caps, cloth=cloth, face_marks=marks, 269: mouth_curve=curve, mouth_bone="J_Bip_C_Head", ink=dict(o=hexp("o"), skin=hexp("1"), hair=hexp("A")), 388: for k, (p, role) in enumerate(zip(portrait.prims, portrait.roles)): 389: if role == "skip" or not len(p["tri"]) or p.get("world") or "J" not in p: 403: mat = dict(name=f"{group_of(p, role)}:{role}", doubleSided=True, 405: tone = ROLE_TONE.get(role) 408: hex_rgb(extra[role]) if isinstance(extra.get(role), str) else [0.8, 0.8, 0.8] 409: if role in TEXTURED: 410: src = portrait._hair_v if role == "hair" and getattr(portrait, "_hair_v", None) is not None \ 416: if role == "hair": 418: if role in ("hair", "line", "brow", "iris", "highlight"): 424: if role in ("metal", "steel", "gold"): 437: meshes.append(dict(name=group_of(p, role), primitives=[dict(attributes=attrs, 440: gnodes.append(dict(name=f"{group_of(p, role)}/{p['material']}", mesh=len(meshes) - 1, skin=0, 441: extras=dict(group=group_of(p, role), role=role, material=p["material"]))) ---- 4:palette tones (skin/hair/cloth bands from normals; eyes, brows, lashes from alpha-tested masks) 39: ("FaceEyeline", "line"), ("FaceEyelash", "line"), ("FaceMouthLine", "line"), ("FaceBrow", "brow"), ("FaceMouth", "mouth"), 46:# VRoid face textures share one layout: marks only count inside the nose-and-mouth window 78:def role_of(material): 85:def bands(v, table): 95:def repose(v, prims, ref, target): 103: def delta(nm): 119:def reduce_blocks(char, depth, pid, role, h, w): 143:def camera_rotation(yaw, pitch): 152:def smooth(t): 161:# 3. resting expression: VRoid's own shapes (hooded eyes, stern brows) held at a low weight 164: face_len=0.0, jaw=0.0, chin_w=0.0, chin_len=0.0, cheek=0.0, brow_ridge=0.0, 166: eye=1.0, eye_tilt=0.0, iris=1.0, mouth=1.0, arm_out=0.0, age_lines=0.0, blush=1.0, curl=0.0, 170:SCULPT_ROLES = ("face", "body", "mouth", "brow", "line", "white", "iris", "highlight", "hair") 173:def bone_pose(v, sh, base_pose): 197: def own_of(i): 211: def as4(r): 274:def landmarks(prims, roles): 275: def verts(*rs): 285: mouth = verts("mouth").mean(0) 286: band = mid[(mid[:, 1] > mouth[1] + 0.005) & (mid[:, 1] < eyeL[1] - 0.01)] 291: def window(pts, cond_y, cond_x=None): 300: def edge(y, side, pts=face): 305: def front(x, y, pts=skin): 309: lm = dict(eyeL=eyeL, eyeR=eyeR, eye_y=eye_y, chin=chin, mouth=mouth, nose=nose, span=span) 329:def sculpt(prims, roles, lm, sh, sigma=0.022): 333: def move(name, d): 341: move("ridge" + sd, np.array([0, -0.0015, -0.004]) * sh["brow_ridge"]) 346: for name in ("crown", "occiput", "forehead", "neck_front", "neck_back", "mouth"): 363:def face_texture(prims, roles, v): 373:def strip_blush(prims, roles, v, keep): 393:def face_normals(prims, roles, lm, amount, hair=0.0, forward=0.0, pull=0.5, neck=None): 397: instead of patches that jump as the head turns. The face, its brows, lines and mouth and the skull take it in 417: if r in ("face", "brow", "line", "mouth"): 457:def neck_frame(prims, roles, neck): 469:def axis_at(A, u, y): 474:def _shadow_card(name, src, P): 493:def shadow_cards(prims, roles, lm, amount, neck=None): 563:def face_lines(prims, roles, v, amount): 565: (from the nose's wing round the mouth, under the eyes, crow's feet, a frown between the brows, 566: down from the mouth's corners), darker and more of them as `amount` (0..1) rises. Each stroke is 583: def verts(*rs): 586: mouth = verts("mouth") 591: mo = lm["mouth"] 592: mhw = 0.55 * np.ptp(mouth[:, 0]) # half the mouth's width (the inner mouth spans it) 594: def eye_box(c): 600: def drop(x, y): 605: def to_uv(P): 617: def curve(ctrl, n=24): 640: # from the nose's wing round the mouth 651: # down from the mouth's corner 655: # a frown between the brows 695:def reshape(prims, roles, lm, sh): 696: """Face length, sculpting, eye size/tilt and mouth width (skeleton and expression are applied 713: if sh["mouth"] != 1.0 and r == "mouth": 714: P[:, 0] = lm["mouth"][0] + (P[:, 0] - lm["mouth"][0]) * sh["mouth"] 731: def __init__(self, path, morphs=None, pose=BUST_POSE, shape=None, hair_from=None, costume=None, 732: mouth="curve", world=None, ref_pose=BUST_POSE, hair_trim=None): 733: """mouth: how the closed mouth is drawn. 'texture' = VRoid's painted lip line, thickened; 734: 'texture+lip' = that plus a lower-lip shade; 'drawn' = an FE8-style mouth between the 735: projected mouth corners; 'none' = geometry only (the line disappears when closed).""" 736: self.mouth = mouth 825: if self.roles[k] in ("line", "brow", "iris", "highlight", "white", "hair", "mouth"): 837: self.mouth_pts = None 838: self.mouth_curve = None 852: # 2 = mouth (dark line), 1 = nose mark (one tone softer) 854: if self.mouth_pts is None and raw.any(): 867: self.mouth_pts = pts 877: self.mouth_curve = np.array(curve) if len(curve) >= 4 else None 879: def frame(self, view="bust", pitch=None, w=None, h=None): 883: def _frame(self, view, pitch, w, h): 900: def render(self, yaw=35, pitch=None, view="bust", smooth=None, w=None, h=None): 908: def projector(Rm): 909: def project(P): 922: if k in self.tex and self.roles[k] in ("line", "brow", "iris", "highlight", "white", "hair", "mouth")} 963: if mark is not None and self.mouth in ("texture", "texture+lip", "curve", "curve+lip"): 970: if self.mouth == "texture+lip": 974: if self.mouth in ("texture", "texture+lip"): 993: elif r == "brow": 995: elif r == "mouth": 1026: if self.mouth == "drawn" and self.mouth_pts and all(q is not None for q in self.mouth_pts): 1027: pts = project(np.array(self.mouth_pts)) / [SS, SS, 1] 1028: draw_mouth(ink, rol, pts) 1029: if self.mouth in ("curve", "curve+lip") and self.mouth_curve is not None: 1030: draw_curve_mouth(ink, rol, dep, project(self.mouth_curve) / [SS, SS, 1], lip=self.mouth == "curve+lip") 1033: def ink(self, c, dep, prim, rol): 1043: def nb(a, fill): 1077:def uv_to_3d(prims, uv): 1104:def draw_mouth(c, rol, pts): 1105: """FE8-style closed mouth: a dark line between the corners with lighter ends, and a lower-lip 1123:def draw_curve_mouth(c, rol, dep, pts, lip=False, keep=(0.12, 0.88), dark=(0.28, 0.72)): 1124: """Closed mouth as a 1-px line along the projected lip curve: dark in the middle, one tone 1148:def smooth_view(portrait, yaw=35, pitch=None, palette=None, view="bust", w=None, h=None): 1154: "line": hexrgb("o"), "brow": hexrgb("B"), "mouth": hexrgb("2"), "white": hexrgb("w"), 1163:def image(char, scale=1, bg=None, palette=None): 1177:def main():Bashsource ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && grep -n "mouth_curve\|face_marks\|mouthCurve\|curve" review/vroid-02/index.html | head -30; sed -n 780,900p review/vroid-02/index.html
command
source ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && grep -n "mouth_curve\|face_marks\|mouthCurve\|curve" review/vroid-02/index.html | head -30; sed -n 780,900p review/vroid-02/index.html
result, 122 lines
389: // the surface curves (normal change per metre on screen), and broad plates get a soft tone that const k = idx(i, j), r = this.r0[k] + this.push[k], h0 = this.h0[k], tt = this.t0[k]; // keep the rest length of this segment: what the push adds outward comes off the drop const L = Math.hypot(this.r0[k] - this.r0[k - 1], h0 - this.h0[k - 1], tt - this.t0[k - 1]); const dr = r - pr, dtan = tt - pt, dh = -Math.sqrt(Math.max(L * L - dr * dr - dtan * dtan, 1e-6)); const h = ph + (j === 1 ? Math.min(dh, h0) : dh); const q = W[i].clone().addScaledVector(d, r).addScaledVector(t, tt); q.y += h; X[3 * k] = q.x; X[3 * k + 1] = q.y; X[3 * k + 2] = q.z; pr = r; ph = h; pt = tt; } } this.prev.set(X); } reset() { this.push.fill(0); this.pose(0, true); } step(dt) { this.pose(dt, false); } } // ---- materials per mesh ---------------------------------------------------------------------------------------------- const KEY = new THREE.Vector3(-0.45, 0.55, 0.70).normalize(); // stage grades: ambient multiply, key colour, back rim (characters and stage share them) const GRADE = { battle: { ambient: [0.84, 0.87, 0.98], key: [1.05, 1.0, 0.92], rim: [0.38, 0.34, 0.26] }, bust: { ambient: [0.90, 0.90, 0.98], key: [1.03, 1.0, 0.96], rim: [0.24, 0.24, 0.34] } }; const KIND = { cloth: 0, skin: 1, hair: 2, steel: 3, gold: 4, leather: 5, world: 6 }; function paintMaterial(V, role, map, index, infoRole = role, decal = false) { const Rl = V.roles[role] || { mode: "flat", color: "#ff00ff" }, P = (V.paint || {})[role]; const u = { uKind: { value: 9 }, uBase: { value: new THREE.Color(0.8, 0.8, 0.8) }, uKey: { value: KEY.clone() }, uMap: { value: map || white }, uAlpha: { value: 0 }, uPass: { value: 0 }, uInfo: { value: new THREE.Vector4(roleId(infoRole) / 255, (index % 256) / 255, Math.floor(index / 256) / 255, 1) }, uEye: { value: V.roles.iris.colors.map(hex) }, uFlat: { value: new THREE.Color() }, uSkinRef: { value: new THREE.Vector3(0.965, 0.878, 0.80) }, uGold: { value: hex((V.paint && V.paint.gold && V.paint.gold.base) || "#b08b3f") }, uAmbient: { value: new THREE.Vector3(1, 1, 1) }, uKeyCol: { value: new THREE.Vector3(1, 1, 1) }, uRimCol: { value: new THREE.Vector3() }, uGrain: { value: 0.05 }, uFaceTex: { value: 0 }, uDecal: { value: decal ? 1 : 0 }, uBand: { value: null }, uSkinTone: { value: new THREE.Vector3(1, 1, 1) }, uShadeOnly: { value: 0 }, uSkinRamp: { value: 0 }, uSkinL: { value: new THREE.Color() }, uSkinS: { value: new THREE.Color() }, uSkinC: { value: new THREE.Color() }, tCastInfo: { value: null }, tCastDepth: { value: null }, uCastRes: { value: new THREE.Vector2(1, 1) }, uCastOn: { value: 0 }, uCastNear: { value: 0.1 }, uCastFar: { value: 40 }, uCastPpm: { value: 1 }, uMotif: { value: hex((V.paint && V.paint.motif && V.paint.motif.base) || "#3a44a0") } }; if (P) { u.uKind.value = KIND[P.kind] ?? 0; if (P.base) u.uBase.value = hex(P.base); if (P.grain != null) u.uGrain.value = P.grain; } else if (Rl.bands) { u.uKind.value = Rl.world ? 6 : 0; u.uBase.value = hex(Rl.bands[Math.min(1, Rl.bands.length - 1)][1]); } if (P && P.kind === "world") { u.uKey.value = new THREE.Vector3(...V.sun); u.uGrain.value = 0.12; } if (P && P.shade) u.uShadeOnly.value = 1; // shadow cards: skin, always in its shade tone if (P && P.kind === "skin" && P.ramp) { // a designed ramp: lit, shade, deep shade u.uSkinRamp.value = 1; u.uSkinL.value = hex(P.ramp[0]); u.uSkinS.value = hex(P.ramp[1]); u.uSkinC.value = hex(P.ramp[2]); } if (P && P.kind === "skin" && P.base) { // skin tone: the base colour against the default light skin const c = hex(P.base); u.uSkinTone.value.set(c.r / 0.973, c.g / 0.882, c.b / 0.835); } if (role === "face" && map) u.uFaceTex.value = 1; if (role === "hair" && map) u.uAlpha.value = 1; if (role === "iris") { u.uKind.value = 7; u.uAlpha.value = 1; } else if (role === "white" || role === "highlight") { u.uKind.value = map ? 8 : 9; if (map) u.uAlpha.value = 1; u.uFlat.value = hex(Rl.color || "#ffffff"); } else if (!P && Rl.mode === "flat") { u.uKind.value = 9; u.uFlat.value = hex(Rl.color); if (map) u.uAlpha.value = role === "line" ? 2 : 1; } u.uBand.value = u.uGold.value.clone(); const m = new THREE.ShaderMaterial({ uniforms: u, vertexShader: PAINT_VS, fragmentShader: PAINT_FS, side: THREE.DoubleSide, extensions: { derivatives: true } }); // meshes without plate data or a cloth edge read zeros, not whatever another program left in that slot m.defaultAttributeValues = { ...m.defaultAttributeValues, _plate: [0, 0, 0, 0], edge: [0] }; return m; } // ---- models ------------------------------------------------------------------------------------------------------ const loader = new GLTFLoader(); const chars = new Map(); // name -> loaded character let ground = null; const stage = new THREE.Group(); // the duel: both fighters stand on it; the turn slider rotates it scene.add(stage); // the lineup: every enemy model standing in a row (camera.json's lineup), each turning in place const LINEUP = CAM.lineup || { order: [], spacing: 0.9 }; const ROW_VIEW = () => state.view === "lineup" || state.view === "compare"; // several models in a row, each turning in place const CAM_VIEW = () => state.view === "duel" || ROW_VIEW() ? CAM.duel.cam : charDef(state.who).cams[state.view]; const activeNames = () => state.view === "duel" ? [CAM.duel.order.player, CAM.duel.order.opponent] : state.view === "lineup" ? LINEUP.order : state.view === "compare" ? state.compare : [state.who]; const active = () => activeNames().map(n => chars.get(n)).filter(Boolean); async function parseGlb(file) { const j = await (await fetch(file)).json(); const bin = Uint8Array.from(atob(j.glb), c => c.charCodeAt(0)); return await new Promise((ok, bad) => loader.parse(bin.buffer, "", ok, bad)); } let meshIndex = 1; function prepMesh(o, V, opts = {}) { const m0 = o.material, role = o.userData.role || "body", raw = rawTex(m0.map); const infoRole = role === "hair" && opts.hairB ? "hair_b" : role === "shadow" ? "body" : role; const decal = !!(m0.userData && m0.userData.decal); // the map is a design mask, not a colour const base = { map: decal ? null : (m0.map || null), color: m0.color.clone(), side: THREE.DoubleSide, alphaTest: m0.alphaTest || 0 }; const clay = new THREE.MeshStandardMaterial({ ...base, roughness: 0.85, metalness: m0.metalness || 0 }); const toon = new THREE.MeshToonMaterial({ ...base, gradientMap: gradient }); if (role === "hair" && m0.map && V.paint && V.paint.hair) { // the VRoid sample's hair texture carries its own colour (dark on some samples): in 3D use the // character's hair colour, with the texture's light and dark only for strand detail for (const m of [clay, toon]) { m.color = hex(V.paint.hair.base); m.onBeforeCompile = sh => { sh.fragmentShader = sh.fragmentShader.replace("#include <map_fragment>", `#ifdef USE_MAP vec4 texel_ = texture2D(map, vMapUv); diffuseColor.rgb *= mix(1.0, clamp(dot(texel_.rgb, vec3(0.3333)) / 0.5, 0.45, 1.35), 0.6); diffuseColor.a *= texel_.a; #endif`); }; m.customProgramCacheKey = () => "hairlum"; } } o.userData.mats = { clay, toon, painted: paintMaterial(V, role, raw, meshIndex, infoRole, decal) }; meshIndex++; o.userData.group = o.userData.group || "body"; o.userData.role = role; o.frustumCulled = false; } async function loadCharacter(name) { if (chars.has(name)) return chars.get(name); const def = charDef(name), V = def.info, ci = CAM.characters.indexOf(def); const gltf = await parseGlb(`${name}.glb.json`); const turn = new THREE.Group(), holder = new THREE.Group(); turn.add(holder); holder.add(gltf.scene); const meshes = []; gltf.scene.traverse(o => { if (o.isMesh) { prepMesh(o, V, { hairB: ci > 0 }); meshes.push(o); } }); gltf.scene.updateMatrixWorld(true); const bone = n => gltf.scene.getObjectByName(n); const cloths = [], world = new THREE.Group(); for (const [cname, info] of Object.entries(V.cloth || {})) { const skinned = meshes.find(m => m.userData.material === cname); if (!skinned) continue;Bashsource ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && sed -n 180,245p tools/export_glb.py; grep -n "def hexp\|PALETTE\s*=\|^ \"[0-9A-Za-z]\":" tools/*.py | head -20; cat $KIT/variant_build.py | head -80
command
source ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && sed -n 180,245p tools/export_glb.py; grep -n "def hexp\|PALETTE\s*=\|^ \"[0-9A-Za-z]\":" tools/*.py | head -20; cat $KIT/variant_build.py | head -80
result, 121 lines
CLOTH_HEM_ONLY = {"OUTFIT_skirt"} # border band on the hem row only (its waist has a gold trim) def scene_info(portrait, palette): """Everything the browser's pixel renderer and cloth solver need besides the meshes: palette bands per role, the lights, body colliders, cloth grids and the mouth curve (model space, turned to face +z like the GLB).""" import vroid_portrait as vp from vrm import bone_weight hexp = lambda c: palette[c] bands = lambda tbl: [[thr if thr > -5 else -9, hexp(c)] for thr, c in tbl] roles = {"face": dict(mode="bands", bands=bands(vp.SKIN_BANDS)), "body": dict(mode="bands", bands=bands(vp.SKIN_BANDS), cap=hexp("4")), "shadow": dict(mode="flat", color=hexp("3")), "hair": dict(mode="bands", bands=bands(vp.HAIR_BANDS)), "cloth": dict(mode="bands", bands=bands(vp.CLOTH_BANDS)), "line": dict(mode="flat", color=hexp("o")), "brow": dict(mode="flat", color=hexp("A")), "mouth": dict(mode="flat", color=hexp("1")), "white": dict(mode="flat", color=hexp("w")), "highlight": dict(mode="flat", color=hexp("w")), "iris": dict(mode="iris", colors=[hexp("r"), hexp("s"), hexp("t")], cuts=[0.25, 0.5])} for r, tbl in vp.MATERIALS.items(): roles[r] = dict(mode="bands", bands=bands(tbl), world=r in vp.WORLD_ROLES) names = portrait.v.nodes_by_name() W = portrait.v.node_world(portrait.pose_now) body = [p for p, r in zip(portrait.prims, portrait.roles) if r == "body"][0] caps = [] for a, b in CAPSULES: if a not in names or b not in names: continue A, B = W[names[a]][:3, 3], W[names[b]][:3, 3] wv = bone_weight(body, (a,)) pts = body["pos"][wv > 0.5] if len(pts) < 8: continue ab = B - A t = np.clip(((pts - A) @ ab) / (ab @ ab), 0, 1) d = np.linalg.norm(pts - (A + t[:, None] * ab), axis=1) caps.append(dict(a=(A * FLIP).tolist(), b=(B * FLIP).tolist(), r=float(np.percentile(d, 70) + 0.012), bones=[a, b])) cloth = {} for p in portrait.prims: if p["material"] in CLOTH and "grid" in p: trims, band, collide = CLOTH[p["material"]] cloth[p["material"]] = dict(u=int(p["grid"][0]), v=int(p["grid"][1]), trims=list(trims), band=band, collide=list(collide) if collide else None, closed=bool(p.get("closed")), edges="hem" if p["material"] in CLOTH_HEM_ONLY else "all") curve = None if getattr(portrait, "mouth_curve", None) is not None: face = [p for p, r in zip(portrait.prims, portrait.roles) if r == "face"] allp = np.concatenate([p["pos"] for p in face]) alln = np.concatenate([p["nrm"] for p in face]) from scipy.spatial import cKDTree _, i = cKDTree(allp).query(portrait.mouth_curve) curve = ((portrait.mouth_curve + alln[i] * 0.0015) * FLIP).tolist() marks = None if getattr(portrait, "marks", None): m = next(iter(portrait.marks.values())) img = Image.fromarray(np.where(m == 1, 128, 0).astype(np.uint8), "L") # nose mark only; the mouth is a line b = io.BytesIO() img.save(b, "PNG", optimize=True) import base64 marks = base64.b64encode(b.getvalue()).decode() # painted renderer: a material kind and one base colour per role; the shader builds the ramp pc = dict(signature="#7d1e2c", doublet="#2a2d3d", heraldry="#dfe2e8", leather="#5f3e2b", boot="#4a3326", glove="#5f3e2b", steel="#a6adbb", gold="#b08b3f", hair=hexp("C"), trim="#b08b3f") pc.update(getattr(portrait, "paint", None) or {}) tools/proportions.py:61: "H": "height, crown to sole (metres)", tools/vroid_portrait.py:34:PALETTE = {**SKIN, **HAIR_TONES, **COAT, **EYES, **CAPE, **METAL, **GOLD, **LEATHER, **DOUBLET, **HERALDRY, **GRASS, """Build armory characters from the worktree in $LOWMARCH_REPO into a site dir, with an expression baked in. python3 variant_build.py SITE_COPY forward=0.13 pull=0.5 hair=0.0 NAME [NAME...] SITE_COPY is a copy of the built armory site (with the patched sheet rows); each NAME's .glb.json is replaced. """ import functools import os import sys os.environ["LOWMARCH_SITE"] = "armory" REPO = os.environ["LOWMARCH_REPO"] # the track's worktree sys.path[:0] = [os.path.join(REPO, "review", "vroid-02"), os.path.join(REPO, "tools"), os.path.join(REPO, "art", "cast")] import build # noqa: E402 import vroid_portrait as vp # noqa: E402 site = sys.argv[1] kw = dict(a.split("=") for a in sys.argv[2:] if "=" in a) names = [a for a in sys.argv[2:] if "=" not in a] hair = float(kw.pop("hair", 0.0)) rest = kw.pop("rest", None) # rest=Fcl_ALL_Joy:1,Fcl_MTH_A:0.5 (VRoid expression shapes) if rest is not None: for n in names: build.CAST[n]["shape"] = dict(build.CAST[n]["shape"], rest={} if rest == "none" else {k: float(v) for k, v in (kv.split(":") for kv in rest.split(";"))}) skin = kw.pop("skin", None) # skin=<tone in cast.SKINS> if skin is not None: import cast for n in names: build.CAST[n]["paint"] = dict(build.CAST[n].get("paint") or {}) if cast.SKINS[skin]: build.CAST[n]["paint"]["skin"] = cast.SKINS[skin] else: build.CAST[n]["paint"].pop("skin", None) if "marks" in kw: # marks=nose_dy,ry,rx/lip_dy,ry,rx/neck nose, lip, neck = kw.pop("marks").split("/") vp.SHADE_MARKS = dict(nose=tuple(map(float, nose.split(","))), lip=tuple(map(float, lip.split(","))), neck=float(neck)) vp.face_normals = functools.partial(vp.face_normals, **{k: float(v) for k, v in kw.items()}) vp.NEUTRAL["hair_normals"] = hair build.OUT = site build.GEOM = os.path.join(site, "_geom") os.makedirs(build.GEOM, exist_ok=True) import json cam_path = os.path.join(site, "camera.json") cam = json.load(open(cam_path)) for n in names: res = build.character((n, None)) for c in cam["characters"]: # the page reads paint, roles and cameras from camera.json if c["name"] == n: c.update(info=res["info"], cams=res["cams"]) print(n, res["size"]) json.dump(cam, open(cam_path, "w"))Bashsource ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && ls review/vroid-02 art/cast; grep -n "rest" review/vroid-02/build.py art/cast/*.py | head -30; grep -n "arm_f_slender\|arm_m_youth" -A12 art/cast/*.py | grep -i "rest\|shape\|name" | head
command
source ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && ls review/vroid-02 art/cast; grep -n "rest" review/vroid-02/build.py art/cast/*.py | head -30; grep -n "arm_f_slender\|arm_m_youth" -A12 art/cast/*.py | grep -i "rest\|shape\|name" | head
result, 46 lines
art/cast: cast.py __pycache__ review/vroid-02: armory_notes.html base_spike_notes.html battle_test.py build.py closeup_test.py costume_test.py duel_check.py fit_test.py index.html mh_workshop_notes.html mouths.py pose_test.py __pycache__ sheets.py snapshot.py art/cast/cast.py:232:# archetype. What a face does at rest (a frown, half-closed eyes) and age lines are drawn when the figure is built art/cast/cast.py:273:PORTRAIT = { # at rest and age lines: name -> shape extras (tools/vroid_portrait.py) art/cast/cast.py:274: "arm_m_strong": dict(rest={"Fcl_BRW_Angry": 0.4}), art/cast/cast.py:275: "arm_m_heroic": dict(rest={"Fcl_BRW_Angry": 0.25}), art/cast/cast.py:276: "arm_m_brute": dict(rest={"Fcl_EYE_Close": 0.28, "Fcl_BRW_Angry": 0.75}), art/cast/cast.py:277: "arm_m_old": dict(age_lines=0.8, blush=0.0, rest={"Fcl_EYE_Angry": 0.4, "Fcl_EYE_Close": 0.25, "Fcl_BRW_Angry": 0.5, art/cast/cast.py:279: "arm_f_old": dict(age_lines=0.6, blush=0.3, rest={"Fcl_EYE_Close": 0.22, "Fcl_MTH_Down": 0.2}), art/cast/cast.py:280: "arm_f_strong": dict(rest={"Fcl_BRW_Angry": 0.2}), art/cast/cast.py:321: shape=dict(head=0.95, leg_len=1.04, shoulder_w=0.008, rest={"Fcl_BRW_Angry": 0.35}), art/cast/cast.py:332: shape=dict(head=0.95, leg_len=1.04, shoulder_w=0.008, rest={"Fcl_BRW_Angry": 0.35}), art/cast/cast.py:348: rest={"Fcl_EYE_Angry": 0.35}), art/cast/cast.py:353: rest={"Fcl_EYE_Angry": 0.45, "Fcl_EYE_Close": 0.1, "Fcl_EYE_Joy": 0.18, "Fcl_BRW_Angry": 0.45, art/cast/cast.py:386: rest={"Fcl_EYE_Angry": 0.35}, group="Round 2 · VRoid"), art/cast/cast.py:417: eye=0.92, neck=1.2, chest=1.06, rest={"Fcl_EYE_Close": 0.2, "Fcl_BRW_Angry": 0.35}), art/cast/cast.py:424: rest={"Fcl_EYE_Close": 0.18, "Fcl_BRW_Angry": 0.6}), art/cast/cast.py:440: rest={"Fcl_EYE_Close": 0.15}), 273-PORTRAIT = { # at rest and age lines: name -> shape extras (tools/vroid_portrait.py) 274- "arm_m_strong": dict(rest={"Fcl_BRW_Angry": 0.4}), 275- "arm_m_heroic": dict(rest={"Fcl_BRW_Angry": 0.25}), 276- "arm_m_brute": dict(rest={"Fcl_EYE_Close": 0.28, "Fcl_BRW_Angry": 0.75}), 277- "arm_m_old": dict(age_lines=0.8, blush=0.0, rest={"Fcl_EYE_Angry": 0.4, "Fcl_EYE_Close": 0.25, "Fcl_BRW_Angry": 0.5, 279- "arm_f_old": dict(age_lines=0.6, blush=0.3, rest={"Fcl_EYE_Close": 0.22, "Fcl_MTH_Down": 0.2}), 280- "arm_f_strong": dict(rest={"Fcl_BRW_Angry": 0.2}), 282-ARMORY = { # name: (label, sex, its reference, cage spec beyond base and ref) 291: "arm_f_slender": ("Woman, slender", "f", "ref_f_slender", dict(relax=4, bust_shape=dict(spread=0.008, drop=0.005, flat=0.25))), 292- "arm_f_average": ("Woman, average", "f", "ref_f_average", dict(relax=4, bust_shape=dict(spread=0.005, drop=0.008, flat=0.3))),Bashsource ~/Projects/lowmarch-bench/kit/env-a.sh && mkdir -p $WORK/tex && cat > $WORK/tex/x.py <<'EOF'
command
source ~/Projects/lowmarch-bench/kit/env-a.sh && mkdir -p $WORK/tex && cat > $WORK/tex/x.py <<'EOF' import json,base64,struct,sys,io from PIL import Image j=json.load(open(sys.argv[1])); b=base64.b64decode(j["glb"]) L=struct.unpack("<I",b[12:16])[0]; g=json.loads(b[20:20+L]); bin_=b[20+L+8:] for m in g["materials"]: print(m["name"], m.get("pbrMetallicRoughness",{}).get("baseColorTexture"), m.get("pbrMetallicRoughness",{}).get("baseColorFactor")) for i,t in enumerate(g["textures"]): im=g["images"][t["source"]]; bv=g["bufferViews"][im["bufferView"]] data=bin_[bv.get("byteOffset",0):bv.get("byteOffset",0)+bv["byteLength"]] Image.open(io.BytesIO(data)).save(f"{sys.argv[2]}/t{i}.png") EOF python3 -I $WORK/tex/x.py $WORK/site/arm_f_slender.glb.json $WORK/tex 2>&1 | grep -i "face\|mouth\|brow\|line" ; ls $WORK/texresult, 16 lines
body:mouth {'index': 0} None body:face {'index': 3} None body:brow {'index': 5} None body:line {'index': 6} None body:line {'index': 7} None t0.png t1.png t2.png t3.png t4.png t5.png t6.png t7.png t8.png t9.png x.pyRead~/Projects/lowmarch-bench/work/a/tex/t0.png
input
{ "file_path": "~/Projects/lowmarch-bench/work/a/tex/t0.png" }result
no output
Read~/Projects/lowmarch-bench/work/a/tex/t5.png
input
{ "file_path": "~/Projects/lowmarch-bench/work/a/tex/t5.png" }result
no output
Bashsource ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && sed -n 1310,1372p review/vroid-02/index.html; grep -n "uCastOn\|tCastInfo.value\|renderOrder" review/vroid-02/index.html
command
source ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && sed -n 1310,1372p review/vroid-02/index.html; grep -n "uCastOn\|tCastInfo.value\|renderOrder" review/vroid-02/index.html
result, 68 lines
if (view === "duel") setClip("clash", SHEET ? null : "idle"); else if (view === "battle") setClip(want); else if (view === "lineup") setClip(clip && CLIP_VIEW[clip] === "battle" ? clip : "idle"); else setClip("idle"); animate(0); scene.updateMatrixWorld(true); cs.forEach(c => c.cloths.forEach(cl => { cl.reset(false); cl.write(); })); } catch (e) { console.error(e); statusEl.textContent = "The model didn't load."; } } // ---- rendering ------------------------------------------------------------------------------------------------------- function applySkin(meshes) { // the picked tone on every skin material (face, body, shadow cards); the built tone is kept to go back to const tone = state.skin && PAGE && PAGE.skins ? PAGE.skins.find(s => s.name === state.skin) : null; for (const o of meshes) { const m = o.userData.mats && o.userData.mats.painted, u = m && m.uniforms; if (!u || !u.uSkinRamp || u.uKind.value !== 1) continue; if (!o.userData.builtSkin) o.userData.builtSkin = [u.uSkinRamp.value, u.uSkinL.value.clone(), u.uSkinS.value.clone(), u.uSkinC.value.clone()]; if (tone) { u.uSkinRamp.value = 1; u.uSkinL.value.copy(hex(tone.ramp[0])); u.uSkinS.value.copy(hex(tone.ramp[1])); u.uSkinC.value.copy(hex(tone.ramp[2])); } else { const b = o.userData.builtSkin; u.uSkinRamp.value = b[0]; u.uSkinL.value.copy(b[1]); u.uSkinS.value.copy(b[2]); u.uSkinC.value.copy(b[3]); } } } function render() { const meshes = allMeshes(), cs = active(); applySkin(meshes); const cloths = cs.flatMap(c => c.cloths); if (state.mode === "3d") { meshes.forEach(o => o.material = o.userData.mat3d); cloths.forEach(c => c.lines3d.visible = trimsShown(c)); sun.position.copy(camera.position).add(new THREE.Vector3(-3, 4, 0)); sun.target.position.copy(ctl.target); sun.target.updateMatrixWorld(); R.setRenderTarget(null); R.setClearColor(0x191626, 1); R.clear(); R.render(scene, camera); R.setClearColor(0x000000, 0); return; } const buf = R.getDrawingBufferSize(new THREE.Vector2()); targetsFor(buf.x, buf.y); cloths.forEach(c => c.lines3d.visible = false); meshes.forEach(o => { o.material = o.userData.mats.painted; o.material.uniforms.uPass.value = 0; }); const G = GRADE[state.view === "bust" ? "bust" : "battle"]; // stage grade, shared by the characters and the stage for (const o of meshes) { const mu = o.material.uniforms; mu.uAmbient.value.set(...G.ambient); mu.uKeyCol.value.set(...G.key); mu.uRimCol.value.set(...G.rim); } // the info pass (roles, ids, depth) first: the colour pass reads it for cast shadows meshes.forEach(o => { // unbound while it is drawn: reading it now would be a feedback loop const mu = o.material.uniforms; mu.uPass.value = 1; mu.uCastOn.value = 0; mu.tCastInfo.value = null; mu.tCastDepth.value = null; }); R.setRenderTarget(targets.rtInfo); R.clear(); R.render(scene, camera); const ppmBuf = buf.y / (camera.top - camera.bottom) * camera.zoom; for (const o of meshes) { const mu = o.material.uniforms; mu.uPass.value = 0; mu.uCastOn.value = 1; mu.tCastInfo.value = targets.rtInfo.texture; mu.tCastDepth.value = targets.rtInfo.depthTexture; mu.uCastRes.value.set(buf.x, buf.y); mu.uCastNear.value = camera.near; mu.uCastFar.value = camera.far; mu.uCastPpm.value = ppmBuf; } R.setRenderTarget(targets.rt); R.clear(); R.render(scene, camera); const u = paint.mat.uniforms; u.tColor.value = targets.rt.texture; u.tInfo.value = targets.rtInfo.texture; u.tDepth.value = targets.rtInfo.depthTexture; u.uRes.value.set(buf.x, buf.y); u.uNear.value = camera.near; u.uFar.value = camera.far; // line weight follows the figure's size on screen (study: ~0.4% of its height) const ppm = buf.y / (camera.top - camera.bottom) * camera.zoom, dpr = R.getPixelRatio(); u.uSil.value = THREE.MathUtils.clamp(1.75 * ppm * 0.0042, dpr, 2.6 * dpr); u.uInner.value = Math.max(dpr * 0.75, u.uSil.value * 0.55); R.setRenderTarget(null); R.clear(); R.render(paint.scene, compCam); } // ---- check sheets ---------------------------------------------------------------------------------------------------- // ?sheet=<name> poses one character through every check view; ?sheet=duel shows the bind. Each tile // is posed, its cloth settled at that angle, rendered by the same painted renderer as the page, and // drawn into one image, which lands in #sheetOut as a PNG data URL (a headless browser reads it). const ARMS_YAWS = [0, 45, 90, 135, 180, 270]; 347: uniform sampler2D tCastInfo; uniform sampler2D tCastDepth; uniform vec2 uCastRes; uniform int uCastOn; 426: else if (uCastOn == 1 && uKind == 1 && int(uInfo.x * 255.0 + 0.5) == 2 && castShadow() > 0.5) 813: tCastInfo: { value: null }, tCastDepth: { value: null }, uCastRes: { value: new THREE.Vector2(1, 1) }, uCastOn: { value: 0 }, 1348: const mu = o.material.uniforms; mu.uPass.value = 1; mu.uCastOn.value = 0; mu.tCastInfo.value = null; mu.tCastDepth.value = null; 1354: mu.uPass.value = 0; mu.uCastOn.value = 1; mu.tCastInfo.value = targets.rtInfo.texture; mu.tCastDepth.value = targets.rtInfo.depthTexture;Bashsource ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && grep -n "SKIN_HEAD\b\|const SKIN_HEAD\|const SKIN_BODY" review/vroid-02/index.html | head -3; grep -n "\"A\"\|'A':\|\"o\":\|\"1\":\|\"2\":" tools/vroid_portrait.py | head; grep -n "^HAIR_TONES\|^SKIN =" -A6 tools/vroid_portrait.py | head -20
command
source ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && grep -n "SKIN_HEAD\b\|const SKIN_HEAD\|const SKIN_BODY" review/vroid-02/index.html | head -3; grep -n "\"A\"\|'A':\|\"o\":\|\"1\":\|\"2\":" tools/vroid_portrait.py | head; grep -n "^HAIR_TONES\|^SKIN =" -A6 tools/vroid_portrait.py | head -20
result, 7 lines
309:const SKIN_HEAD = `#include <common> 311:const SKIN_BODY = `#include <beginnormal_vertex> 316:const PAINT_VS = `${SKIN_HEAD} 42:PRIORITY = {"o": 8, "r": 5, "s": 5, "t": 4, "w": 3, "A": 3, "1": 8} # features win the 4x4 vote 994: char[mk] = "A" 1019: down = {"E": "D", "D": "C", "C": "B", "B": "A"} 1062: rule[hair0 & hair1 & (p1 != prim)] = "A"
agent
+2:19Now the page edits: brow depth bias, darker brow colour, thicker alpha, mouth texture remap and mouth outline.
Bashsource ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && python3 - <<'EOF'
command
source ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && python3 - <<'EOF' p="review/vroid-02/index.html"; s=open(p).read() def rep(a,b,n=1): global s assert s.count(a)==n, (a, s.count(a)); s=s.replace(a,b) # 1. vertex shader: a depth pull toward the camera (brows over the bangs) rep(""" attribute float edge; attribute vec4 _plate; varying vec3 vN;""", """ attribute float edge; attribute vec4 _plate; uniform float uFront; varying vec3 vN;""") rep(""" vec4 mv = modelViewMatrix * vec4(transformed, 1.0); vView = -mv.xyz; gl_Position = projectionMatrix * mv; }`;""", """ vec4 mv = modelViewMatrix * vec4(transformed, 1.0); vView = -mv.xyz; gl_Position = projectionMatrix * vec4(mv.xy, mv.z + uFront, mv.w); // uFront: drawn over what is this close in front }`;""") # 2. fragment: brow alpha threshold, mouth kind rep(""" if (uAlpha == 1 && tex.a < 0.5) discard;""", """ if (uAlpha == 1 && tex.a < 0.5) discard; if (uAlpha == 3 && tex.a < 0.28) discard; // brows: a fuller stroke than the texture's half alpha""") rep(""" } else if (uKind == 8) { col = tex.rgb; }""", """ } else if (uKind == 8) { col = tex.rgb; } else if (uKind == 10) { // the open mouth: VRoid's map read as three parts, float lum = dot(tex.rgb, vec3(1.0 / 3.0)); // teeth (white), tongue (its light pink) and the col = mix(vec3(0.40, 0.13, 0.16), vec3(0.86, 0.47, 0.50), smoothstep(0.53, 0.58, tex.g)); // dark inside col = mix(col, vec3(0.98, 0.96, 0.95), smoothstep(0.80, 0.86, lum)); }""") # 3. material setup rep(""" u.uBand.value = u.uGold.value.clone(); const m = new THREE.ShaderMaterial(""", """ if (role === "brow") { // brows: a deep shade of the hair, drawn over the bangs (3 cm) u.uAlpha.value = map ? 3 : 0; u.uFront.value = 0.03; if (V.paint && V.paint.hair) { const h = {}; hex(V.paint.hair.base).getHSL(h); u.uFlat.value.setHSL(h.h * 0.92, Math.min(1, h.s * 0.75), 0.17); } } if (role === "mouth" && map) u.uKind.value = 10; u.uBand.value = u.uGold.value.clone(); const m = new THREE.ShaderMaterial(""") rep(""" uSkinTone: { value: new THREE.Vector3(1, 1, 1) }, uShadeOnly: { value: 0 },""", """ uSkinTone: { value: new THREE.Vector3(1, 1, 1) }, uShadeOnly: { value: 0 }, uFront: { value: 0 },""") # 4. composite: a dark line round the open mouth rep(""" bool feature = r0 >= 4 && r0 <= 9;""", """ bool feature = r0 >= 4 && r0 <= 9; if (r0 == 9) { // the open mouth's rim: a lip line where it meets the face for (int k = 0; k < 8; k++) { float a = float(k) * 0.785398; vec2 q = uv + vec2(cos(a), sin(a)) * uSil * 0.9 * px; vec4 ni = texture2D(tInfo, q); if (ni.a > 0.5 && rid(ni) == 1) { col = mix(ink(texture2D(tColor, q).rgb), vec3(0.30, 0.10, 0.12), 0.4); break; } } }""") open(p,"w").write(s) EOF grep -n "hex = \|function hex" review/vroid-02/index.html | head -3result, 1 line
290:const hex = h => { // raw palette value, no colour-space conversionBashsource ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && sed -n 288,296p review/vroid-02/index.html
command
source ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && sed -n 288,296p review/vroid-02/index.html
result, 9 lines
} const hex = h => { // raw palette value, no colour-space conversion const n = parseInt(h.slice(1), 16); return new THREE.Color().setRGB((n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255, THREE.LinearSRGBColorSpace); }; const rawTex = t => { if (!t) return null; const c = t.clone(); c.colorSpace = THREE.NoColorSpace; c.needsUpdate = true; return c; }; const gradient = (() => { const d = new Uint8Array([70, 70, 70, 255, 140, 140, 140, 255, 205, 205, 205, 255, 255, 255, 255, 255]);Bashsource ~/Projects/lowmarch-bench/kit/env-a.sh && cp $WORK/expr_list.txt $WORK/expr_list.orig.txt && cat > $WORK/expr_list.txt <<'EOF'
command
source ~/Projects/lowmarch-bench/kit/env-a.sh && cp $WORK/expr_list.txt $WORK/expr_list.orig.txt && cat > $WORK/expr_list.txt <<'EOF' # expression recipes: name|VRoid shapes baked at build (Fcl_* names, weight; ';' between; 'none' = neutral) # the 14 the sheets show; keep these names (front_sheet.py reads them). Edit the recipes freely. neutral|none joy|Fcl_EYE_Joy:1;Fcl_BRW_Joy:1;Fcl_MTH_Joy:0.6 smile|Fcl_BRW_Fun:0.6;Fcl_EYE_Fun:0.4;Fcl_MTH_Fun:1 angry|Fcl_BRW_Angry:1;Fcl_EYE_Angry:0.7;Fcl_MTH_Angry:1 sad|Fcl_BRW_Sorrow:1;Fcl_EYE_Sorrow:0.7;Fcl_MTH_Sorrow:1 surprised|Fcl_BRW_Surprised:1;Fcl_EYE_Surprised:1;Fcl_MTH_Surprised:0.55 shout|Fcl_BRW_Angry:1;Fcl_EYE_Angry:0.6;Fcl_MTH_A:1 wink|Fcl_EYE_Close_R:1;Fcl_BRW_Fun:0.6;Fcl_MTH_Fun:1 smug|Fcl_EYE_Close:0.35;Fcl_BRW_Fun:0.4;Fcl_MTH_Fun:0.7 lip_A|Fcl_MTH_A:0.75 lip_I|Fcl_MTH_I:1 lip_U|Fcl_MTH_U:1 lip_E|Fcl_MTH_E:0.85 lip_O|Fcl_MTH_O:0.8 EOF cd $LOWMARCH_REPO && date && time $KIT/build_expr.sh $WORK/expr_list.txt neutral joy smile angry sad
result, 10 lines
Fri Oct 30 12:45:59 PM PST 2026 built neutral built joy built smile built angry built sad real 1m26.062s user 1m8.025s sys 0m52.766s
Bashsource ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && time $KIT/build_expr.sh $WORK/expr_list.txt surprised shout wink smug lip_A lip_I lip_U lip_E lip_O && time $KIT/shoot_expr.sh $LOWMARCH_REPO/review/vroid-02/index.html r1 && $KIT/sheets.sh r1 "Round 1: brows over bangs, mouth colours+rim, split recipes"
command
source ~/Projects/lowmarch-bench/kit/env-a.sh && cd $LOWMARCH_REPO && time $KIT/build_expr.sh $WORK/expr_list.txt surprised shout wink smug lip_A lip_I lip_U lip_E lip_O && time $KIT/shoot_expr.sh $LOWMARCH_REPO/review/vroid-02/index.html r1 && $KIT/sheets.sh r1 "Round 1: brows over bangs, mouth colours+rim, split recipes"
result, 20 lines
built surprised built shout built wink built smug built lip_A built lip_I built lip_U built lip_E built lip_O real 2m26.598s user 2m2.291s sys 1m43.091s shot r1 real 7m31.980s user 0m32.006s sys 0m26.725s Command did not complete within its 600s timeout and was moved to the background (ID: bjx5t4ezo). Output is being written to: /tmp/claude-1000/-home-severian-Projects/d2fdcea1-0c48-4814-81a2-89cf837a9b4f/tasks/bjx5t4ezo.output. You will be notified when it completes. It is running now: do not start a second copy while it runs. If it is no longer needed, stop it with TaskStop. To check interim output, use Read on that file path. Session cwd remains ~/Projects/lowmarch-bench/c; directory changes made by the backgrounded command do not apply to subsequent commands.
Read~/Projects/lowmarch-bench/work/a/shot_r1/angry/painted_arm_f_slender.png
input
{ "file_path": "~/Projects/lowmarch-bench/work/a/shot_r1/angry/painted_arm_f_slender.png" }result
no output


