From 4359fb3e131237f12be61ad74450f909fb29d2ee Mon Sep 17 00:00:00 2001 From: zerone0x <39543393+zerone0x@users.noreply.github.com> Date: Sat, 29 Aug 2026 20:06:00 +0000 Subject: [PATCH] test: guard rig convention docs drift --- src/rig-report.js | 2 +- tests/rig-report.test.js | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/rig-report.js b/src/rig-report.js index 4da934cbd..863df7ebd 100644 --- a/src/rig-report.js +++ b/src/rig-report.js @@ -72,7 +72,7 @@ export const LIMB_GROUPS = Object.freeze([ // `schema` maps onto the avatar-manifest `skeleton` enum // (packages/avatar-schema/schema/avatar.v1.json), so the generated manifest // declares the right value instead of defaulting everything to `custom`. -const CONVENTIONS = [ +export const CONVENTIONS = [ { id: 'rpm', label: 'Ready Player Me', schema: 'rpm', test: (ctx) => ctx.meshNames.some((n) => /^Wolf3D_/i.test(n)) && ctx.joints.some((n) => /Hips$/i.test(n)), diff --git a/tests/rig-report.test.js b/tests/rig-report.test.js index 777bd9eb7..4cca79310 100644 --- a/tests/rig-report.test.js +++ b/tests/rig-report.test.js @@ -16,6 +16,7 @@ import { MIN_CANONICAL_BONES, CANONICAL_TOTAL, LIMB_GROUPS, + CONVENTIONS, } from '../src/rig-report.js'; const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); @@ -29,6 +30,17 @@ function report(name) { return analyzeGlb(load(name), { fileName: `${name}.glb` }); } +function documentedRigConventions() { + const doc = readFileSync(join(ROOT, 'docs/rig-doctor.md'), 'utf8'); + const rows = []; + for (const match of doc.matchAll(/^\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*`([^`]+)`\s*\|$/gm)) { + const convention = match[1].trim(); + if (convention === 'Convention' || /^-+$/.test(convention)) continue; + rows.push({ convention, detectedBy: match[2].trim(), schema: match[3].trim() }); + } + return rows; +} + describe('readGlbJson', () => { it('rejects a file that is not a GLB with a message a creator can act on', () => { const notGlb = new TextEncoder().encode('this is plainly not a binary glTF file'); @@ -55,6 +67,19 @@ describe('readGlbJson', () => { describe('detectConvention fingerprints', () => { const ctx = (joints, meshNames = []) => ({ joints, meshNames, json: {} }); + it('keeps documented rig conventions in sync with the detector', () => { + const implemented = CONVENTIONS.filter((c) => c.id !== 'unknown'); + const docs = documentedRigConventions(); + const implementedLabels = implemented.map((c) => c.label); + const documentedLabels = docs.map((row) => row.convention); + + expect(documentedLabels).toEqual(implementedLabels); + for (const convention of implemented) { + expect(convention.evidence, `${convention.label} evidence`).toEqual(expect.any(String)); + expect(convention.evidence.trim(), `${convention.label} evidence`).not.toBe(''); + } + }); + it('identifies an MMD rig from its Japanese PMX bone names', () => { const c = detectConvention(ctx(['センター', '上半身', '左腕', '左ひじ', '右ひざ'])); expect(c.id).toBe('mmd');