Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ jobs:
- run: bun install --frozen-lockfile
- run: bun run build
- run: bun run build:registry
- run: bun run games:clone
env:
GAMES_CLONE_TOKEN: ${{ secrets.GAMES_CLONE_TOKEN }}
- run: bun run check-types
- run: bun test packages apps/dev scripts

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ jobs:
run: bun run stage-skills
- run: bun run check-artifacts
- run: bun run build
- run: bun run games:clone
env:
GAMES_CLONE_TOKEN: ${{ secrets.GAMES_CLONE_TOKEN }}
- run: bun run check-types
- run: bun test packages Games
- name: Publish unpublished packages in dependency order
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ between (`--json` for structured output).

## [Unreleased]

### Fixed

- Correct foot-IK endpoint recomputation after hip rotation; verify reachable targets and clamping without changing bone lengths.
- Restore release CI prerequisites and make catalog-loader tests independent of the external Games checkout.

<!--
Every PR that changes `packages/*/src` records its consumer-facing change here, so
the next release's notes are complete by construction. Add a bullet under the right
Expand Down
14 changes: 8 additions & 6 deletions packages/editor/src/mcp/loadGameCatalogs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ describe("loadGameCatalogs", () => {
expect(loaded.catalogs).toEqual([]);
});

test("tower-guard editorCatalogs export decodes with tower schema entries", async () => {
const loaded = await loadGameCatalogs("tower-guard");
test("loads an exported catalog factory with its schema and entries", async () => {
const loaded = await loadGameCatalogs("../packages/editor/testFixtures/catalog-game");
expect(loaded.ok).toBe(true);
if (!loaded.ok) throw new Error(`expected load success: ${loaded.errors.map((e) => e.message).join("; ")}`);
expect(loaded.catalogs.some((catalog) => catalog.id === "towers")).toBe(true);
const towers = loaded.catalogs.find((catalog) => catalog.id === "towers")!;
expect(towers.entries.length).toBeGreaterThan(0);
expect(towers.schema.fields.some((field) => field.key === "damage")).toBe(true);
expect(loaded.catalogs).toEqual([{
id: "towers",
label: "Towers",
schema: { fields: [{ key: "damage", type: "number", default: 1 }] },
entries: [{ id: "arrow", label: "Arrow tower", meta: { damage: 8 } }],
}]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function editorCatalogs() {
return [{
id: "towers",
label: "Towers",
schema: { fields: [{ key: "damage", type: "number", default: 1 }] },
entries: [{ id: "arrow", label: "Arrow tower", meta: { damage: 8 } }],
}];
}
20 changes: 17 additions & 3 deletions packages/shell/src/render/useFootIk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ describe("applyFootIk", () => {
expect(model.url).toContain("kaykit-adventurers");
});

test("solves a configured foot chain against the scene raycast", () => {
test.each([
{ name: "reaches a reachable terrain target", target: [0.15, 0.2, 0.1] as const },
{ name: "clamps an unreachable terrain target to the leg length", target: [0.15, -0.2, 0] as const },
])("$name without stretching either bone", ({ target }) => {
const scene = new THREE.Object3D();
const root = new THREE.Object3D();
root.name = "thigh";
Expand Down Expand Up @@ -49,7 +52,7 @@ describe("applyFootIk", () => {
damageEligible: false,
blocks: true,
distance: 0.75,
point: [0.15, -0.2, 0],
point: target,
normal: [0.45, 0.89, 0],
};
},
Expand All @@ -58,7 +61,18 @@ describe("applyFootIk", () => {

expect(grounded).toBe(true);
expect(input).toMatchObject({ direction: [0, -1, 0], maxDistance: 2, filter: { terrain: true } });
expect(tip.getWorldPosition(new THREE.Vector3()).y).toBeCloseTo(-0.2, 3);
const rootPosition = root.getWorldPosition(new THREE.Vector3());
const midPosition = mid.getWorldPosition(new THREE.Vector3());
const tipPosition = tip.getWorldPosition(new THREE.Vector3());
const targetPosition = new THREE.Vector3(...target);
const direction = targetPosition.clone().sub(rootPosition);
const expectedTip = rootPosition.clone().add(direction.clone().normalize().multiplyScalar(Math.min(1, direction.length())));
expect(tipPosition.distanceTo(expectedTip)).toBeLessThan(1e-6);
expect(rootPosition.toArray()).toEqual([0, 1, 0]);
expect(rootPosition.distanceTo(midPosition)).toBeCloseTo(0.5, 6);
expect(midPosition.distanceTo(tipPosition)).toBeCloseTo(0.5, 6);
expect(mid.position.toArray()).toEqual([0, -0.5, 0]);
expect(tip.position.toArray()).toEqual([0, -0.5, 0]);
});

test("does not mutate a rig when the ray misses", () => {
Expand Down
4 changes: 3 additions & 1 deletion packages/shell/src/render/useFootIk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ export function applyFootIk(
aimSegment(root, rootPosition, midPosition, output.mid, weight);
root.updateMatrixWorld(true);
const solvedMid = mid.getWorldPosition(new THREE.Vector3());
aimSegment(mid, solvedMid, tipPosition, output.tip, weight);
// Rotating the root also moves the tip; aim the shin from that updated pose.
const currentTip = tip.getWorldPosition(new THREE.Vector3());
aimSegment(mid, solvedMid, currentTip, output.tip, weight);
}
if (config.lookAt !== undefined && cameraTarget !== undefined) {
const bone = scene.getObjectByName(config.lookAt.bone);
Expand Down
Loading