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: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<slug>.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`) |
Expand Down Expand Up @@ -61,7 +62,7 @@ The landing HTML gives each adapter version its own escaped `<code>` 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.

Expand Down
49 changes: 49 additions & 0 deletions content/docs/capabilities.md
Original file line number Diff line number Diff line change
@@ -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.

<!-- SOURCES (not published)
- Release snapshot from fresh npm latest manifests + tarballs, with gitHead: rig-c 2.74.0 78269be5dca759ed7a3cf4597fae2068700906cf; Parts 1.68.0 89a7671229d155976fc8b5e87ffd6459301ff7b9; Pose 1.2.1 35d4d140fa45410051117251c9e55a9fb36f4920. These are capability pins, not dynamic latest badges or retimed demonstration recordings.
- Core AUTHORING at that gitHead §§3.4.5 (drawing-sized sheets / polarity / zScale / setup-world projection), 4.5.1 (derive shift and separate scale), 4.12 (yaw/pitch, narrowing default / false); shipped src/deformgen.ts:428-521 and src/depth.ts:214-215; FACE §§1,2.2,8,11: rotation, ceilings and artwork-dependent turn limits. No global safe-angle claim.
- Parts AUTHORING turn field and Per-layer depth sheets; src/turn.ts buildDepth25DTurn:1458-1770 uses depth:true,narrowing:false and separate dials; CHANGELOG 1.68.0 #774/#777. Legacy scalar ordering-depth is not the per-pixel sheet. No claim that latest output played end to end here.
- Pose REFERENCE at gitHead 35d4d14: derived depth, draw order bindings/layout, foreshortening/planted feet and depth-scale. Existing docs/pose scope and historic swing pins preserved.
- Verified playback means the existing source-bound site records: parts idle (Play 1.12.0); pose swing (Pose 0.35.0 / Core 2.61.0 / Play 1.12.0); adapters walk (host adapters 0.1.1 / Play 1.10.0). No new package-runtime playback measurement in this docs card.
- Standalone bundle README + app.js and existing examples source: nominal 15/12 degrees, gain=1; depth samples / pivot / gaze / rear counter. Bundle hash pinned by src/head-turn.ts and unchanged. Demo has no generic package model loader/exporter.
- Three npm 0.1.1 README Native drawing and scene placement, shipped dist/index.js:271-272 adds z=0 to posed 2D positions; shader without lighting. Actual native scene placement is supported, full character reconstruction is not supplied by that implementation.
- Native format: published Play 1.20.0 README Install; licensing language is delegated to existing unchanged site guidance.
-->
2 changes: 2 additions & 0 deletions content/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions content/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
14 changes: 8 additions & 6 deletions src/links.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -68,23 +70,23 @@ 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\n<a href="/docs/core/#section-5">raw</a>\n\n\`[code](/absent/)\`\n\n\`\`\`html\n<a href="/absent/">code</a>\n\`\`\`\n\n<!-- <a href="/absent/">comment</a> -->`;
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\n<a href="/docs/core/#section-core">raw</a>\n\n\`[code](/absent/)\`\n\n\`\`\`html\n<a href="/absent/">code</a>\n\`\`\`\n\n<!-- <a href="/absent/">comment</a> -->`;
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', () => {
const root = fixture();
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/)]');
Expand Down
Loading