diff --git a/README.md b/README.md index 78632a2..6e08379 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Every documentation page is one Markdown file under `content/` (frontmatter + bo | --- | --- | --- | | `/` | `content/index.md` | Toolchain overview, package table, licensing boundaries | | `/docs/` | `content/docs/index.md` | Docs hub: the packages in pipeline order (the list is generated from each page's frontmatter) | +| `/docs/capabilities/` | `content/docs/capabilities.md` | Input, producer, controls, output and playback matrix for 2D, depth-driven 2.5D and full 3D | | `/docs/getting-started/` | `content/docs/getting-started.md` | The three-step pipeline and the 10-step no-GPU walkthrough from [`rig-c/getting-started`](https://github.com/rig-c/getting-started) | | `/docs/parts/` … `/docs/adapters/` | `content/docs/.md` | One concept page per package: what it is, when to use it, what it solves, what you get, how it works, key concepts, and links into the repo for detail | | `/examples/` | `content/examples.md` | Reproducible examples (`rig-c/examples`, `rig-c/trailer`) | @@ -61,7 +62,7 @@ The landing HTML gives each adapter version its own escaped `` item separa ## Link checks -`bun run verify` checks links in the twelve generated pages, their Markdown mirrors, `llms.txt` and each page section of `llms-full.txt`. The same check runs alone with `bun run check:links`. Build the output first: standalone checks refuse a nonexistent output directory or any missing expected page HTML, Markdown mirror, `llms.txt` or `llms-full.txt`, naming the missing input and exiting 1. Expected documents must be regular files. It reads the actual element IDs from generated HTML, including IDs on non-heading elements; it does not guess heading slugs. Markdown is rendered by Bun to extract inline, reference, autolinks and raw HTML links. Code examples and HTML comments are not links. +`bun run verify` checks links in all registered generated pages, their Markdown mirrors, `llms.txt` and each page section of `llms-full.txt`. The same check runs alone with `bun run check:links`. Build the output first: standalone checks refuse a nonexistent output directory or any missing expected page HTML, Markdown mirror, `llms.txt` or `llms-full.txt`, naming the missing input and exiting 1. Expected documents must be regular files. It reads the actual element IDs from generated HTML, including IDs on non-heading elements; it does not guess heading slugs. Markdown is rendered by Bun to extract inline, reference, autolinks and raw HTML links. Code examples and HTML comments are not links. Same-page, relative, cross-page and absolute same-origin links are resolved in the source page's URL context. A link to a known `index.md#fragment` uses that page's generated HTML IDs. Each `llms-full.txt` section keeps its original page context, so another page's heading cannot satisfy its same-page fragment. Attributes decode semicolon-terminated named and numeric HTML entities once; URL paths and fragments decode percent escapes once, including Unicode. Empty `#` and `#top` navigate to the top. Missing files, missing IDs, invalid URL escapes and fragments on unsupported non-HTML files fail with the source and original href. diff --git a/content/docs/capabilities.md b/content/docs/capabilities.md new file mode 100644 index 0000000..cfd6f58 --- /dev/null +++ b/content/docs/capabilities.md @@ -0,0 +1,49 @@ +--- +navLabel: Capabilities +title: rig-c capabilities — 2D, depth-driven 2.5D and full 3D +description: Compare rig-c routes by input, released producer, turn controls, output and verified playback, from cut-out 2D to depth-driven rotation and the boundary with full 3D. +keywords: rig-c capabilities, 2D rigging, depth map, 2.5D rotation, Spine 4.3, Three.js +repoUrl: https://github.com/rig-c +summaryForLlms: Capability matrix: cut-out 2D, Core depth-driven rotation, the released Parts producer and the standalone preview, with inputs, controls, outputs, playback evidence and full-3D limits. +--- +# 2D, depth-driven 2.5D, and full 3D + +A painted rig can turn in depth without becoming a complete 3D character. The route matters: Core can project actual rotation of a declared surface, Parts chooses a particular projection, and a Three.js scene can host the result. + +## Capability matrix + +These are release-scoped capabilities, checked on **2026-10-27**. **VERIFIED** names an existing playback record and its pins; **UNVERIFIED** means that combination has no playback evidence here; **UNSUPPORTED** means the named path does not provide it. A released producer or a green compiler gate alone is not a playback result. On narrow screens, scroll the table horizontally. + +| Route | Required input | Producing package / version | Controls or range | Output | Verified playback | +| --- | --- | --- | --- | --- | --- | +| Cut-out 2D | Painting, layered PSD or See-through layers, corrected config; or authored rig/motion specs and part PNGs | [Parts 1.68.0](/docs/parts/) → [Core 2.74.0](/docs/core/) | Bone transforms, weighted meshes, authored motion; range depends on art and rig | Native model + Spine 4.3 export | **VERIFIED:** [recorded Parts idle](/docs/parts/#3-a-drafted-rig-that-idles) in Play 1.12.0. This older fixture does not establish every current producer/player pairing. | +| 2D body retarget, with optional depth cues | Cleaned body-18 joints, built rig, explicit bone map; depth-derived ordering also needs depth data and target bindings | [Pose 1.2.1](/docs/pose/) → Core | `clean --depth walk\|run`; opt-in `retarget --draw-order depth`, `--leg-scale`, `--plant-feet`, `--stance`, `--depth-scale` | Motion spec, report, gated native model + Spine export | **VERIFIED:** [swing](/docs/pose/#how-the-swing-reached-character-1), Pose 0.35.0 / Core 2.61.0 in [Play 1.12.0](/docs/play/). **UNVERIFIED:** these 1.2.1 opt-ins in that playback fixture. | +| Core depth-driven 2.5D rotation | Authored specs; mesh with a drawing-sized greyscale depth sheet, explicit `near` / `zScale`, or a declared cylinder radius; feature depths for bone `derive` | [Core 2.74.0](https://github.com/rig-c/core/releases/tag/v2.74.0) | Authored `yaw` / `pitch` degrees and axis; default `narrowing:true`; mesh fold ceiling and feature edge-on limits are construction-specific | Compiled 2D deform / bone keys in native model + Spine export | **UNVERIFIED:** this release's depth-sheet route across Play, adapters and official runtimes. See the [projection contract](#what-the-turn-means); the standalone preview is a different path. | +| Released Parts depth-sheet head turn | Registered layer sheets; opt-in `config.depth` polarity / scale; `config.turn.roles` with a `surface`, plus `unit` / `rear` parts as needed | [Parts 1.68.0](https://github.com/rig-c/parts/releases/tag/v1.68.0) → Core | `yaw_dial` / `pitch_dial`, `turn` / `tilt`; default band ±15° yaw / ±12° pitch; **`narrowing:false`** | Rig/motion specs and depth-sheet companion → native model + Spine export | **UNVERIFIED:** this producer's depth output with Play, adapters or official runtimes. Release scope is documented in the [Parts contract](/docs/parts/#depth-driven-head-turns). | +| Standalone depth preview | Its shipped precompiled scene, textures and depth samples, prepared from one painting and its depth map | [Frozen preview bundle](/examples/#preview-depth-driven-head-turn), separate from npm producers | Pointer: nominal ±15° yaw / ±12° pitch at gain 1; gain, pivot, gaze and rear counter controls | Preview-specific scene JSON / compressed geometry and browser bundle | **VERIFIED:** the [standalone WebGL demo](/demos/head-turn/) documented with that bundle. **UNSUPPORTED:** loading this scene format directly as a Play model or Spine export. | +| Full 3D character reconstruction / toon relighting | Would require unseen surfaces, volume and lighting/material information beyond these painted inputs | **UNSUPPORTED** by the routes above | No unrestricted orbit or relight control in these producers | No complete 3D character model from these paths | **UNSUPPORTED:** treating the Three.js adapter as a reconstruction producer. Its supported scene placement is described [below](#pick-the-playback-path). | + +## What the turn means + +**Core evaluates a rotation, then projects it into the picture.** Its [mesh transform and bone `derive` contracts](https://github.com/rig-c/core/blob/78269be5dca759ed7a3cf4597fae2068700906cf/docs/AUTHORING.md) include both `u·(cos θ − 1)` narrowing and `−z·sin θ` depth shift. A depth sheet supplies each mesh vertex's `z`; a radius supplies a cylinder's `z`. Bone `derive` can also key the separate foreshortening scale. This is more than sliding each layer at one ordering depth. + +**Parts 1.68.0 deliberately keeps the front-view proportions.** Its [shipped producer](https://github.com/rig-c/parts/blob/89a7671229d155976fc8b5e87ffd6459301ff7b9/src/turn.ts) sets `narrowing:false` on surface and feature displacement: sine-depth movement remains, cosine narrowing is omitted. The standalone preview also uses sine-depth displacement, but has its own dense geometry and controller. Neither describes every Core turn. + +Depth supplies no unseen drawing. Wider Parts bands require a declared alternate face drawing; a Core surface's fold ceiling is a geometric limit, not a guarantee that the face looks right. The [face construction guide](https://github.com/rig-c/core/blob/78269be5dca759ed7a3cf4597fae2068700906cf/docs/FACE.md) explains why larger turns can need different eye, cheek and neck art. Pose's [depth and feet options](/docs/pose/#depth-projection-and-feet) estimate constrained cues from 2D joints; they do not reconstruct a rotating 3D body. + +## Pick the playback path + +- **Native model:** [Play](/docs/play/) loads `skeleton.model.json` (`rigc-compiled/3`) and image pages. It does not load the Spine export. [Engine adapters](/docs/adapters/) consume Play's posed frames. The recorded walk verifies adapters **0.1.1** with Play **1.10.0**; it does not verify new depth producers with today's releases. +- **Spine export:** `skeleton.json` + `skeleton.atlas` and image pages go to an official Spine runtime or the editor. That is a separate integration; matching file names do not establish feature parity. Use the existing [licensing guidance](/docs/licensing/#spine-terms-they-follow-what-plays-the-output) for each path. +- **Three.js host:** [Three 0.1.1's contract](https://github.com/rig-c/adapters/blob/26af17776d90f56af1ff0e153e4f4b95ec292bdc/packages/three/README.md) permits translating, rotating and scaling a flat Group in perspective or orthographic scenes, with ordinary 3D occlusion. Its slot vertices remain `(x, y, 0)` and its shader has no lighting: painted shading and tint are not reconstructed normals or toon relighting. + + diff --git a/content/docs/index.md b/content/docs/index.md index fe05c2a..40231aa 100644 --- a/content/docs/index.md +++ b/content/docs/index.md @@ -12,6 +12,8 @@ rig-c is a set of small packages that hand files to each other: a painting split ## Start here +Comparing 2D rigging, depth-driven turns and 3D scene placement? Start with the [capability matrix](/docs/capabilities/): inputs, released producers, controls, outputs and playback evidence in one place. + {{docs-map}} ## How the pieces fit diff --git a/content/examples.md b/content/examples.md index 0fdfc32..efc8b36 100644 --- a/content/examples.md +++ b/content/examples.md @@ -85,6 +85,8 @@ Licences for all of these: [Licensing](/docs/licensing/). - **Small features move as one piece.** Eyes, nose and ears each take a single depth value, so they slide without warping. The eyes also look at the pointer, inside the eyelids' outline. - **Hair keeps its layers.** The fringe moves with the face, and the back hair shifts slightly the other way (10 % by default) so the head reads as having a back. +For the available routes, controls and playback evidence, compare [2D, depth-driven 2.5D and full 3D](/docs/capabilities/). + **The released producer.** [`@rig-c/parts` 1.68.0](https://github.com/rig-c/parts/releases/tag/v1.68.0) ships per-layer depth input and a 2.5D head-turn producer. Enable `config.depth` with explicit depth polarity and world-unit scale, then declare `config.turn.roles`: `surface` for depth-sampled grid surfaces, `unit` for features that move together, and `rear` for the back layer. The rig stage writes `turn` and `tilt` animations driven by `yaw_dial` and `pitch_dial`; Core projects the depth samples. See [Parts: depth-driven head turns](/docs/parts/#depth-driven-head-turns) and the [1.68.0 authoring contract](https://github.com/rig-c/parts/blob/89a7671229d155976fc8b5e87ffd6459301ff7b9/docs/AUTHORING.md#3-configjson-field-by-field). The demo above is a separate, precompiled implementation with its own pointer and gaze controller. It has not been rebuilt here with the released producer, and the producer's grid and controller behavior are not claimed to match it. The demo's original preview caption belongs to that earlier snapshot; the release status here describes the package. diff --git a/src/content.ts b/src/content.ts index 9cf34e6..bd90221 100644 --- a/src/content.ts +++ b/src/content.ts @@ -28,6 +28,7 @@ export interface Page { const SOURCES: readonly { file: string; route: string; section: Section }[] = [ { file: 'index.md', route: '/', section: 'home' }, { file: 'docs/index.md', route: '/docs/', section: 'docs' }, + { file: 'docs/capabilities.md', route: '/docs/capabilities/', section: 'docs' }, { file: 'docs/getting-started.md', route: '/docs/getting-started/', section: 'docs' }, { file: 'docs/parts.md', route: '/docs/parts/', section: 'docs' }, { file: 'docs/pose.md', route: '/docs/pose/', section: 'docs' }, diff --git a/src/links.test.ts b/src/links.test.ts index 6edf365..5303f18 100644 --- a/src/links.test.ts +++ b/src/links.test.ts @@ -6,14 +6,16 @@ import { htmlRel, mdRoute, PAGES, SITE_ORIGIN } from './content.ts'; import { checkInternalLinks, decodeAttribute, inspectHtml, markdownDocument, siteDocuments } from './links.ts'; const temporary: string[] = []; +// Core's links exercise cross-page anchors, independent of docs navigation order. +const sectionId = (route: string, index: number) => route === '/docs/core/' ? 'section-core' : `section-${index}`; function fixture() { const root = mkdtempSync(join(realpathSync(tmpdir()), 'site-links-')); temporary.push(root); for (const [index, page] of PAGES.entries()) { const html = join(root, htmlRel(page.route)); mkdirSync(join(html, '..'), { recursive: true }); - writeFileSync(html, Bun.markdown.html(`# Page ${index}\n\n## Section ${index}`, { headings: { ids: true } })); - writeFileSync(join(root, mdRoute(page.route)), `# Page ${index}\n\n[Section](#section-${index})`); + writeFileSync(html, Bun.markdown.html(`# Page ${index}\n\n## ${sectionId(page.route, index)}`, { headings: { ids: true } })); + writeFileSync(join(root, mdRoute(page.route)), `# Page ${index}\n\n[Section](#${sectionId(page.route, index)})`); } llms(root); return root; @@ -68,15 +70,15 @@ test('missing file, malformed percent, moved routes and non-HTML fragment target test('Markdown links use rendered inline, reference, autolink and raw HTML; code/comments are not links', () => { const root = fixture(); - const md = `[inline](/docs/core/#section-5) [reference][target]\n\n[target]: /docs/core/index.md#section-5\n\n<${SITE_ORIGIN}/docs/core/#section-5>\n\nraw\n\n\`[code](/absent/)\`\n\n\`\`\`html\ncode\n\`\`\`\n\n`; + const md = `[inline](/docs/core/#section-core) [reference][target]\n\n[target]: /docs/core/index.md#section-core\n\n<${SITE_ORIGIN}/docs/core/#section-core>\n\nraw\n\n\`[code](/absent/)\`\n\n\`\`\`html\ncode\n\`\`\`\n\n`; expect(checkInternalLinks(root, [markdownDocument('mirror.md', '/docs/core/index.md', md)])).toEqual([]); expect(checkInternalLinks(root, [markdownDocument('mirror.md', '/docs/core/index.md', '[bad](#missing)')])[0]).toContain('[mirror.md]'); }); function llms(root: string) { - const full = PAGES.map((page, i) => `================================================================================\nURL: ${SITE_ORIGIN}${page.route}\nMarkdown: ${SITE_ORIGIN}${mdRoute(page.route)}\nRepository: https://github.com/rig-c/core\n================================================================================\n\n# Page ${i}\n\n[Own section](#section-${i})\n`).join('\n'); + const full = PAGES.map((page, i) => `================================================================================\nURL: ${SITE_ORIGIN}${page.route}\nMarkdown: ${SITE_ORIGIN}${mdRoute(page.route)}\nRepository: https://github.com/rig-c/core\n================================================================================\n\n# Page ${i}\n\n[Own section](#${sectionId(page.route, i)})\n`).join('\n'); writeFileSync(join(root, 'llms-full.txt'), full); - writeFileSync(join(root, 'llms.txt'), `[Core](${SITE_ORIGIN}/docs/core/index.md#section-5)`); + writeFileSync(join(root, 'llms.txt'), `[Core](${SITE_ORIGIN}/docs/core/index.md#section-core)`); } test('llms-full resolves every same-page fragment in original page context; LLM-only defect fails', () => { @@ -84,7 +86,7 @@ test('llms-full resolves every same-page fragment in original page context; LLM- llms(root); expect(checkInternalLinks(root, siteDocuments(root))).toEqual([]); const fullPath = join(root, 'llms-full.txt'); - writeFileSync(fullPath, readFileSync(fullPath, 'utf8').replace('[Own section](#section-5)', '[Own section](#section-0)')); + writeFileSync(fullPath, readFileSync(fullPath, 'utf8').replace('[Own section](#section-core)', '[Own section](#section-0)')); const errors = checkInternalLinks(root, siteDocuments(root)); expect(errors).toHaveLength(1); expect(errors[0]).toContain('[llms-full.txt (/docs/core/)]');