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:
ryanfitzpatrickio
2026-07-31 06:32:43 -05:00
commit 8a96ede9f2
181 changed files with 25807 additions and 0 deletions
+143
View File
@@ -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;
}