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
+34 -2
View File
@@ -9,6 +9,17 @@ using Object = UnityEngine.Object;
/// <summary>Bake an upright, grounded, periodic in-place gait onto this exact bind.</summary>
public static class FinalizeAnimalWalk
{
[Serializable] public sealed class QuadrupedRecipe
{
public string assetRoot,id,rootBone,frontRightFoot,hindLeftFoot,frontLeftFoot,hindRightFoot,outputDir;
public float height,walkStride,walkLift,walkSeconds,walkSourceWeight,walkSwingFraction;
}
public static string RunQuadruped(string recipePath)
{
var r=JsonUtility.FromJson<QuadrupedRecipe>(File.ReadAllText(Path.GetFullPath(Path.Combine(Application.dataPath,"../..",recipePath))));
if(!(r.walkSwingFraction>0 && r.walkSwingFraction<=.25f))throw new ArgumentException("Four-beat swing must be at most a quarter cycle");
return RunCore(r.assetRoot,r.id,r.height,r.rootBone,new[]{r.frontRightFoot,r.hindLeftFoot,r.frontLeftFoot,r.hindRightFoot},r.walkStride,r.walkLift,r.walkSeconds,r.outputDir,true,r.walkSourceWeight,true,r.walkSwingFraction);
}
const string Root = "Assets/Bundles/Characters/Animal/RockBall";
sealed class Pose
{
@@ -48,6 +59,10 @@ public static class FinalizeAnimalWalk
public static string RunFor(string assetRoot,string actor,float height,string rootBone,string rightFoot,string leftFoot,
float strideMeters,float liftMeters,float cycleSeconds,string reportDirectory,bool calibrateBind=false,float sourceWeight=1f)
=>RunCore(assetRoot,actor,height,rootBone,new[]{rightFoot,leftFoot},strideMeters,liftMeters,cycleSeconds,reportDirectory,calibrateBind,sourceWeight,false,.2f);
static string RunCore(string assetRoot,string actor,float height,string rootBone,string[] footNames,
float strideMeters,float liftMeters,float cycleSeconds,string reportDirectory,bool calibrateBind,float sourceWeight,bool fourBeat,float swingFraction)
{
var asset=AssetDatabase.LoadAssetAtPath<GameObject>(assetRoot+"/Rigged/"+actor+"_Rigged.fbx");
var model=Object.Instantiate(asset);
@@ -57,7 +72,9 @@ public static class FinalizeAnimalWalk
if(animator!=null) animator.enabled=false;
var bones=model.GetComponentsInChildren<Transform>().Where(t=>t!=model.transform && (t.name=="Armature" || t.name.StartsWith("bone_"))).ToArray();
var root=bones.First(t=>t.name==rootBone);
var feet=new[] { bones.First(t=>t.name==rightFoot),bones.First(t=>t.name==leftFoot) };
var feet=footNames.Select(name=>bones.First(t=>t.name==name)).ToArray();
if(feet.Distinct().Count()!=feet.Length)throw new ArgumentException("Foot mappings must be distinct");
var footPositions=feet.Select(f=>f.position).ToArray();var footRotations=feet.Select(f=>f.rotation).ToArray();
var bindPose=Read(bones);
var bindRotation=root.rotation;
var bindPosition=root.position;
@@ -88,6 +105,7 @@ public static class FinalizeAnimalWalk
}
}
else root.rotation=bindRotation*Quaternion.Inverse(reference)*root.rotation;
if(fourBeat)root.rotation=bindRotation;
root.position=new Vector3(bindPosition.x,root.position.y,bindPosition.z);
root.position+=Vector3.up*(bindBottom-Bottom(model));
poses[f]=Read(bones);
@@ -117,10 +135,24 @@ public static class FinalizeAnimalWalk
p.q[i]=a.q[i]*Quaternion.Slerp(Quaternion.identity,Quaternion.Inverse(end.q[i])*begin.q[i],blend);
}
Write(bones,p);
if(fourBeat)root.rotation=bindRotation;
// Author an explicit alternating short step in the OFFLINE clip bake.
// Bone mapping and dimensions belong to this character's recipe.
for(int foot=0;foot<feet.Length;foot++)
{
if(fourBeat)
{
// Four separate swing windows leave the other three paws supporting.
float u=Mathf.Repeat(t-foot*.25f,1);
bool swinging=u<swingFraction;
float advance=swinging ? Mathf.SmoothStep(-.5f,.5f,u/swingFraction) : .5f-(u-swingFraction)/(1-swingFraction);
float lift=swinging ? Mathf.Pow(Mathf.Sin(Mathf.PI*u/swingFraction),2) : 0;
for(int b=0;b<bones.Length;b++)if(bones[b]!=feet[foot] && bones[b].IsChildOf(feet[foot]))
{bones[b].localPosition=bindPose.p[b];bones[b].localRotation=bindPose.q[b];bones[b].localScale=bindPose.s[b];}
feet[foot].position=footPositions[foot]+(Vector3.back*(advance*strideMeters)+Vector3.up*(lift*liftMeters))*metersToBind;
feet[foot].rotation=footRotations[foot];
continue;
}
float phase=2*Mathf.PI*t+foot*Mathf.PI;
float swing=Mathf.Max(0,Mathf.Cos(phase));
feet[foot].position+=Vector3.back*(Mathf.Sin(phase)*strideMeters*.5f*metersToBind)
@@ -162,7 +194,7 @@ public static class FinalizeAnimalWalk
AnimationUtility.SetAnimationClipSettings(clip,settings);
EditorUtility.SetDirty(clip);
AssetDatabase.SaveAssets();
string report=$"frames={first}..{last} length={clip.length:F4} phaseRms={error:F3} sourceInitialRootTilt={referenceTilt:F3}; allBoneBindCalibration={calibrateBind} sourceWeight={sourceWeight:F3}; root bind calibration + in-place XZ + per-frame grounded skin + periodic closure; authored short-foot stride={strideMeters:F3}m lift={liftMeters:F3}m cycle={cycleSeconds:F3}s";
string report=$"frames={first}..{last} length={clip.length:F4} phaseRms={error:F3} sourceInitialRootTilt={referenceTilt:F3}; allBoneBindCalibration={calibrateBind} sourceWeight={sourceWeight:F3}; root bind calibration + in-place XZ + per-frame grounded skin + periodic closure; feet={feet.Length} fourBeat={fourBeat} swingFraction={swingFraction:F3}; authored short-foot stride={strideMeters:F3}m lift={liftMeters:F3}m cycle={cycleSeconds:F3}s";
string outPath=Path.GetFullPath(Path.Combine(Application.dataPath,"../../",reportDirectory,"walk_processing.txt"));
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
File.WriteAllText(outPath,report);