Feat/outline thickness and tp weapon hold #22

Merged
Dotts merged 43 commits from feat/outline-thickness-and-tp-weapon-hold into main 2026-07-27 23:22:53 -07:00
Showing only changes of commit 4559a1adc3 - Show all commits
@@ -97,6 +97,42 @@ guesses.
tracks onto them (`keep_anim_armature`), the AnimationPlayer overwrites the tracks onto them (`keep_anim_armature`), the AnimationPlayer overwrites the
spring solver every frame. spring solver every frame.
## Worked example: why the two shipped characters differ so much
Both are in `assets/characters/skins/`. Compare their sidecars:
| | Taila | Miku |
|---|---|---|
| source had a skeleton | yes | **no — 5 meshes, 0 joints** |
| `weights_authored` | true | **false** |
| cloth chains | 35 (127 bones) | **0** |
| twist bones | 8 | **0** |
| meshes shipped | 18 | **1** |
Miku's source (`assets/characters/incoming/miku_test.glb`) is an unrigged mesh,
so she went through `autorig.py`: joined to one mesh, rebound by nearest-bone
weighting, no cloth chains. Her twin tails and skirt are dead geometry that
cannot move, and `SkinLegRepair` runs destructively on her every spawn.
Nothing downstream can recover this. **The single highest-leverage decision in
this whole pipeline is choosing a source model that already has a skeleton with
skirt and hair bones.** Everything else is recoverable; this is not.
A quick check on any candidate, without Blender:
```python
import json, struct
with open(path,'rb') as f:
f.read(12); clen,_=struct.unpack('<II',f.read(8))
j=json.loads(f.read(clen))
nodes=[n.get('name','') for n in j['nodes']]
joints=[nodes[i] for s in j.get('skins',[]) for i in s['joints']]
print(len(j['meshes']), 'meshes', len(joints), 'joints')
print([n for n in joints if any(t in n.lower() for t in ('hair','skirt','tail','ribbon'))])
```
Several meshes, 50+ joints, and a non-empty cosmetic list means a good source.
## Checking a source model before importing ## Checking a source model before importing
```bash ```bash