Add formal enemies, remaining formal partners and synthesized UI SFX

- UI SFX v3: procedurally synthesized one-shots (Tools/synth_ui_sfx.py) wired to
  buttons, rewards, errors and red dots via UiSfx, gated by the SFX setting.
- Five formal enemies (Hornlet, Dashlet, Shellhorn, HornCaptain, GreatHorn):
  Hunyuan 50k -> 2000-tri XPbr, DGXA TokenRig, Idle/Walk/Attack, nested per type
  in PrototypeArena; PrototypeBattle.Enemy gains presentation-only Moving/Strikes.
- Formal partners for all ten pets in the arena and portraits, including new
  VineSprite, ThunderLeopard and TideWhale.
- Pipeline fixes: reject local TokenRig fallback, drop loose mesh islands,
  configurable Unity run_script timeout, companion static stage.
- Also includes concurrent work from another tool in this working tree
  (FlameLion, enemy combat FX, challenge mode, home explorer, server updates).
- Resources registered in Doc/ResDesc_*.md; verified on isolated :18081.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
udx
2026-10-21 12:23:55 +08:00
co-authored by Claude Opus 5.5
parent 6c64210331
commit 89e0fa246f
780 changed files with 7117051 additions and 15906 deletions
+27
View File
@@ -157,6 +157,33 @@ def main():
# recipe-defined geodesic masks can bind that appendage to an existing
# stable bone without changing geometry, UVs or the animation skeleton.
override_reports=[]
# Explicit source-space regions can bind a rigid carapace without altering
# geometry/UVs. The recipe must be fitted to this character's inspected mesh.
if args.weight_overrides:
rules=json.loads(args.weight_overrides.read_text(encoding='utf-8-sig')).get('skinRegions',[])
extent=max(p.y for p in original_points)-min(p.y for p in original_points)
points=[p/extent for p in original_points]
for rule in rules:
if rule['bone'] not in arm.data.bones:raise ValueError('Region bone missing')
lo,hi=rule['min'],rule['max'];feather=rule.get('feather',0)
if feather<0 or any(lo[c]>=hi[c] for c in range(3)):raise ValueError('Invalid rigid region')
target=low.vertex_groups[rule['bone']];affected=[];full=[]
for i,p in enumerate(points):
distance=max(max(lo[c]-p[c],0,p[c]-hi[c]) for c in range(3))
if distance>feather:continue
weight=1 if distance==0 else 1-distance/feather
weight=weight*weight*(3-2*weight)
previous={g.group:g.weight for g in low.data.vertices[i].groups}
for group,value in previous.items():low.vertex_groups[group].add([i],value*(1-weight),'REPLACE')
target.add([i],previous.get(target.index,0)*(1-weight)+weight,'REPLACE')
affected.append(i)
if distance==0:full.append(i)
if not full:raise ValueError('Rigid region selected no vertices')
override_reports.append(dict(kind='source_space_region',bone=rule['bone'],affected=len(affected),full_weight_vertices=len(full),
normalized_min=[min(points[i][c] for i in affected) for c in range(3)],
normalized_max=[max(points[i][c] for i in affected) for c in range(3)]))
# More precise geodesic appendage overrides run after broad spatial regions.
if args.weight_overrides:
overrides=json.loads(args.weight_overrides.read_text(encoding='utf-8-sig')).get('skinOverrides',[])
extent=max(p.y for p in original_points)-min(p.y for p in original_points)