Hair becomes a separate skinned mesh, which is what the character-artist skill asks for: a card system, and swappable parts as their own meshes.
Tools/character_hair_build.py builds SM_Char_Hair: scalp cap + 18 cards in three rings, quads only, all vertices weighted to head, UV-mapped for the strand sheet.
Tools/ue_character_hair_import.py imports it as its own skeletal mesh — joined into the body, Unreal collapses the second material slot and hair renders as skin.
The pawn carries it on a second USkeletalMeshComponent using SetLeaderPoseComponent, so it follows the head with no second rig.
Also in this branch: Tools/comfyui_generate.py (local ComfyUI driver with provenance sidecars) and Tools/generate_character_textures.py (procedural skin pores and hair strands).
Still wrong: the cards point outward so the silhouette reads as a spiked crown; the next pass orients them tangentially with a second overlapping row. Tools/ue_character_materials.py segfaults in the headless commandlet when editing a material graph, so the generated textures are not wired yet.
Hair becomes a separate skinned mesh, which is what the `character-artist` skill asks for: a card system, and swappable parts as their own meshes.
- `Tools/character_hair_build.py` builds `SM_Char_Hair`: scalp cap + 18 cards in three rings, quads only, all vertices weighted to `head`, UV-mapped for the strand sheet.
- `Tools/ue_character_hair_import.py` imports it as its own skeletal mesh — joined into the body, Unreal collapses the second material slot and hair renders as skin.
- The pawn carries it on a second `USkeletalMeshComponent` using `SetLeaderPoseComponent`, so it follows the head with no second rig.
Also in this branch: `Tools/comfyui_generate.py` (local ComfyUI driver with provenance sidecars) and `Tools/generate_character_textures.py` (procedural skin pores and hair strands).
**Still wrong:** the cards point outward so the silhouette reads as a spiked crown; the next pass orients them tangentially with a second overlapping row. `Tools/ue_character_materials.py` segfaults in the headless commandlet when editing a material graph, so the generated textures are not wired yet.
Follows the character-artist skill on two points it is explicit about: hair for a
game character is a card system, and swappable parts stay separate meshes.
- Tools/character_hair_build.py builds SM_Char_Hair: a scalp cap plus 18 cards in
three rings, quads only, every vertex weighted 1.0 to the head bone, UV-mapped for
the strand sheet from Tools/generate_character_textures.py. 321 verts / 404 tris.
- Tools/ue_character_hair_import.py imports it as its own skeletal mesh, which is the
point: joined into the body, Unreal collapses the second material slot and the hair
renders with the skin material.
- The pawn carries it as a second USkeletalMeshComponent on SetLeaderPoseComponent, so
it follows the head bone with no second rig and hides with the body in first person.
Still wrong and recorded: the cards point outward, so the silhouette reads as a spiked
crown rather than a short cut. The next pass orients them tangentially along the
skull with a second overlapping row. Tools/ue_character_materials.py also segfaults in
the headless commandlet when editing a material graph, so the generated textures are
not wired yet and the hair keeps its Blender base colour.
The first card build anchored every card on a fixed outward vector, so the silhouette
was a spiked crown. Each card is now oriented from the scalp normal at its anchor: the
length runs along the downward tangent, the width across it, and a small tip lift gives
the cut volume. Three overlapping rows (7/9/10) replace the single ring; overlap is
what stops cards reading as separate spikes.
26 cards, 401 verts, 224 quads, 468 tris, still all quads on one head bone.
Recorded as still open: the skin renders darker than the baked diffuse suggests, and
the generated pore and strand textures remain unwired because the material-editing
tool segfaults in the headless commandlet.
Two tools that turn character work into something measurable.
- Tools/comfyui_character_blueprint.py generates a four-view sheet (front, side, back,
head) plus a contact sheet from one description, with fixed seeds and a JSON record
of prompts and prompt ids. First sheet: 'GigLine walker', lean pedestrian in a navy
tee, jeans and sneakers.
- Tools/character_match.py compares a rendered character against a sheet: figure
aspect, fill and coverage deltas, mean CIE76 colour distance, dominant-palette
distance, and a side-by-side montage. The deltas stay separate on purpose - a single
similarity score between a photoreal sheet and a low-detail body would be a number
with no meaning.
- comfyui_generate.py grew wait_for_image() so both tools share the polling path
instead of duplicating it.
- 13 unit tests for the colour maths, masks and palette; 95 tool tests pass.
First real result, recorded rather than smoothed over: aspect delta 1.24, fill delta
0.12, mean deltaE 31.9, palette deltaE 25.4. The montage shows the reason - the model
is a nude body and the blueprint wears a tee and jeans, so clothing dominates every
metric. Figure segmentation is explicit (--render-box): auto-detection picked backdrop
blobs on every attempt, and a background plate is the proper fix.
Follows the character-artist clothing workflow literally: duplicate the body's own
faces for the garment base, solidify for cloth thickness, and keep each piece a
separate mesh. Attached as leader-pose components beside the hair, so no second rig.
- Tools/character_clothing_build.py builds three garments from the body mesh, trimmed
geometrically (height band plus a width limit) instead of by dominant bone: the bone
filter left holes wherever a face's strongest bone was outside the allowed list.
- Tools/ue_character_clothing_import.py imports them as CharacterTee / CharacterJeans /
CharacterShoes, each arriving with its own single material slot.
- The pawn attaches all three, hidden with the body in first person.
Three traps recorded in MEMORY.md, each of which cost a build: MPFB weights its helper
geometry to groups that dominate every vertex, Blender refuses to apply a modifier to a
mesh with shape keys, and object-mode polygon selection is discarded on entering edit
mode - the delete wiped the whole garment.
Known defects, in TODO.md: the garments have coverage holes at shoulders, hips and
knees, and the hair reads light and wispy since its material was repointed to the
masked strand sheet.
Solidify thickness went from 1 cm to 2 cm and the shell is fattened 8 mm along its own
normals afterwards. That closed the shoulders, hips and knees where the body was
showing through the cloth.
Evaluation against the blueprint, same shot and box before and after:
mean deltaE 31.9 -> 21.8
palette deltaE 25.4 -> 19.7
aspect delta 1.24 -> 1.22
Also fixes a crash I introduced: binding the materials in Tick used
ConstructorHelpers::FObjectFinder, which is fatal outside a constructor - the game died
with 'FObjectFinders can't be used outside of constructors' and the render run failed
with signal 11. The binding now lives in BeginPlay and uses LoadObject.
Not fixed, and recorded: the arms and face still render neutral grey, so
M_Character_Skin itself is suspect rather than the assignment.
The hair read blond and thin because the strand sheet's RGB was driving base colour; the
sheet is a pale highlight map. Colour is now a dark constant (0.030/0.020/0.014) and the
texture supplies only alpha. Sampled in the render: hair (31, 26, 21).
Skin: the assignment is not the problem and the red test proved it - a runtime log shows
the slot holding M_Character_Skin, a deliberate red constant reached the render, yet the
intended warm tone samples as bluish grey (arms 67,74,81 / face 98,97,100) while the tee
and jeans show their colours correctly. Recorded in TODO.md with the next suspects rather
than guessed at.
The temporary material log is removed again.
The skin was never grey. A red test colour reached the render and the slot was confirmed
holding M_Character_Skin, so the material and the assignment were both fine - the body
was simply standing in building shadow and lit almost entirely by the blue skylight
(intensity 2.6). Warm skin under blue fill reads blue-grey.
Raising the sun from pitch -62 to -74, same camera:
face (99, 98, 100) -> (131, 121, 117)
arms (67, 74, 81) -> (71, 78, 85)
This touches every approved shot, so it sits on the branch for the owner's approval
rather than being folded in. Also records that the Unreal commandlet silently drops
unknown switches, which is why a --unlit-test flag never took effect.
The appearance changed in 0.2.2 and the README still described an unclothed body with
splayed fingers, which is a defect in its own right: a shot whose README section is
stale is not a review.
Shots 6, 7 and 8 now describe the clothing, the dark card hair and the raised sun, and
their approvals are reset to pending because the appearance changed. 8/8 shots verify.
The previous documentation commit dropped both rows from the status table while editing
the section text - the table then listed six shots against eight embeds. Restored, both
marked verified and pending approval.
Ticks both the shot sections and their status rows, so the table and the sections agree:
5 previously approved, these 3 approved 2026-11-09 in their clothed, haired, relit state.
Writes the 0.2.2 entry and prunes the section from TODO.md. The entry records that this
ships as a patch by the owner's decision even though it adds asset categories, which the
policy otherwise puts in a minor release - so the numbering is not mistaken for scope.
Contents: clothing as three separate skinned meshes, hair cards oriented from the scalp
normal, ComfyUI character blueprints and the render-vs-blueprint comparison, procedural
textures, materials built from scratch, the raised sun, and the walk cycle starting at
maximum stride.
Verified: 8/8 shots, all approved; mean deltaE 31.9 -> 21.8 against the blueprint; 95
tool tests. Known limitations listed rather than omitted - garments are shells over body
faces, the baked diffuse cannot be used because the mesh UVs do not map to it, and the
character has no face detail.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Hair becomes a separate skinned mesh, which is what the
character-artistskill asks for: a card system, and swappable parts as their own meshes.Tools/character_hair_build.pybuildsSM_Char_Hair: scalp cap + 18 cards in three rings, quads only, all vertices weighted tohead, UV-mapped for the strand sheet.Tools/ue_character_hair_import.pyimports it as its own skeletal mesh — joined into the body, Unreal collapses the second material slot and hair renders as skin.USkeletalMeshComponentusingSetLeaderPoseComponent, so it follows the head with no second rig.Also in this branch:
Tools/comfyui_generate.py(local ComfyUI driver with provenance sidecars) andTools/generate_character_textures.py(procedural skin pores and hair strands).Still wrong: the cards point outward so the silhouette reads as a spiked crown; the next pass orients them tangentially with a second overlapping row.
Tools/ue_character_materials.pysegfaults in the headless commandlet when editing a material graph, so the generated textures are not wired yet.