BlueGit

golden-age paperdoll-renderer

design: yaw 0 front follows glTF +Z convention for all models (Tim t991u)

timfa Grok Bot · pushed by timfa

Authored 2026-11-07 · 7 days ago

Hash
ab1372ac49a73071d675ee40740cb4e1d77a9bfb
Parent
63611249
Pipeline
Passed
1 file changed +9 −4
design.md +9 −4
@@ -106,7 +106,7 @@ Paths use `/` and stay inside the project directory. Save writes/fsyncs a tempor
106 106
107 107 Required views: pitches −60, −30, 0, +30, +60 degrees (t955u; originally −30/0/+30); yaw 0, 30, 60, ..., 330 degrees. **Proposal:** yaw zero means the standardized character front faces the camera, world +Y is up, and positive yaw is defined by a documented right-hand convention. Distinguish camera elevation from model rotation: use orbiting orthographic camera around fixed character origin. Golden orientation fixture must show labelled front/back/left/right to remove ambiguity before production export.
108 108
109 **Implemented fixed framing (Tim t952u):** orthographic projection, +Y world up, yaw 0 camera on −Z looking toward the character; positive yaw follows the existing orbit convention. The **orbit pivot** is independent of the vertical image center: target is `(0, pivot_y/1000, 0)` with camera distance **10**. `pivot_y_milli = None` resolves the unique rest-pose neck joint (name tokens such as `Neck`, `mixamorigNeck`, `Bip001 Neck`, `CC_Base_Neck`); missing/ambiguous necks fall back to `center_y_milli` with a UI diagnostic; an explicit `Some` value is a manual override. Vertical world extent is `height_milli/1000`, horizontal extent vertical × target aspect. Orthographic Y translation `(pivot − center) / half_height` keeps zero-pitch image framing (feet / image center) stable when the pivot is raised to the neck. Near **0.05**, far **40** are shared absolutely across all layers, frames and views. Body, equipment layered preview, fixed live frame and production `camera()` all share `camera::resolve` + `camera::matrices`. No per-frame/per-equipment auto-framing or depth normalization. Source root motion is retained as imported (no extraction). User adjusts height/center/pivot in the body view; automatic animation envelopes are not promised.
109 **Implemented fixed framing (Tim t952u):** orthographic projection, +Y world up, yaw 0 camera on +Z looking toward the character's front (glTF convention, see Tim t991u decision); positive yaw follows the existing orbit convention. The **orbit pivot** is independent of the vertical image center: target is `(0, pivot_y/1000, 0)` with camera distance **10**. `pivot_y_milli = None` resolves the unique rest-pose neck joint (name tokens such as `Neck`, `mixamorigNeck`, `Bip001 Neck`, `CC_Base_Neck`); missing/ambiguous necks fall back to `center_y_milli` with a UI diagnostic; an explicit `Some` value is a manual override. Vertical world extent is `height_milli/1000`, horizontal extent vertical × target aspect. Orthographic Y translation `(pivot − center) / half_height` keeps zero-pitch image framing (feet / image center) stable when the pivot is raised to the neck. Near **0.05**, far **40** are shared absolutely across all layers, frames and views. Body, equipment layered preview, fixed live frame and production `camera()` all share `camera::resolve` + `camera::matrices`. No per-frame/per-equipment auto-framing or depth normalization. Source root motion is retained as imported (no extraction). User adjusts height/center/pivot in the body view; automatic animation envelopes are not promised.
110 110
111 111 **Vast renderkader tonen** switches live placement to this same orthographic camera and target-aspect, centered yellow frame. Drag still changes yaw/pitch; orbit-scroll zoom is ignored while the fixed frame is shown. Uncheck it for the independent perspective orbit/scroll camera. Neither free orbit zoom nor live selected clip/time changes a production job. Render snapshots the current project settings at job start (including resolved pivot and `pivot_source`); later edits do not change that in-flight batch. **Opslaan** persists male and female fixed framing (including optional manual pivot) in `.ppd`.
112 112
@@ -198,7 +198,7 @@ After every merge, `README.md` describes only the current behavior: what the pro
198 198
199 199 The application icon is artwork supplied by Tim Falken. The original file is `assets/icons/paperdoll-renderer.png` (57×60 RGBA). Package bitmaps are scaled from that file with a premultiplied Lanczos filter, so the drawing is not redrawn. The eframe window uses the 128×128 raster as its window icon. Larger hicolor sizes stay in the packages. Linux AppImage, `.deb` and Arch packages install the hicolor sizes and name the icon `paperdoll-renderer` from both the desktop file and the AppStream metainfo. The MinGW Windows build embeds the same artwork in `paperdoll-renderer.exe` as `RT_GROUP_ICON`; the zip does not carry a separate `.ico`.
200 200
201 Live 3D preview, as built: the body screen draws the skinned glTF/GLB or FBX mesh with an independent perspective orbit camera or the visible fixed orthographic render frame (§6). Drag orbits, scroll zooms, and buttons snap to pitches −60/−30/0/+30/+60 and the twelve yaw steps of 30 degrees. Yaw 0 places the camera on -Z because the test bodies face -Z. Walk and attack are sampled on the CPU and uploaded as joint matrices. The preview light is a placement shade. The placement preview is lit, unlike exported albedo. It overlays bone-attached OBJ or FBX equipment (§9); FBX bodies are drawn from the normalized `fbx_scene`. The shell explicitly requests a native 24-bit depth buffer in eframe's **Glow/OpenGL** backend (not wgpu). Every body callback owns depth-test LESS, depth writes and clear-to-1 each frame; subsequent equipment layers must share that attachment without clearing it. A real GPU overlap/readback probe checks draw-order independence and depth clearing between frames. The CPU still rasterizer also depth-tests finite in-frustum fragments; neither preview is allowed to use painter's submission order as occlusion.
201 Live 3D preview, as built: the body screen draws the skinned glTF/GLB or FBX mesh with an independent perspective orbit camera or the visible fixed orthographic render frame (§6). Drag orbits, scroll zooms, and buttons snap to pitches −60/−30/0/+30/+60 and the twelve yaw steps of 30 degrees. Yaw 0 places the camera on +Z because glTF defines +Z as the front (Tim t991u). Walk and attack are sampled on the CPU and uploaded as joint matrices. The preview light is a placement shade. The placement preview is lit, unlike exported albedo. It overlays bone-attached OBJ or FBX equipment (§9); FBX bodies are drawn from the normalized `fbx_scene`. The shell explicitly requests a native 24-bit depth buffer in eframe's **Glow/OpenGL** backend (not wgpu). Every body callback owns depth-test LESS, depth writes and clear-to-1 each frame; subsequent equipment layers must share that attachment without clearing it. A real GPU overlap/readback probe checks draw-order independence and depth clearing between frames. The CPU still rasterizer also depth-tests finite in-frustum fragments; neither preview is allowed to use painter's submission order as occlusion.
202 202
203 203 CI filename requested: `blue-ci-cd.yml`. **Unverified infrastructure detail:** do not invent BlueGit YAML syntax. Inspect documented BlueGit runner schema and narrow existing `timfa/posprint` and `legacy-cycles` pipeline/release examples before writing configuration. Respect runner/image selection, artifact retention and release API semantics. No GitLab mirror is requested for this new project.
204 204
@@ -306,7 +306,7 @@ Minimal server change, not applied in this repository: add `POST /api/v1/repos/{
306 306
307 307 `tests/fixtures/bodies/test-body-male.glb` and `test-body-female.glb` are generated by `scripts/generate_test_bodies.py`. No external model is downloaded. Both files are self-contained glTF 2.0 binary, CC0-1.0, and labelled in `asset.extras` as test assets with `finalCharacter: false`. They are not final characters.
308 308
309 Each file has one skin, 19 named joints, rigid skin weights, and two clips: `walk` (1 second, arms opposite the legs) and `attack` (0.8 seconds, the right arm swings forward). Bind rotations are identity. The documented axes are +Y up and forward -Z. Male and female differ in shoulder width, hip width and hip height so a later importer can tell them apart.
309 Each file has one skin, 19 named joints, rigid skin weights, and two clips: `walk` (1 second, arms opposite the legs) and `attack` (0.8 seconds, the right arm swings forward). Bind rotations are identity. The documented axes are +Y up and forward +Z (glTF convention), left side at +X. Male and female differ in shoulder width, hip width and hip height so a later importer can tell them apart.
310 310
311 311 `tests/test_body_assets.rs` checks that both files exist, parse as GLB, contain that skeleton, contain both clips, and byte-match a fresh run of the generator. That test is a fixture check. It is not the product importer, it does not play the clips, and it does not render. The current editor imports and animates these CC0 fixtures; native GPU and batch probes test actual rendering separately from this asset-structure test.
312 312
@@ -425,7 +425,7 @@ Alpha is always kept (scaled to the bit depth). Distance = near + t·(far − ne
425 425 - KHR_texture_transform on the base-color texture is baked into the UVs at load. Sampler wrap modes stay unread (nearest + REPEAT everywhere); metallic/roughness, normal/occlusion/emissive textures and morph targets stay unused, body specular stays zero. These limits are documented in the README rather than approximated.
426 426
427 427 ## Decision 2026-11-07 (#13 orientation + envelope, implementation choice)
428 - Orientation convention fixed and test-guarded with labelled markers: +Y up, character faces −Z, its right is +X; yaw 0 = front (character's right on image left), 90 = left side, 180 = back, 270 = right side; positive pitch looks from above.
428 - Orientation convention fixed and test-guarded with labelled markers: +Y up, character faces +Z, its left is +X (glTF convention; originally −Z, superseded by Tim t991u); yaw 0 = front (character's right on image left), 90 = left side, 180 = back, 270 = right side; positive pitch looks from above.
429 429 - "Omhullende animaties" is a one-shot, user-triggered button: height and center Y are set so every clip, every frame at project fps and all 60 views fit, +5% margin per side; the pivot is unchanged. Framing stays fixed afterwards: no per-frame, per-clip or per-layer autozoom. Equipment allowance stays manual. Root motion stays retained, so a travelling clip widens the envelope.
430 430
431 431 ## Decision 2026-11-07 (#14 robustness gate, implementation choice)
@@ -449,3 +449,8 @@ Decision (superseded and reverted per Tim t991u, see below): automatic per-body
449 449 - The program does no model or animation fixes. Bodies, equipment and clips are loaded exactly as the file describes them: no facing flip, no reattaching of static joint chains (IK feet), no front/back motion mirroring, no other heuristic correction.
450 450 - If a model or animation looks wrong, the animator fixes the source file. The t965u findings above (IK feet without keys, mirrored knee motion) stay source-data issues for the animator.
451 451 - The t965u automatic corrections (`orient.rs`, the editor "Automatische correctie" notes, their tests and README text) are removed.
452
453 ## Decision 2026-11-07 (Tim t991u): front follows the glTF convention (+Z)
454 - This is a convention, not a model fix. glTF 2.0 §3.4 says +Y is up, +Z is forward and −X is right, so the front of an asset faces +Z. The camera follows that for every model: yaw 0 puts the camera on +Z and looks at the front. The character's left side (+X) is at yaw 90, the back at 180 and the right side at 270. Positive pitch looks from above, and light azimuth 0 is also the +Z front.
455 - This supersedes the earlier −Z convention (t952u fixed framing, #13 orientation). The bundled synthetic bodies (generator scripts, test-body glbs/fbx, static glb/obj) have been regenerated to be spec-correct: front +Z, left limbs at +X, walk/attack rotations mirrored to match, extras `forward: "+Z"`. The labelled-marker orientation tests, preview/still tests and the binary preset snapshot follow the new convention.
456 - FBX goes through the same axis conversion as before. A model that faces another way is shown that way; reorienting it is the animator's job.
@@ -106,7 +106,7 @@ Paths use `/` and stay inside the project directory. Save writes/fsyncs a tempor
106
106
107Required views: pitches −60, −30, 0, +30, +60 degrees (t955u; originally −30/0/+30); yaw 0, 30, 60, ..., 330 degrees. **Proposal:** yaw zero means the standardized character front faces the camera, world +Y is up, and positive yaw is defined by a documented right-hand convention. Distinguish camera elevation from model rotation: use orbiting orthographic camera around fixed character origin. Golden orientation fixture must show labelled front/back/left/right to remove ambiguity before production export.
107Required views: pitches −60, −30, 0, +30, +60 degrees (t955u; originally −30/0/+30); yaw 0, 30, 60, ..., 330 degrees. **Proposal:** yaw zero means the standardized character front faces the camera, world +Y is up, and positive yaw is defined by a documented right-hand convention. Distinguish camera elevation from model rotation: use orbiting orthographic camera around fixed character origin. Golden orientation fixture must show labelled front/back/left/right to remove ambiguity before production export.
108
108
109**Implemented fixed framing (Tim t952u):** orthographic projection, +Y world up, yaw 0 camera on −Z looking toward the character; positive yaw follows the existing orbit convention. The **orbit pivot** is independent of the vertical image center: target is `(0, pivot_y/1000, 0)` with camera distance **10**. `pivot_y_milli = None` resolves the unique rest-pose neck joint (name tokens such as `Neck`, `mixamorigNeck`, `Bip001 Neck`, `CC_Base_Neck`); missing/ambiguous necks fall back to `center_y_milli` with a UI diagnostic; an explicit `Some` value is a manual override. Vertical world extent is `height_milli/1000`, horizontal extent vertical × target aspect. Orthographic Y translation `(pivot − center) / half_height` keeps zero-pitch image framing (feet / image center) stable when the pivot is raised to the neck. Near **0.05**, far **40** are shared absolutely across all layers, frames and views. Body, equipment layered preview, fixed live frame and production `camera()` all share `camera::resolve` + `camera::matrices`. No per-frame/per-equipment auto-framing or depth normalization. Source root motion is retained as imported (no extraction). User adjusts height/center/pivot in the body view; automatic animation envelopes are not promised.
109**Implemented fixed framing (Tim t952u):** orthographic projection, +Y world up, yaw 0 camera on +Z looking toward the character's front (glTF convention, see Tim t991u decision); positive yaw follows the existing orbit convention. The **orbit pivot** is independent of the vertical image center: target is `(0, pivot_y/1000, 0)` with camera distance **10**. `pivot_y_milli = None` resolves the unique rest-pose neck joint (name tokens such as `Neck`, `mixamorigNeck`, `Bip001 Neck`, `CC_Base_Neck`); missing/ambiguous necks fall back to `center_y_milli` with a UI diagnostic; an explicit `Some` value is a manual override. Vertical world extent is `height_milli/1000`, horizontal extent vertical × target aspect. Orthographic Y translation `(pivot − center) / half_height` keeps zero-pitch image framing (feet / image center) stable when the pivot is raised to the neck. Near **0.05**, far **40** are shared absolutely across all layers, frames and views. Body, equipment layered preview, fixed live frame and production `camera()` all share `camera::resolve` + `camera::matrices`. No per-frame/per-equipment auto-framing or depth normalization. Source root motion is retained as imported (no extraction). User adjusts height/center/pivot in the body view; automatic animation envelopes are not promised.
110
110
111**Vast renderkader tonen** switches live placement to this same orthographic camera and target-aspect, centered yellow frame. Drag still changes yaw/pitch; orbit-scroll zoom is ignored while the fixed frame is shown. Uncheck it for the independent perspective orbit/scroll camera. Neither free orbit zoom nor live selected clip/time changes a production job. Render snapshots the current project settings at job start (including resolved pivot and `pivot_source`); later edits do not change that in-flight batch. **Opslaan** persists male and female fixed framing (including optional manual pivot) in `.ppd`.
111**Vast renderkader tonen** switches live placement to this same orthographic camera and target-aspect, centered yellow frame. Drag still changes yaw/pitch; orbit-scroll zoom is ignored while the fixed frame is shown. Uncheck it for the independent perspective orbit/scroll camera. Neither free orbit zoom nor live selected clip/time changes a production job. Render snapshots the current project settings at job start (including resolved pivot and `pivot_source`); later edits do not change that in-flight batch. **Opslaan** persists male and female fixed framing (including optional manual pivot) in `.ppd`.
112
112
@@ -198,7 +198,7 @@ After every merge, `README.md` describes only the current behavior: what the pro
198
198
199The application icon is artwork supplied by Tim Falken. The original file is `assets/icons/paperdoll-renderer.png` (57×60 RGBA). Package bitmaps are scaled from that file with a premultiplied Lanczos filter, so the drawing is not redrawn. The eframe window uses the 128×128 raster as its window icon. Larger hicolor sizes stay in the packages. Linux AppImage, `.deb` and Arch packages install the hicolor sizes and name the icon `paperdoll-renderer` from both the desktop file and the AppStream metainfo. The MinGW Windows build embeds the same artwork in `paperdoll-renderer.exe` as `RT_GROUP_ICON`; the zip does not carry a separate `.ico`.
199The application icon is artwork supplied by Tim Falken. The original file is `assets/icons/paperdoll-renderer.png` (57×60 RGBA). Package bitmaps are scaled from that file with a premultiplied Lanczos filter, so the drawing is not redrawn. The eframe window uses the 128×128 raster as its window icon. Larger hicolor sizes stay in the packages. Linux AppImage, `.deb` and Arch packages install the hicolor sizes and name the icon `paperdoll-renderer` from both the desktop file and the AppStream metainfo. The MinGW Windows build embeds the same artwork in `paperdoll-renderer.exe` as `RT_GROUP_ICON`; the zip does not carry a separate `.ico`.
200
200
201Live 3D preview, as built: the body screen draws the skinned glTF/GLB or FBX mesh with an independent perspective orbit camera or the visible fixed orthographic render frame (§6). Drag orbits, scroll zooms, and buttons snap to pitches −60/−30/0/+30/+60 and the twelve yaw steps of 30 degrees. Yaw 0 places the camera on -Z because the test bodies face -Z. Walk and attack are sampled on the CPU and uploaded as joint matrices. The preview light is a placement shade. The placement preview is lit, unlike exported albedo. It overlays bone-attached OBJ or FBX equipment (§9); FBX bodies are drawn from the normalized `fbx_scene`. The shell explicitly requests a native 24-bit depth buffer in eframe's **Glow/OpenGL** backend (not wgpu). Every body callback owns depth-test LESS, depth writes and clear-to-1 each frame; subsequent equipment layers must share that attachment without clearing it. A real GPU overlap/readback probe checks draw-order independence and depth clearing between frames. The CPU still rasterizer also depth-tests finite in-frustum fragments; neither preview is allowed to use painter's submission order as occlusion.
201Live 3D preview, as built: the body screen draws the skinned glTF/GLB or FBX mesh with an independent perspective orbit camera or the visible fixed orthographic render frame (§6). Drag orbits, scroll zooms, and buttons snap to pitches −60/−30/0/+30/+60 and the twelve yaw steps of 30 degrees. Yaw 0 places the camera on +Z because glTF defines +Z as the front (Tim t991u). Walk and attack are sampled on the CPU and uploaded as joint matrices. The preview light is a placement shade. The placement preview is lit, unlike exported albedo. It overlays bone-attached OBJ or FBX equipment (§9); FBX bodies are drawn from the normalized `fbx_scene`. The shell explicitly requests a native 24-bit depth buffer in eframe's **Glow/OpenGL** backend (not wgpu). Every body callback owns depth-test LESS, depth writes and clear-to-1 each frame; subsequent equipment layers must share that attachment without clearing it. A real GPU overlap/readback probe checks draw-order independence and depth clearing between frames. The CPU still rasterizer also depth-tests finite in-frustum fragments; neither preview is allowed to use painter's submission order as occlusion.
202
202
203CI filename requested: `blue-ci-cd.yml`. **Unverified infrastructure detail:** do not invent BlueGit YAML syntax. Inspect documented BlueGit runner schema and narrow existing `timfa/posprint` and `legacy-cycles` pipeline/release examples before writing configuration. Respect runner/image selection, artifact retention and release API semantics. No GitLab mirror is requested for this new project.
203CI filename requested: `blue-ci-cd.yml`. **Unverified infrastructure detail:** do not invent BlueGit YAML syntax. Inspect documented BlueGit runner schema and narrow existing `timfa/posprint` and `legacy-cycles` pipeline/release examples before writing configuration. Respect runner/image selection, artifact retention and release API semantics. No GitLab mirror is requested for this new project.
204
204
@@ -306,7 +306,7 @@ Minimal server change, not applied in this repository: add `POST /api/v1/repos/{
306
306
307`tests/fixtures/bodies/test-body-male.glb` and `test-body-female.glb` are generated by `scripts/generate_test_bodies.py`. No external model is downloaded. Both files are self-contained glTF 2.0 binary, CC0-1.0, and labelled in `asset.extras` as test assets with `finalCharacter: false`. They are not final characters.
307`tests/fixtures/bodies/test-body-male.glb` and `test-body-female.glb` are generated by `scripts/generate_test_bodies.py`. No external model is downloaded. Both files are self-contained glTF 2.0 binary, CC0-1.0, and labelled in `asset.extras` as test assets with `finalCharacter: false`. They are not final characters.
308
308
309Each file has one skin, 19 named joints, rigid skin weights, and two clips: `walk` (1 second, arms opposite the legs) and `attack` (0.8 seconds, the right arm swings forward). Bind rotations are identity. The documented axes are +Y up and forward -Z. Male and female differ in shoulder width, hip width and hip height so a later importer can tell them apart.
309Each file has one skin, 19 named joints, rigid skin weights, and two clips: `walk` (1 second, arms opposite the legs) and `attack` (0.8 seconds, the right arm swings forward). Bind rotations are identity. The documented axes are +Y up and forward +Z (glTF convention), left side at +X. Male and female differ in shoulder width, hip width and hip height so a later importer can tell them apart.
310
310
311`tests/test_body_assets.rs` checks that both files exist, parse as GLB, contain that skeleton, contain both clips, and byte-match a fresh run of the generator. That test is a fixture check. It is not the product importer, it does not play the clips, and it does not render. The current editor imports and animates these CC0 fixtures; native GPU and batch probes test actual rendering separately from this asset-structure test.
311`tests/test_body_assets.rs` checks that both files exist, parse as GLB, contain that skeleton, contain both clips, and byte-match a fresh run of the generator. That test is a fixture check. It is not the product importer, it does not play the clips, and it does not render. The current editor imports and animates these CC0 fixtures; native GPU and batch probes test actual rendering separately from this asset-structure test.
312
312
@@ -425,7 +425,7 @@ Alpha is always kept (scaled to the bit depth). Distance = near + t·(far − ne
425- KHR_texture_transform on the base-color texture is baked into the UVs at load. Sampler wrap modes stay unread (nearest + REPEAT everywhere); metallic/roughness, normal/occlusion/emissive textures and morph targets stay unused, body specular stays zero. These limits are documented in the README rather than approximated.
425- KHR_texture_transform on the base-color texture is baked into the UVs at load. Sampler wrap modes stay unread (nearest + REPEAT everywhere); metallic/roughness, normal/occlusion/emissive textures and morph targets stay unused, body specular stays zero. These limits are documented in the README rather than approximated.
426
426
427## Decision 2026-11-07 (#13 orientation + envelope, implementation choice)
427## Decision 2026-11-07 (#13 orientation + envelope, implementation choice)
428- Orientation convention fixed and test-guarded with labelled markers: +Y up, character faces −Z, its right is +X; yaw 0 = front (character's right on image left), 90 = left side, 180 = back, 270 = right side; positive pitch looks from above.
428- Orientation convention fixed and test-guarded with labelled markers: +Y up, character faces +Z, its left is +X (glTF convention; originally −Z, superseded by Tim t991u); yaw 0 = front (character's right on image left), 90 = left side, 180 = back, 270 = right side; positive pitch looks from above.
429- "Omhullende animaties" is a one-shot, user-triggered button: height and center Y are set so every clip, every frame at project fps and all 60 views fit, +5% margin per side; the pivot is unchanged. Framing stays fixed afterwards: no per-frame, per-clip or per-layer autozoom. Equipment allowance stays manual. Root motion stays retained, so a travelling clip widens the envelope.
429- "Omhullende animaties" is a one-shot, user-triggered button: height and center Y are set so every clip, every frame at project fps and all 60 views fit, +5% margin per side; the pivot is unchanged. Framing stays fixed afterwards: no per-frame, per-clip or per-layer autozoom. Equipment allowance stays manual. Root motion stays retained, so a travelling clip widens the envelope.
430
430
431## Decision 2026-11-07 (#14 robustness gate, implementation choice)
431## Decision 2026-11-07 (#14 robustness gate, implementation choice)
@@ -449,3 +449,8 @@ Decision (superseded and reverted per Tim t991u, see below): automatic per-body
449- The program does no model or animation fixes. Bodies, equipment and clips are loaded exactly as the file describes them: no facing flip, no reattaching of static joint chains (IK feet), no front/back motion mirroring, no other heuristic correction.
449- The program does no model or animation fixes. Bodies, equipment and clips are loaded exactly as the file describes them: no facing flip, no reattaching of static joint chains (IK feet), no front/back motion mirroring, no other heuristic correction.
450- If a model or animation looks wrong, the animator fixes the source file. The t965u findings above (IK feet without keys, mirrored knee motion) stay source-data issues for the animator.
450- If a model or animation looks wrong, the animator fixes the source file. The t965u findings above (IK feet without keys, mirrored knee motion) stay source-data issues for the animator.
451- The t965u automatic corrections (`orient.rs`, the editor "Automatische correctie" notes, their tests and README text) are removed.
451- The t965u automatic corrections (`orient.rs`, the editor "Automatische correctie" notes, their tests and README text) are removed.
452
453## Decision 2026-11-07 (Tim t991u): front follows the glTF convention (+Z)
454- This is a convention, not a model fix. glTF 2.0 §3.4 says +Y is up, +Z is forward and −X is right, so the front of an asset faces +Z. The camera follows that for every model: yaw 0 puts the camera on +Z and looks at the front. The character's left side (+X) is at yaw 90, the back at 180 and the right side at 270. Positive pitch looks from above, and light azimuth 0 is also the +Z front.
455- This supersedes the earlier −Z convention (t952u fixed framing, #13 orientation). The bundled synthetic bodies (generator scripts, test-body glbs/fbx, static glb/obj) have been regenerated to be spec-correct: front +Z, left limbs at +X, walk/attack rotations mirrored to match, extras `forward: "+Z"`. The labelled-marker orientation tests, preview/still tests and the binary preset snapshot follow the new convention.
456- FBX goes through the same axis conversion as before. A model that faces another way is shown that way; reorienting it is the animator's job.