Initial public release of PSX Adventure Engine
Browser reference stack for PSX-era third-person adventure: fixed cameras, inventory puzzles, Box3D physics, host-authoritative P2P co-op, and a modular character harness. Ships the Ashgrove Precinct Level 1 investigation demo with a full cast and nine linked rooms.
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Canonical skeleton — GDD §10.2.
|
||||
*
|
||||
* Mixamo-compatible core set. The harness builds joints under these names and
|
||||
* binds skin weights to them, so every generated character plays the same
|
||||
* procedural clips (and any future shared animation library).
|
||||
*/
|
||||
|
||||
export const CANONICAL_BONES = [
|
||||
"Hips",
|
||||
"Spine",
|
||||
"Spine1",
|
||||
"Spine2",
|
||||
"Neck",
|
||||
"Head",
|
||||
"LeftShoulder",
|
||||
"LeftArm",
|
||||
"LeftForeArm",
|
||||
"LeftHand",
|
||||
"RightShoulder",
|
||||
"RightArm",
|
||||
"RightForeArm",
|
||||
"RightHand",
|
||||
"LeftUpLeg",
|
||||
"LeftLeg",
|
||||
"LeftFoot",
|
||||
"LeftToeBase",
|
||||
"RightUpLeg",
|
||||
"RightLeg",
|
||||
"RightFoot",
|
||||
"RightToeBase",
|
||||
] as const;
|
||||
|
||||
export type CanonicalBone = (typeof CANONICAL_BONES)[number];
|
||||
|
||||
/** Parent of each bone; `null` marks the skeleton root. */
|
||||
export const CANONICAL_HIERARCHY: Record<CanonicalBone, CanonicalBone | null> = {
|
||||
Hips: null,
|
||||
Spine: "Hips",
|
||||
Spine1: "Spine",
|
||||
Spine2: "Spine1",
|
||||
Neck: "Spine2",
|
||||
Head: "Neck",
|
||||
LeftShoulder: "Spine2",
|
||||
LeftArm: "LeftShoulder",
|
||||
LeftForeArm: "LeftArm",
|
||||
LeftHand: "LeftForeArm",
|
||||
RightShoulder: "Spine2",
|
||||
RightArm: "RightShoulder",
|
||||
RightForeArm: "RightArm",
|
||||
RightHand: "RightForeArm",
|
||||
LeftUpLeg: "Hips",
|
||||
LeftLeg: "LeftUpLeg",
|
||||
LeftFoot: "LeftLeg",
|
||||
LeftToeBase: "LeftFoot",
|
||||
RightUpLeg: "Hips",
|
||||
RightLeg: "RightUpLeg",
|
||||
RightFoot: "RightLeg",
|
||||
RightToeBase: "RightFoot",
|
||||
};
|
||||
|
||||
/** Bones a rigger must produce for a clip to be playable at all. */
|
||||
export const REQUIRED_BONES: readonly CanonicalBone[] = [
|
||||
"Hips",
|
||||
"Spine",
|
||||
"Head",
|
||||
"LeftArm",
|
||||
"RightArm",
|
||||
"LeftUpLeg",
|
||||
"LeftLeg",
|
||||
"LeftFoot",
|
||||
"RightUpLeg",
|
||||
"RightLeg",
|
||||
"RightFoot",
|
||||
];
|
||||
|
||||
const CANONICAL_SET: ReadonlySet<string> = new Set(CANONICAL_BONES);
|
||||
|
||||
export function isCanonicalBone(name: string): name is CanonicalBone {
|
||||
return CANONICAL_SET.has(name);
|
||||
}
|
||||
|
||||
/** Depth-first walk order, parents always before children. */
|
||||
export function canonicalWalkOrder(): CanonicalBone[] {
|
||||
const out: CanonicalBone[] = [];
|
||||
const visit = (bone: CanonicalBone) => {
|
||||
out.push(bone);
|
||||
for (const child of CANONICAL_BONES) {
|
||||
if (CANONICAL_HIERARCHY[child] === bone) visit(child);
|
||||
}
|
||||
};
|
||||
visit("Hips");
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common aliases emitted by third-party riggers, normalised to canonical names.
|
||||
* Mixamo's `mixamorig:` prefix is stripped before lookup.
|
||||
*/
|
||||
export const BONE_ALIASES: Record<string, CanonicalBone> = {
|
||||
hip: "Hips",
|
||||
pelvis: "Hips",
|
||||
root: "Hips",
|
||||
spine_01: "Spine",
|
||||
spine_02: "Spine1",
|
||||
spine_03: "Spine2",
|
||||
chest: "Spine2",
|
||||
upperchest: "Spine2",
|
||||
head_end: "Head",
|
||||
clavicle_l: "LeftShoulder",
|
||||
clavicle_r: "RightShoulder",
|
||||
upperarm_l: "LeftArm",
|
||||
upperarm_r: "RightArm",
|
||||
lowerarm_l: "LeftForeArm",
|
||||
lowerarm_r: "RightForeArm",
|
||||
hand_l: "LeftHand",
|
||||
hand_r: "RightHand",
|
||||
thigh_l: "LeftUpLeg",
|
||||
thigh_r: "RightUpLeg",
|
||||
calf_l: "LeftLeg",
|
||||
calf_r: "RightLeg",
|
||||
foot_l: "LeftFoot",
|
||||
foot_r: "RightFoot",
|
||||
ball_l: "LeftToeBase",
|
||||
ball_r: "RightToeBase",
|
||||
};
|
||||
|
||||
/** Best-effort mapping of an arbitrary rigger bone name onto the canonical set. */
|
||||
export function normaliseBoneName(raw: string): CanonicalBone | null {
|
||||
const stripped = raw.replace(/^mixamorig[:_]?/i, "").trim();
|
||||
if (isCanonicalBone(stripped)) return stripped;
|
||||
|
||||
const key = stripped.toLowerCase().replace(/[\s.-]+/g, "_");
|
||||
const alias = BONE_ALIASES[key];
|
||||
if (alias) return alias;
|
||||
|
||||
// `LeftArm` vs `leftarm` vs `Left_Arm`
|
||||
const squashed = key.replace(/_/g, "");
|
||||
for (const bone of CANONICAL_BONES) {
|
||||
if (bone.toLowerCase() === squashed) return bone;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user