← hex_body · API reference · version 0.3.6
4 file(s), 1472 lines. This is the source the published version ships, and it is loft — the library is written in the same language you call it from.
Public items
A worked example is a call site the author chose as the thing to read first. Call sites are every line naming the item, found mechanically over the source with comments excluded — so an item with no worked example is not an item nobody uses.
| Item | Defined | Worked example | Call sites |
|---|---|---|---|
Joint struct | 28 | — | 36 37 86 94 101 233 |
joint_new fn | 36 | — | 234 44 |
pose_of fn | 46 | HXB-001 | 448 456 14 20 21 22 … (7 total) |
wheel_value fn | 59 | HXB-007 | 33 38 304 306 315 |
wheel_angle fn | 65 | — | 80 317 |
wheel_skid fn | 79 | HXB-007 | 33 38 304 307 315 |
joint_fits fn | 86 | HXB-002 | 45 46 48 111 119 |
joint_offer fn | 94 | HXB-002 | 102 47 48 117 119 |
joint_residual fn | 101 | — | 118 |
Rig struct | 163 | — | 178 179 189 190 203 204 … (25 total) |
rig_new fn | 178 | — | 347 349 350 351 352 354 … (50 total) |
rig_bone3 fn | 189 | HXB-004 | 205 379 52 60 67 68 … (29 total) |
rig_bone fn | 203 | HXB-001 | 369 53 54 69 70 25 … (16 total) |
rig_count fn | 209 | — | 62 104 106 238 84 347 … (10 total) |
bone_planar fn | 214 | — | 225 281 261 |
rig_planar fn | 223 | HXB-005 | 263 264 185 186 207 |
rig_joint fn | 233 | HXB-002 | 86 110 |
rig_admissible fn | 242 | HXB-008 | 59 73 209 214 215 239 … (11 total) |
rig_write fn | 277 | HXB-008 | 55 58 41 55 61 62 … (19 total) |
rig_read fn | 344 | HXB-008 | 56 62 70 104 106 342 … (12 total) |
rig_eq fn | 392 | — | 57 71 86 88 89 90 … (9 total) |
rig_world_seg fn | 436 | HXB-005 | 602 643 74 126 135 145 … (19 total) |
Frame struct | 485 | — | 496 544 553 |
rig_world_frame3 fn | 496 | HXB-003 | 565 578 281 303 135 |
frame_point fn | 553 | HXB-003 | 565 579 580 282 283 292 … (9 total) |
rig_world_point3 fn | 563 | HXB-003 | 291 342 348 143 |
rig_world_seg3 fn | 576 | HXB-003 | 127 136 146 156 165 176 … (18 total) |
Obb struct | 585 | — | 601 609 614 91 |
bone_obb fn | 601 | HXB-006 | 73 231 255 |
obb_contains fn | 614 | — | 83 90 93 234 268 |
seg_dist fn | 623 | — | 644 81 267 |
bone_shape_has fn | 641 | HXB-006 | 281 282 |
src/hex_body.loft
// Copyright (c) 2026 Jurjen Stellingwerff
// SPDX-License-Identifier: LGPL-3.0-or-later
// hex_body — HEXBODY'S OWN BODY MODEL: rigs, revolute joints with computed limits, pure poses, proxies.
//
// `G1`, the first brick of the body half (`plans/m1-moving-body/`). The load-bearing decision,
// from the user: **a body is a RIG — bones and the limits in the joints between them, never a
// pose** (`SPEC` **I-POSE**). So this module never stores a pose; it COMPUTES one from the current
// joint values, and that computation is a **pure function** (`SPEC` **I6**). Purity is not a
// testability nicety here — it IS the scope boundary `L15` draws: hexbody answers *where a part is
// NOW*, never *what it will do*, and a pose that depended on history would have smuggled in a
// future.
//
// Re-measured from crawler's `hexwheel`/`wheeltest` (`T2 → T1`): the wheel's rolling identity holds
// **by construction, not by tuning**, because the joint value is computed FROM travel rather than
// accumulated. Its own framing — *"phase is computed from the distance, so `Δφ·2πr = Δd` holds by
// construction"* — is exactly `I6`. ⚠ **Flex is joints, not deformation** (`I-POSE`): a part that
// bends is more bones on more joints, so this survives a flexing wing unchanged and hexbody needs
// no skinning.
const TAU = 6.283185307179586; // one turn, in radians
fn fabs(x: float) -> float { if x < 0.0 { 0.0 - x } else { x } }
// A REVOLUTE JOINT: its current value (in TURNS — a value of 1.0 is one revolution, the number a
// contact point actually cares about) and its LIMITS. The limits are the joint's `fits?` doorstep
// (`K-FIT`): a joint value is ORDINAL, so a refusal owes an OFFER of the nearest admissible — unlike
// a material id, which is nominal and offers nothing (`X68`).
pub struct Joint {
pub jt_value: float, // the joint value, in turns
pub jt_lo: float, // limit low (turns)
pub jt_hi: float, // limit high (turns)
}
// One articulation: its current value and the range it may move in. The limits travel
// WITH the joint so a pose cannot be built that the rig would refuse.
pub fn joint_new(value: float, lo: float, hi: float) -> Joint {
Joint { jt_value: value, jt_lo: lo, jt_hi: hi }
}
// FORWARD KINEMATICS (phase B): a point `(lx, ly)` in a part's LOCAL frame, posed by rotating it
// through the joint's angle. A PURE FUNCTION of `(value, lx, ly)` — it reads no stored state, no
// elapsed time, no call count, and no previous value. Two different paths to the same `value`
// therefore give byte-identical output, which is `I6` and the precondition for `I9`'s replay.
//
// Example: @HXB-001
pub fn pose_of(value: float, lx: float, ly: float) -> (float, float) {
th = value * TAU;
c = cos(th);
s = sin(th);
(lx * c - ly * s, lx * s + ly * c)
}
// A WHEEL is a revolute joint whose value is DERIVED from how far it has travelled (phase C):
// `value = travel / (2πr)`. Because the value is a function of travel and not an accumulation of
// steps, the no-slip identity is exact by construction. `slip` is the deliberate defect knob —
// 0 for a true roll; non-zero is a skid, and it is the control that keeps `wheel_skid` honest.
//
// Example: @HXB-007
pub fn wheel_value(travel: float, radius: float, slip: float) -> float {
if radius < 0.000000001 { return 0.0; }
(1.0 + slip) * (travel / (TAU * radius) ?? 0.0)
}
// The joint value as an ANGLE in radians.
pub fn wheel_angle(value: float) -> float { value * TAU }
// THE SKID — the contact point's slip, `|r·θ − travel|`. For a value derived by `wheel_value`
// with `slip = 0` this is **machine-ε, not algebraic zero** — and that distinction was measured,
// not assumed: `travel → value → r·θ` is a float round-trip (a divide by `2πr` then a multiply by
// it), which f64 does not close bit-for-bit, so the residual is ~1 ulp of `travel`. This is the
// BODY'S SOLE FLOAT STEP — the wheel's `ε_seam` (`SPEC` **I-FSEAM**, `X53`): the pose/roll
// computation is exactly where imprecision is licensed, bounded by machine ε and nowhere else.
// It is NOT a `P4` violation: `P4` governs the R1 geometry round trip, which is integer and exact;
// a wheel's roll is kinematics, which is the float frame. Drive `θ` independently of `travel` —
// the spec's *"spin a wheel off-travel"* — and the skid is a real distance, orders of magnitude
// above ε.
//
// Example: @HXB-007
pub fn wheel_skid(radius: float, value: float, travel: float) -> float {
fabs(radius * wheel_angle(value) - travel)
}
// THE DOORSTEP (`K-FIT` for a joint): a value inside the joint's limits fits.
//
// Example: @HXB-002
pub fn joint_fits(j: Joint, value: float) -> boolean {
value >= j.jt_lo && value <= j.jt_hi
}
// The OFFER a refusal owes — the nearest admissible value. A joint value is ordinal, so clamping
// to the limit is a real correction the editor can show, not a silent snap to a different thing.
//
// Example: @HXB-002
pub fn joint_offer(j: Joint, value: float) -> float {
if value < j.jt_lo { return j.jt_lo; }
if value > j.jt_hi { return j.jt_hi; }
value
}
// The residual an offer carries — how far the requested value was from admissible.
pub fn joint_residual(j: Joint, value: float) -> float {
fabs(value - joint_offer(j, value))
}
// ── THE RIG, AND ITS CANONICAL TEXT (phase A of plans/m1-moving-body) ────────
//
// A body's `original` is a RIG — `⟨bones, joint limits⟩`, never a pose (`SPEC` **I-POSE**). A
// bone is a segment with a **joint at its base** connecting it to its PARENT bone; the root bone
// has no parent. This is the shape a **flexing wing** takes: not a soft bone but more bones on
// more joints, so the rig is a tree and the flex is joint values along it (`I-POSE`).
//
// The rig is a DESCRIPTION, so it round-trips like a stencil's `Form`: `write(read(R)) = R`
// **byte-for-byte**, and a malformed text is REFUSED rather than repaired. A rig carries floats
// (offsets, lengths, joint limits), and the float↔text round trip is byte-exact in loft, so the
// text is exact with no tolerance (`I-EXACT`/`P4`: content we authored recovers exactly).
//
// INDEX ORDER IS THE CANONICAL LABELLING. A tree can be relabelled and describe the same rig; the
// canonical form fixes the labels by requiring **topological order** — the root is bone 0 and every
// bone's parent is an EARLIER bone. The PARSER enforces the labelling (a bone out of order is
// refused), the same way `form_read` fixes the start corner by index order (`C2`); the PARENT is
// `rig_admissible`'s, because a forward or absent parent round-trips faithfully and so is not a
// spelling question. Reading a rig is not the same as being allowed to pose it. Fuller canonicalisation — collapsing every relabelling — is the census-shaped
// problem `S5`–`S8` solved for forms; phase A's contract is the byte round trip, which topological
// order plus a strict parser delivers.
// ── THE THIRD DIMENSION (`G1b`) — an explicit axis, and an `oz` ──────────────
//
// A revolute joint in a PLANE needs no axis: there is only one, `+z`, and the
// 2-D code leaves it implicit by ADDING joint angles. In space it has to be
// said, and `hex_part::Hinge` already says it with the same eight numbers —
// `{ox, oy, oz, ax, ay, az, lo, hi}`, of which the planar rig's are the six
// minus two. So this extension unifies two libraries that already agree on
// units (TURNS) and on convention, rather than inventing a third shape.
//
// ⚠ **THE AXIS IS STORED AS GIVEN AND IS NOT NORMALISED**, which is
// `hex_part/hinge.loft`'s rule kept verbatim: *"the length of the axis carries
// no meaning; a consumer that needs a unit vector normalises where it needs
// one"*. `rig_world_seg3` is that consumer and normalises at use.
//
// ⚠⚠ **A PLANAR RIG IS BIT-IDENTICAL, AND ONLY A SINGLE BONE IS** — measured
// before this was written, because it is the claim the whole extension rests
// on. `rig_world_seg` composes by ADDING ANGLES (`th = pth + v`, then one
// `cos`/`sin`); any 3-D form composes ROTATIONS, and `cos(a+b)` is not
// `cos a cos b - sin a sin b` bit-for-bit in f64. Over a five-bone planar
// chain at awkward angles:
//
// quaternion form 0 of 5 bones identical, worst 6.7e-16 m
// matrix form the ROOT identical, worst 2.2e-16 m
//
// so the matrix is what shipped. ⚠ The quaternion's root fails for a reason
// worth keeping: it stores `cos(theta/2)` and rebuilds the rotation through
// double-angle arithmetic, where Rodrigues at `(0,0,1)` collapses to exactly
// `[[c,-s,0],[s,c,0],[0,0,1]]` with `c` and `s` untouched. ⚠ And the axis
// `(0,0,1)` has length exactly 1.0, so normalising it divides by 1.0 and does
// not disturb that — gated, not assumed.
//
// ⚠ `rig_world_seg` IS UNCHANGED. Re-expressing it through the 3-D path would
// have moved every existing consumer's numbers by an ulp to buy nothing.
// A bone rig: for each bone its parent (-1 for the root), its joint offset in the parent's
// frame, its length along its own +x, its revolute axis and its joint limits. `rig_new` and
// `rig_read` make one.
pub struct Rig {
pub rg_parent: vector<integer>, // parent bone index; -1 for the root
pub rg_ox: vector<float>, // joint offset in the parent's frame (x)
pub rg_oy: vector<float>, // joint offset in the parent's frame (y)
pub rg_oz: vector<float>, // joint offset in the parent's frame (z) — 0 in a planar rig
pub rg_len: vector<float>, // the bone's length from its joint, along its own +x
pub rg_ax: vector<float>, // the revolute axis, AS GIVEN — see above
pub rg_ay: vector<float>, // "
pub rg_az: vector<float>, // " — (0,0,1) in a planar rig: the implicit axis made explicit
pub rg_lo: vector<float>, // the joint's limit low (turns)
pub rg_hi: vector<float>, // the joint's limit high (turns)
}
// An empty rig. Add bones to it; every parallel vector stays the same length, which is
// what makes bone index the only identity a caller needs.
pub fn rig_new() -> Rig {
Rig { rg_parent: [], rg_ox: [], rg_oy: [], rg_oz: [], rg_len: [],
rg_ax: [], rg_ay: [], rg_az: [], rg_lo: [], rg_hi: [] }
}
// Append one bone in SPACE, growing the rig's parallel field vectors in place.
//
// ⚠ Eleven parameters is the eight of `hex_part::Hinge` plus a parent, a length
// and the rig — the family's own shape rather than a shorter one invented here.
//
// Example: @HXB-004
pub fn rig_bone3(r: Rig, parent: integer, ox: float, oy: float, oz: float, len: float,
ax: float, ay: float, az: float, lo: float, hi: float) -> Rig {
r.rg_parent += [parent];
r.rg_ox += [ox]; r.rg_oy += [oy]; r.rg_oz += [oz]; r.rg_len += [len];
r.rg_ax += [ax]; r.rg_ay += [ay]; r.rg_az += [az];
r.rg_lo += [lo]; r.rg_hi += [hi];
r
}
// Append one bone in the PLANE — the special case. A planar bone is one at
// `oz = 0` about `(0, 0, 1)`,
// which is exactly the axis the 2-D code left implicit.
//
// Example: @HXB-001
pub fn rig_bone(r: Rig, parent: integer, ox: float, oy: float, len: float,
lo: float, hi: float) -> Rig {
rig_bone3(r, parent, ox, oy, 0.0, len, 0.0, 0.0, 1.0, lo, hi)
}
// How many bones the rig holds — the bound for any walk over it.
pub fn rig_count(r: Rig) -> integer { len(r.rg_parent) }
// Does bone `i` lie in the plane the 2-D rig assumes? ⚠ It is asked with `==`
// and no tolerance, because it decides which RECORD the bone is written as and
// a near-planar bone must not be written as a planar one.
pub fn bone_planar(r: Rig, i: integer) -> boolean {
(r.rg_oz[i] ?? 0.0) == 0.0 && (r.rg_ax[i] ?? 0.0) == 0.0
&& (r.rg_ay[i] ?? 0.0) == 0.0 && (r.rg_az[i] ?? 0.0) == 1.0
}
// Is the whole rig planar? The question `rig_world_seg` implicitly asks of
// every rig it is handed, made available to a caller that wants to know.
//
// Example: @HXB-005
pub fn rig_planar(r: Rig) -> boolean {
for i in 0..len(r.rg_parent) {
if !bone_planar(r, i) { return false; }
}
true
}
// The joint at bone `i`, as a `Joint` carrying its limits (value defaults to 0 — the neutral pose).
//
// Example: @HXB-002
pub fn rig_joint(r: Rig, i: integer) -> Joint {
joint_new(0.0, r.rg_lo[i] ?? 0.0, r.rg_hi[i] ?? 0.0)
}
// ADMISSIBLE — topological (root is bone 0 with parent -1, every other parent is earlier), and
// every joint limit is ordered `lo <= hi`. This is the rig's doorstep: `fits?` for a whole rig.
//
// Example: @HXB-004
// Example: @HXB-008
pub fn rig_admissible(r: Rig) -> boolean {
n = len(r.rg_parent);
if n == 0 { return false; }
if (r.rg_parent[0] ?? 0) != -1 { return false; }
for i in 0..n {
p = r.rg_parent[i] ?? 0;
if i == 0 {
if p != -1 { return false; }
} else {
if p < 0 || p >= i { return false; } // parent must be an EARLIER bone
}
if (r.rg_lo[i] ?? 0.0) > (r.rg_hi[i] ?? 0.0) { return false; }
// ⚠ AN AXIS OF ZERO LENGTH IS A JOINT WITH NOTHING TO TURN ABOUT —
// `hex_part::HG_AXIS`, and asked the same way `moros_sim::has_axis` asks
// it: one component non-zero, never a normalised length. It is a
// validation the planar rig had no way to express, because there the axis
// was implicit and could not be got wrong.
if (r.rg_ax[i] ?? 0.0) == 0.0 && (r.rg_ay[i] ?? 0.0) == 0.0
&& (r.rg_az[i] ?? 0.0) == 0.0 { return false; }
}
true
}
// ── THE FORMAT, AND WHY A SPATIAL BONE CHANGES ITS RECORD TAG ────────────────
//
// A planar bone writes a `bone` record, so a planar rig reads in any version
// that knows `bone`.
//
// ⚠⚠ A spatial bone writes `bone3` rather than a longer `bone` line. A reader
// that checks the keywords it knows and not the word COUNT would read extra
// fields on a `bone` line as the bone's PLANAR PROJECTION — a rig quietly
// flattened. Under its own tag the same reader refuses the whole rig, which
// `rig_admissible` then rejects. Loud beats tidy.
//
// Example: @HXB-008
pub fn rig_write(r: Rig, name: text) -> text {
n = len(r.rg_parent);
s = "rig {name} bones {n}";
for bi in 0..n {
if bone_planar(r, bi) {
s = s + "\nbone {bi} parent {r.rg_parent[bi] ?? 0} at {r.rg_ox[bi] ?? 0.0} {r.rg_oy[bi] ?? 0.0}"
+ " len {r.rg_len[bi] ?? 0.0} lim {r.rg_lo[bi] ?? 0.0} {r.rg_hi[bi] ?? 0.0}";
} else {
s = s + "\nbone3 {bi} parent {r.rg_parent[bi] ?? 0}"
+ " at {r.rg_ox[bi] ?? 0.0} {r.rg_oy[bi] ?? 0.0} {r.rg_oz[bi] ?? 0.0}"
+ " len {r.rg_len[bi] ?? 0.0}"
+ " axis {r.rg_ax[bi] ?? 0.0} {r.rg_ay[bi] ?? 0.0} {r.rg_az[bi] ?? 0.0}"
+ " lim {r.rg_lo[bi] ?? 0.0} {r.rg_hi[bi] ?? 0.0}";
}
}
s
}
fn rig_word(line: text, i: integer) -> text {
parts = line.split(' ');
parts[i] ?? ""
}
fn rig_word_int(line: text, i: integer) -> integer { rig_word(line, i) as integer ?? 0 }
fn rig_word_float(line: text, i: integer) -> float { rig_word(line, i) as float ?? 0.0 }
// How many space-separated fields the line has. Each record has a fixed shape, so a line
// carrying MORE is a different line — trailing junk and a trailing space both land here.
fn rig_word_count(line: text) -> integer { len(line.split(' ')) }
// Is field `i` a number SPELLED THE WAY `rig_write` spells one? Not a character class: the
// parse has to reproduce the original characters, which refuses ``, `x`, `007`, `+1.5`, `1.50`
// and `1.5e0` in one test — each of which a fallible cast otherwise hands back as a plausible
// number that then writes out differently.
fn rig_word_is_int(line: text, i: integer) -> boolean {
w = rig_word(line, i);
v = w as integer ?? 0;
"{v}" == w
}
fn rig_word_is_float(line: text, i: integer) -> boolean {
w = rig_word(line, i);
v = w as float ?? 0.0;
"{v}" == w
}
// Every float field of a `bone` / `bone3` record, spelled as this library spells one.
fn rig_floats_ok(line: text, lo: integer, hi: integer) -> boolean {
for i in lo..hi + 1 {
if !rig_word_is_float(line, i) { return false; }
}
true
}
// STRICT PARSER — accepts exactly what `rig_write` emits, refuses anything else by returning an
// empty rig (which `rig_admissible` then rejects). A lenient reader would void the byte diff.
//
// What pins that is a RE-SPELLING IDENTITY: a field is a number only if printing the parse
// reproduces the original characters (so `1.0`, `+1.5`, `01.5`, `1.50`, `1.5e0`, an empty
// field and `x` are refused), and a line is a record only if it carries exactly the fields the
// writer emits (so a trailing space or an extra field is refused).
//
// ⚠ WHAT THIS PARSER DOES NOT CHECK IS THE PARENT. A
// forward, absent or duplicate-root parent round-trips FAITHFULLY — nothing is repaired, the
// byte diff still means what it says — so it is not the reader's to refuse. It is
// `rig_admissible`'s, which is the rig's doorstep and catches every one of them. Reading a rig
// is not the same as being allowed to pose it.
//
// Example: @HXB-008
pub fn rig_read(t: text) -> Rig {
lines = t.split('\n');
nl = len(lines);
if nl < 1 { return rig_new(); }
head = lines[0] ?? "";
if rig_word(head, 0) != "rig" { return rig_new(); }
if rig_word(head, 2) != "bones" { return rig_new(); }
if rig_word_count(head) != 4 { return rig_new(); }
if !rig_word_is_int(head, 3) { return rig_new(); }
nb = rig_word_int(head, 3);
if nl != nb + 1 { return rig_new(); }
out = rig_new();
for ri in 1..nl {
ln = lines[ri] ?? "";
tag = rig_word(ln, 0);
if rig_word_int(ln, 1) != ri - 1 { return rig_new(); } // index order, like form C2
if rig_word(ln, 2) != "parent" { return rig_new(); }
if !rig_word_is_int(ln, 1) || !rig_word_is_int(ln, 3) { return rig_new(); }
if tag == "bone" {
if rig_word_count(ln) != 12 { return rig_new(); }
if rig_word(ln, 4) != "at" { return rig_new(); }
if rig_word(ln, 7) != "len" { return rig_new(); }
if rig_word(ln, 9) != "lim" { return rig_new(); }
if !rig_floats_ok(ln, 5, 6) || !rig_word_is_float(ln, 8)
|| !rig_floats_ok(ln, 10, 11) { return rig_new(); }
out = rig_bone(out, rig_word_int(ln, 3), rig_word_float(ln, 5), rig_word_float(ln, 6),
rig_word_float(ln, 8), rig_word_float(ln, 10), rig_word_float(ln, 11));
} else if tag == "bone3" {
if rig_word_count(ln) != 17 { return rig_new(); }
if rig_word(ln, 4) != "at" { return rig_new(); }
if rig_word(ln, 8) != "len" { return rig_new(); }
if rig_word(ln, 10) != "axis" { return rig_new(); }
if rig_word(ln, 14) != "lim" { return rig_new(); }
if !rig_floats_ok(ln, 5, 7) || !rig_word_is_float(ln, 9)
|| !rig_floats_ok(ln, 11, 13) || !rig_floats_ok(ln, 15, 16) { return rig_new(); }
out = rig_bone3(out, rig_word_int(ln, 3),
rig_word_float(ln, 5), rig_word_float(ln, 6), rig_word_float(ln, 7),
rig_word_float(ln, 9),
rig_word_float(ln, 11), rig_word_float(ln, 12), rig_word_float(ln, 13),
rig_word_float(ln, 15), rig_word_float(ln, 16));
} else {
return rig_new(); // an unknown record, refused
}
}
out
}
// Byte-exact equality — floats compared with `==`, no tolerance (`I-EXACT`).
pub fn rig_eq(a: Rig, b: Rig) -> boolean {
n = len(a.rg_parent);
if len(b.rg_parent) != n { return false; }
for ei in 0..n {
if (a.rg_parent[ei] ?? 0) != (b.rg_parent[ei] ?? 0) { return false; }
if (a.rg_ox[ei] ?? 0.0) != (b.rg_ox[ei] ?? 0.0) { return false; }
if (a.rg_oy[ei] ?? 0.0) != (b.rg_oy[ei] ?? 0.0) { return false; }
if (a.rg_oz[ei] ?? 0.0) != (b.rg_oz[ei] ?? 0.0) { return false; }
if (a.rg_len[ei] ?? 0.0) != (b.rg_len[ei] ?? 0.0) { return false; }
if (a.rg_ax[ei] ?? 0.0) != (b.rg_ax[ei] ?? 0.0) { return false; }
if (a.rg_ay[ei] ?? 0.0) != (b.rg_ay[ei] ?? 0.0) { return false; }
if (a.rg_az[ei] ?? 0.0) != (b.rg_az[ei] ?? 0.0) { return false; }
if (a.rg_lo[ei] ?? 0.0) != (b.rg_lo[ei] ?? 0.0) { return false; }
if (a.rg_hi[ei] ?? 0.0) != (b.rg_hi[ei] ?? 0.0) { return false; }
}
true
}
// ── THE COLLISION PROXY (phase D of plans/m1-moving-body) ────────────────────
//
// The project's founding claim, applied to the body: **a collision proxy is DERIVED from the
// geometry itself**, not hand-authored. A rig's geometry is its posed BONE SEGMENTS, so the proxy
// is derived from those: each bone's SHAPE is the capsule (all points within a half-width `w` of
// its segment), and its PROXY is the oriented box that bounds the capsule. `SPEC` **I4**: a proxy
// **⊇** its shape (never misses an overlap) and its overshoot is **bounded and stated**.
//
// The whole-body pose is FORWARD KINEMATICS up the tree — each bone's world frame composes its
// parent's frame with its joint value — and it is a **PURE FUNCTION of the joint values** (`I6`),
// because it composes `pose_of`, which is pure. A flexing wing (more bones on more joints, `I-POSE`)
// poses the same way; nothing here deforms.
// The world segment of bone `i` at the given joint values: `(x0, y0)` its base, `(x1, y1)` its tip.
// Composes the tree from the root (bones are topological, so ancestors are computed first).
//
// ⚠⚠ PLANAR RIGS ONLY, and it says so with numbers rather than a refusal. This walk composes
// joints by ADDING ANGLES, so it reads neither `rg_oz` nor the stored axis: hand it a bone hinged
// about `+y` and it answers as though that hinge were about `+z`. The rig can be perfectly
// ADMISSIBLE, the call returns four ordinary floats, and nothing says the body it describes is not
// the body you built — measured on a two-bone arm, the tip is 2.5 away at a quarter turn and 3.2 at
// three quarters, on a bone 2 long. `rig_planar` is the precondition; a spatial rig belongs on
// `rig_world_frame3` / `rig_world_seg3`, and its proxy has to be derived from those.
//
// Example: @HXB-001
// Example: @HXB-005
pub fn rig_world_seg(r: Rig, values: vector<float>, i: integer) -> (float, float, float, float) {
n = i + 1;
bx: vector<float> = [];
by: vector<float> = [];
th: vector<float> = [];
for k in 0..n {
v = values[k] ?? 0.0;
p = r.rg_parent[k] ?? -1;
if p < 0 {
bx += [0.0]; by += [0.0]; th += [v];
} else {
pth = th[p] ?? 0.0;
(rx, ry) = pose_of(pth, r.rg_ox[k] ?? 0.0, r.rg_oy[k] ?? 0.0); // offset in parent's frame
bx += [(bx[p] ?? 0.0) + rx];
by += [(by[p] ?? 0.0) + ry];
th += [pth + v]; // revolute joints add in 2D
}
}
x0 = bx[i] ?? 0.0;
y0 = by[i] ?? 0.0;
(ex, ey) = pose_of(th[i] ?? 0.0, r.rg_len[i] ?? 0.0, 0.0); // tip = base + len along angle
(x0, y0, x0 + ex, y0 + ey)
}
// ── FORWARD KINEMATICS IN SPACE ──────────────────────────────────────────────
//
// The composition is the same tree walk one dimension up: a bone's offset rides
// its PARENT's rotation (not its own joint), and its length runs along its own
// `+x` under its OWN rotation. What changes is that a rotation can no longer
// be carried as an angle, because two rotations about different axes do not add
// — so a 3x3 basis is carried instead, and Rodrigues builds each joint's own.
//
// ⚠ Nine parallel float vectors is the honest cost of a rig with NO
// DEPENDENCIES. `mesh3d` owns a `Mat4` and is one `use` away, and taking it
// would make the body model depend on a renderer's linear algebra — which the
// manifest's own note refuses (*"the body is pure continuous-space kinematics
// … with NO lattice dependency"*), and a matrix library is the same kind of
// coupling one step further out.
//
// ⚠ A ZERO-LENGTH AXIS POSES AS THE IDENTITY — the joint simply does not turn,
// which is the honest reading of *"nothing to turn about"*. It is not repaired
// to `+z`: that would invent an axis the author did not write, and
// `rig_admissible` refuses the rig anyway.
// A bone's WORLD FRAME: where its joint sits, and how its own axes lie.
//
// A consumer that draws a body needs it: a limb hung on a bone is a
// cloud of points in the BONE's frame, and posing them means applying this
// once and reusing it, not walking the tree per point.
pub struct Frame {
pub fr_x: float, pub fr_y: float, pub fr_z: float, // the bone's joint, in world
pub fr_00: float, pub fr_01: float, pub fr_02: float, // its basis, row-major
pub fr_10: float, pub fr_11: float, pub fr_12: float,
pub fr_20: float, pub fr_21: float, pub fr_22: float,
}
// The world frame of bone `i` at the given joint values. ⚠ One tree walk;
// pose as many points through it as you like with `frame_point`.
//
// Example: @HXB-003
pub fn rig_world_frame3(r: Rig, values: vector<float>, i: integer) -> Frame {
n = i + 1;
bx: vector<float> = []; by: vector<float> = []; bz: vector<float> = [];
m00: vector<float> = []; m01: vector<float> = []; m02: vector<float> = [];
m10: vector<float> = []; m11: vector<float> = []; m12: vector<float> = [];
m20: vector<float> = []; m21: vector<float> = []; m22: vector<float> = [];
for k in 0..n {
th = (values[k] ?? 0.0) * TAU;
c = cos(th);
s = sin(th);
// The axis is stored AS GIVEN, so it is normalised here — at use, by the
// one consumer that needs a unit vector. ⚠ `(0,0,1)` has length exactly
// 1.0 and the divide is exact, which is what keeps a planar single bone
// bit-identical to `rig_world_seg`.
ax = r.rg_ax[k] ?? 0.0; ay = r.rg_ay[k] ?? 0.0; az = r.rg_az[k] ?? 0.0;
al = sqrt(ax * ax + ay * ay + az * az) ?? 0.0;
ux = 0.0; uy = 0.0; uz = 0.0;
if al > 0.0 { ux = ax / al ?? 0.0; uy = ay / al ?? 0.0; uz = az / al ?? 0.0; }
else { c = 1.0; s = 0.0; } // no axis: the identity, never a repair
omc = 1.0 - c;
l00 = c + omc * ux * ux; l01 = omc * ux * uy - s * uz; l02 = omc * ux * uz + s * uy;
l10 = omc * uy * ux + s * uz; l11 = c + omc * uy * uy; l12 = omc * uy * uz - s * ux;
l20 = omc * uz * ux - s * uy; l21 = omc * uz * uy + s * ux; l22 = c + omc * uz * uz;
p = r.rg_parent[k] ?? -1;
if p < 0 {
bx += [0.0]; by += [0.0]; bz += [0.0];
m00 += [l00]; m01 += [l01]; m02 += [l02];
m10 += [l10]; m11 += [l11]; m12 += [l12];
m20 += [l20]; m21 += [l21]; m22 += [l22];
} else {
p00 = m00[p] ?? 1.0; p01 = m01[p] ?? 0.0; p02 = m02[p] ?? 0.0;
p10 = m10[p] ?? 0.0; p11 = m11[p] ?? 1.0; p12 = m12[p] ?? 0.0;
p20 = m20[p] ?? 0.0; p21 = m21[p] ?? 0.0; p22 = m22[p] ?? 1.0;
gx = r.rg_ox[k] ?? 0.0; gy = r.rg_oy[k] ?? 0.0; gz = r.rg_oz[k] ?? 0.0;
bx += [(bx[p] ?? 0.0) + p00 * gx + p01 * gy + p02 * gz];
by += [(by[p] ?? 0.0) + p10 * gx + p11 * gy + p12 * gz];
bz += [(bz[p] ?? 0.0) + p20 * gx + p21 * gy + p22 * gz];
m00 += [p00 * l00 + p01 * l10 + p02 * l20];
m01 += [p00 * l01 + p01 * l11 + p02 * l21];
m02 += [p00 * l02 + p01 * l12 + p02 * l22];
m10 += [p10 * l00 + p11 * l10 + p12 * l20];
m11 += [p10 * l01 + p11 * l11 + p12 * l21];
m12 += [p10 * l02 + p11 * l12 + p12 * l22];
m20 += [p20 * l00 + p21 * l10 + p22 * l20];
m21 += [p20 * l01 + p21 * l11 + p22 * l21];
m22 += [p20 * l02 + p21 * l12 + p22 * l22];
}
}
Frame { fr_x: bx[i] ?? 0.0, fr_y: by[i] ?? 0.0, fr_z: bz[i] ?? 0.0,
fr_00: m00[i] ?? 1.0, fr_01: m01[i] ?? 0.0, fr_02: m02[i] ?? 0.0,
fr_10: m10[i] ?? 0.0, fr_11: m11[i] ?? 1.0, fr_12: m12[i] ?? 0.0,
fr_20: m20[i] ?? 0.0, fr_21: m21[i] ?? 0.0, fr_22: m22[i] ?? 1.0 }
}
// Where a point in the bone's LOCAL frame lands in the world.
//
// Example: @HXB-003
pub fn frame_point(f: Frame, lx: float, ly: float, lz: float) -> (float, float, float) {
(f.fr_x + f.fr_00 * lx + f.fr_01 * ly + f.fr_02 * lz,
f.fr_y + f.fr_10 * lx + f.fr_11 * ly + f.fr_12 * lz,
f.fr_z + f.fr_20 * lx + f.fr_21 * ly + f.fr_22 * lz)
}
// The one-shot door: pose ONE point of bone `i`. ⚠ It walks the tree, so
// a caller posing many points should hold a `Frame` instead.
//
// Example: @HXB-003
pub fn rig_world_point3(r: Rig, values: vector<float>, i: integer,
lx: float, ly: float, lz: float) -> (float, float, float) {
frame_point(rig_world_frame3(r, values, i), lx, ly, lz)
}
// The world segment of bone `i` IN THREE DIMENSIONS: `(x0,y0,z0)` its base,
// `(x1,y1,z1)` its tip. The 2-D `rig_world_seg` is a separate walk — see the
// header for why.
//
// It is TWO POINTS of the bone's own frame: its base is the frame's origin and
// its tip is `(len, 0, 0)` in it.
//
// Example: @HXB-003
pub fn rig_world_seg3(r: Rig, values: vector<float>, i: integer)
-> (float, float, float, float, float, float) {
f = rig_world_frame3(r, values, i);
(bx, by, bz) = frame_point(f, 0.0, 0.0, 0.0);
(tx, ty, tz) = frame_point(f, r.rg_len[i] ?? 0.0, 0.0, 0.0);
(bx, by, bz, tx, ty, tz)
}
// An oriented bounding box: centre, unit axis (along the bone), and half-extents.
pub struct Obb {
pub ob_cx: float, pub ob_cy: float, // centre
pub ob_ux: float, pub ob_uy: float, // unit axis, along the segment
pub ob_hl: float, // half-length, along the axis
pub ob_hw: float, // half-width, perpendicular
}
// THE PROXY of bone `i`: the OBB that bounds its capsule of half-width `w`. Length grows by `w`
// at each end to cover the rounded caps; width is `w`.
//
// ⚠ IT IS BUILT ON `rig_world_seg`, so it is PLANAR-ONLY for the same reason and inherits the same
// silence: on a spatial rig this boxes a bone that is somewhere else, and `I4`'s containment — a
// proxy never misses an overlap — does not hold. Check `rig_planar` first.
//
// Example: @HXB-005
// Example: @HXB-006
pub fn bone_obb(r: Rig, values: vector<float>, i: integer, w: float) -> Obb {
(x0, y0, x1, y1) = rig_world_seg(r, values, i);
dx = x1 - x0;
dy = y1 - y0;
seg = sqrt(dx * dx + dy * dy) ?? 0.0;
ux = 1.0;
uy = 0.0;
if seg > 0.000000001 { ux = dx / seg ?? 1.0; uy = dy / seg ?? 0.0; }
Obb { ob_cx: (x0 + x1) / 2.0, ob_cy: (y0 + y1) / 2.0,
ob_ux: ux, ob_uy: uy, ob_hl: seg / 2.0 + w, ob_hw: w }
}
// Is a world point inside the OBB? Project onto the axis and its perpendicular.
pub fn obb_contains(o: Obb, px: float, py: float) -> boolean {
dx = px - o.ob_cx;
dy = py - o.ob_cy;
along = dx * o.ob_ux + dy * o.ob_uy;
perp = 0.0 - dx * o.ob_uy + dy * o.ob_ux;
fabs(along) <= o.ob_hl && fabs(perp) <= o.ob_hw
}
// Distance from a point to a segment — the capsule test's core.
pub fn seg_dist(x0: float, y0: float, x1: float, y1: float, px: float, py: float) -> float {
dx = x1 - x0;
dy = y1 - y0;
l2 = dx * dx + dy * dy;
if l2 < 0.000000000001 { return sqrt((px - x0) * (px - x0) + (py - y0) * (py - y0)) ?? 0.0; }
t = ((px - x0) * dx + (py - y0) * dy) / l2 ?? 0.0;
if t < 0.0 { t = 0.0; }
if t > 1.0 { t = 1.0; }
cx = x0 + t * dx;
cy = y0 + t * dy;
sqrt((px - cx) * (px - cx) + (py - cy) * (py - cy)) ?? 0.0
}
// The bone's SHAPE: is a world point within `w` of its segment (inside the capsule)?
//
// ⚠ Planar-only, like `bone_obb`: it asks `rig_world_seg`.
//
// Example: @HXB-006
pub fn bone_shape_has(r: Rig, values: vector<float>, i: integer, w: float,
px: float, py: float) -> boolean {
(x0, y0, x1, y1) = rig_world_seg(r, values, i);
seg_dist(x0, y0, x1, y1, px, py) <= w
}
tests/01-hex-body.loft
// Copyright (c) 2026 Jurjen Stellingwerff
// SPDX-License-Identifier: LGPL-3.0-or-later
// body — the conformance tests: a pose is a PURE FUNCTION of joint values, a wheel rolls without
// slip, a rig round-trips, and a bone's proxy contains its shape.
//
// These assert the library's CONTRACT. The exhaustive measurement lives in hexbody's
// `tests/joint.loft`, which prints its evidence and carries a control that fires; this is what CI
// runs under `LOFT_DENY_WARNINGS=1`.
use hex_body::*;
fn fabs2(x: float) -> float { if x < 0.0 { 0.0 - x } else { x } }
fn main() {
(x, y) = pose_of(0.25, 1.0, 0.0); // quarter turn of (1,0) -> ~(0,1)
assert(fabs2(x) < 0.0001 && y > 0.9999, "a quarter turn takes (1,0) to (0,1)");
}
fn test_a_pose_is_a_pure_function_of_the_joint_value() {
// I6: two paths to the same value give a byte-identical pose. pose_of reads no history.
(ax, ay) = pose_of(0.37, 1.4, -0.6);
pose_of(0.1, 1.4, -0.6); // visit other values
pose_of(0.62, 1.4, -0.6);
(bx, by) = pose_of(0.37, 1.4, -0.6);
assert(ax == bx && ay == by, "the pose is byte-identical across two paths to the value");
}
fn test_a_wheel_rolls_without_slip() {
// The value is derived from travel, so skid = |r*theta - travel| is machine-eps.
r = 2.5;
worst = 0.0;
for i in 0..21 {
travel = (i as float) * 0.37;
sk = wheel_skid(r, wheel_value(travel, r, 0.0), travel);
if sk > worst { worst = sk; }
}
assert(worst < 0.000000000001, "a derived wheel value does not slip beyond machine eps");
// control: spin off-travel and it skids by whole units.
off = wheel_skid(r, wheel_value(10.0, r, 0.0), 3.0);
assert(off > 1.0, "an off-travel spin skids");
}
fn test_a_joint_refuses_out_of_range_with_an_offer() {
// K-FIT: a joint value is ordinal, so a refusal offers the nearest admissible.
h = joint_new(0.0, -0.25, 0.25);
assert(joint_fits(h, 0.1), "0.1 is within +-0.25");
assert(!joint_fits(h, 0.9), "0.9 is not");
assert(joint_offer(h, 0.9) == 0.25, "the offer is the nearest limit");
assert(joint_fits(h, joint_offer(h, 0.9)), "and the offer is itself admissible");
}
fn test_a_rig_round_trips_as_canonical_text() {
r = rig_new();
r = rig_bone(r, -1, 0.0, 0.0, 1.5, -0.5, 0.5);
r = rig_bone(r, 0, 0.8660254037844386, 0.25, 0.37, -0.125, 0.125);
t = rig_write(r, "arm");
back = rig_read(t);
assert(rig_eq(r, back), "write(read(R)) = R byte-for-byte, floats included");
assert(rig_write(back, "arm") == t, "and the bytes match");
assert(rig_admissible(r), "a topological rig with ordered limits is admissible");
// a reordered bone index is refused, not repaired.
bad = "rig arm bones 2\nbone 1 parent -1 at 0 0 len 1 lim 0 0\nbone 0 parent 0 at 0 0 len 1 lim 0 0";
assert(rig_count(rig_read(bad)) == 0, "a malformed rig is refused");
}
fn test_a_bone_proxy_contains_its_shape() {
// I4: the derived OBB proxy contains the bone's capsule; a shape point outside it would be a
// missed overlap.
r = rig_new();
r = rig_bone(r, -1, 0.0, 0.0, 3.0, -1.0, 1.0);
r = rig_bone(r, 0, 3.0, 0.0, 2.0, -0.25, 0.25);
vals = [0.0, 0.125];
w = 0.3;
o = bone_obb(r, vals, 1, w);
(x0, y0, x1, y1) = rig_world_seg(r, vals, 1);
miss = 0;
ns = 0;
for gi in 0..41 {
for gj in 0..29 {
gx = 2.0 + (gi as float) * 0.06;
gy = -0.5 + (gj as float) * 0.06;
if seg_dist(x0, y0, x1, y1, gx, gy) <= w {
ns = ns + 1;
if !obb_contains(o, gx, gy) { miss = miss + 1; }
}
}
}
assert(ns > 50, "the grid covers the bone");
assert(miss == 0, "every shape point is inside the proxy (proxy contains the shape)");
// control: a shrunk proxy no longer contains an edge point of the shape.
edge = obb_contains(o, o.ob_cx - 0.9 * w * o.ob_uy, o.ob_cy + 0.9 * w * o.ob_ux);
o2 = Obb { ob_cx: o.ob_cx, ob_cy: o.ob_cy, ob_ux: o.ob_ux, ob_uy: o.ob_uy,
ob_hl: o.ob_hl, ob_hw: w * 0.5 };
shrunk = obb_contains(o2, o.ob_cx - 0.9 * w * o.ob_uy, o.ob_cy + 0.9 * w * o.ob_ux);
assert(edge && !shrunk, "shrinking the proxy below the shape drops an edge point (a missed overlap)");
}
tests/02-rig-bone3.loft
// Copyright (c) 2026 Jurjen Stellingwerff
// SPDX-License-Identifier: LGPL-3.0-or-later
// body `G1b` — the third dimension: an explicit revolute AXIS and an `oz`.
//
// ── The one claim ────────────────────────────────────────────────────────────
//
// > **`rig_bone3` is a strict GENERALISATION: a planar rig behaves exactly as
// > it did, and its bytes are exactly the bytes 0.1.0 wrote.**
//
// Everything below is that claim taken apart. ⚠ The interesting half is where
// the generalisation stops being exact and why — `rig_world_seg` composes by
// ADDING ANGLES while any 3-D form composes ROTATIONS, so a CHAIN cannot be
// bit-identical in f64 and a single bone can. That boundary is measured here
// rather than asserted, and it is the reason the implementation carries a
// matrix and not a quaternion.
use hex_body::*;
fn fabs3(x: float) -> float { if x < 0.0 { 0.0 - x } else { x } }
fn worse(a: float, b: float) -> float { if a > b { a } else { b } }
// The 0.1.0 fixture, verbatim from `01-hex-body.loft`.
fn planar_arm() -> Rig {
r = rig_new();
r = rig_bone(r, -1, 0.0, 0.0, 1.5, -0.5, 0.5);
r = rig_bone(r, 0, 0.8660254037844386, 0.25, 0.37, -0.125, 0.125);
r
}
// ─────────────────────────────────────────────────────────────────────────────
// The format — the compatibility half
// ─────────────────────────────────────────────────────────────────────────────
// ⚠⚠ THE COMPATIBILITY GATE, and it is a byte literal on purpose. Every other
// assertion in this file would still pass if the planar record grew a field;
// only pinning the exact bytes 0.1.0 wrote can see that.
fn test_a_planar_rig_writes_the_bytes_zero_one_wrote() {
want = "rig arm bones 2"
+ "\nbone 0 parent -1 at 0 0 len 1.5 lim -0.5 0.5"
+ "\nbone 1 parent 0 at 0.8660254037844386 0.25 len 0.37 lim -0.125 0.125";
got = rig_write(planar_arm(), "arm");
assert(got == want, "a planar rig's bytes are unchanged across the version.\nwant: {want}\ngot: {got}");
}
// ⚠⚠ A SPATIAL BONE CHANGES ITS RECORD TAG, and the reason is a measurement of
// the SHIPPED 0.1.0 reader: its parser checks the keywords it knows and never
// the word COUNT, so a longer `bone` line is read as the bone's PLANAR
// PROJECTION — a rig silently flattened. Under `bone3` the same reader refuses
// the whole rig instead. Loud beats tidy.
fn test_a_spatial_bone_changes_its_record_tag() {
r = rig_new();
r = rig_bone3(r, -1, 0.0, 0.0, 0.3, 1.5, 1.0, 0.0, 0.0, -0.5, 0.5);
want = "rig lid bones 1"
+ "\nbone3 0 parent -1 at 0 0 0.3 len 1.5 axis 1 0 0 lim -0.5 0.5";
got = rig_write(r, "lid");
assert(got == want, "a spatial bone is its own record.\nwant: {want}\ngot: {got}");
// the control: the SAME call with the planar defaults writes the 0.1.0 record
q = rig_new();
q = rig_bone3(q, -1, 0.0, 0.0, 0.0, 1.5, 0.0, 0.0, 1.0, -0.5, 0.5);
assert(rig_write(q, "lid") == "rig lid bones 1\nbone 0 parent -1 at 0 0 len 1.5 lim -0.5 0.5",
"and a bone3 built at the planar defaults is a planar bone: {rig_write(q, \"lid\")}");
}
fn test_a_spatial_rig_round_trips_as_canonical_text() {
r = rig_new();
r = rig_bone3(r, -1, 0.0, 0.0, 0.0, 1.5, 0.0, 0.0, 1.0, -0.5, 0.5);
r = rig_bone3(r, 0, 1.5, 0.0, 0.42, 0.6, 0.0, 1.0, 0.0, 0.0, 0.30);
t = rig_write(r, "hover");
back = rig_read(t);
assert(rig_eq(r, back), "write(read(R)) = R byte-for-byte, the axis included");
assert(rig_write(back, "hover") == t, "and the bytes match");
assert(rig_admissible(r), "a mixed planar/spatial rig is admissible");
}
// ⚠⚠ `rig_eq` has to SEE each new field SEPARATELY, or the round trip above is
// vacuous — and this test was WRONG on its first writing in exactly the way
// that matters. It compared a rig with axis `(0,1,0)` against one with
// `(1,0,0)`, which differ in TWO components: deleting the `rg_ax` comparison
// left the `rg_ay` one to catch it, and the break passed. A negative control
// has to be MINIMAL — one field apart — or it tests the whole set at once and
// can see none of them.
fn test_rig_eq_sees_each_new_field_on_its_own() {
base = one_spatial_bone(0.42, 0.0, 1.0, 0.0);
assert(rig_eq(base, one_spatial_bone(0.42, 0.0, 1.0, 0.0)),
"the control: two identical rigs ARE equal, so the four below are not vacuous");
assert(!rig_eq(base, one_spatial_bone(0.43, 0.0, 1.0, 0.0)), "oz alone is seen");
assert(!rig_eq(base, one_spatial_bone(0.42, 0.5, 1.0, 0.0)), "ax alone is seen");
assert(!rig_eq(base, one_spatial_bone(0.42, 0.0, 1.5, 0.0)), "ay alone is seen");
assert(!rig_eq(base, one_spatial_bone(0.42, 0.0, 1.0, 0.5)), "az alone is seen");
}
// One root plus one child that differs only in the four fields under test.
fn one_spatial_bone(oz: float, ax: float, ay: float, az: float) -> Rig {
r = rig_new();
r = rig_bone3(r, -1, 0.0, 0.0, 0.0, 1.5, 0.0, 0.0, 1.0, -0.5, 0.5);
r = rig_bone3(r, 0, 1.5, 0.0, oz, 0.6, ax, ay, az, 0.0, 0.30);
r
}
fn test_an_unknown_record_is_refused() {
bad = "rig arm bones 1\nbone9 0 parent -1 at 0 0 len 1 lim 0 0";
assert(rig_count(rig_read(bad)) == 0, "a record this reader does not know is refused");
ok = "rig arm bones 1\nbone 0 parent -1 at 0 0 len 1 lim 0 0";
assert(rig_count(rig_read(ok)) == 1, "the control: the record it does know is read");
}
// ─────────────────────────────────────────────────────────────────────────────
// The pose — where the generalisation is exact, and where it stops
// ─────────────────────────────────────────────────────────────────────────────
// ⚠⚠ THE BOUNDARY, MEASURED. A single planar bone poses IDENTICALLY through
// both functions; a planar CHAIN cannot, because `rig_world_seg` adds angles
// and `rig_world_seg3` composes rotations. This pins both halves, so a future
// change that quietly widens the gap fails here.
fn test_the_planar_pose_survives_the_third_dimension() {
r = rig_new();
r = rig_bone(r, -1, 0.0, 0.0, 1.5, -1.0, 1.0);
r = rig_bone(r, 0, 1.3, 0.25, 0.37, -1.0, 1.0);
r = rig_bone(r, 1, 0.7, -0.6, 0.82, -1.0, 1.0);
vals = [0.13, -0.29, 0.41];
// the ROOT is exact — Rodrigues at (0,0,1) collapses to [[c,-s,0],[s,c,0],
// [0,0,1]] with c and s untouched, and normalising a unit axis divides by 1.0
(_, _, ax1, ay1) = rig_world_seg(r, vals, 0);
(_, _, _, bx1, by1, bz1) = rig_world_seg3(r, vals, 0);
assert(ax1 == bx1 && ay1 == by1,
"a single planar bone poses BIT-IDENTICALLY: ({ax1}, {ay1}) vs ({bx1}, {by1})");
assert(bz1 == 0.0, "and never leaves the plane");
// the CHAIN agrees to within an ulp of the coordinate, and no better
worst = 0.0;
for i in 0..3 {
(_, _, px, py) = rig_world_seg(r, vals, i);
(_, _, _, qx, qy, qz) = rig_world_seg3(r, vals, i);
worst = worse(worst, worse(fabs3(qx - px), fabs3(qy - py)));
assert(qz == 0.0, "bone {i} of a planar rig stays at z = 0");
}
assert(worst < 0.000000000000001,
"a planar chain agrees to within 1e-15 m, got {worst}");
// ⚠ THE CONTROL: can this comparison produce a non-trivial reading at all?
// The same instrument against a DIFFERENT pose must read metres.
(_, _, cx, cy) = rig_world_seg(r, vals, 2);
(_, _, _, dx, dy, _) = rig_world_seg3(r, [0.13, -0.29, 0.9], 2);
assert(fabs3(dx - cx) + fabs3(dy - cy) > 0.5,
"the control: a real difference reads in metres, not ulps");
}
// ⚠ The thing a planar rig CANNOT express, which is why this phase exists: a
// bone hinged about an axis perpendicular to itself swings OUT of the plane.
fn test_a_lateral_hinge_leaves_the_plane() {
r = rig_new();
r = rig_bone3(r, -1, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, -0.5, 0.5);
(_, _, _, x, y, z) = rig_world_seg3(r, [0.25], 0);
// a quarter turn about +y takes the bone's own +x to -z
assert(fabs3(x) < 0.000000000000001 && fabs3(y) < 0.000000000000001,
"a quarter turn about +y empties x and y: ({x}, {y})");
assert(fabs3(z + 1.0) < 0.000000000000001, "and puts the tip at z = -1, got {z}");
// the control: the SAME rig about the PLANAR axis never leaves z = 0
q = rig_new();
q = rig_bone3(q, -1, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, -0.5, 0.5);
(_, _, _, _, _, qz) = rig_world_seg3(q, [0.25], 0);
assert(qz == 0.0, "a planar hinge cannot express this at all, which is the point");
}
// ⚠ The other half of the third dimension, and it is a separate mechanism from
// the axis: an OFFSET in z. Without this the round trip proves `oz` is STORED
// and nothing proves it is USED.
fn test_an_offset_in_z_lifts_the_bone() {
r = rig_new();
r = rig_bone3(r, -1, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, -0.5, 0.5);
r = rig_bone3(r, 0, 0.0, 0.0, 0.42, 0.5, 0.0, 0.0, 1.0, -0.5, 0.5);
(bx, by, bz, tx, ty, tz) = rig_world_seg3(r, [0.0, 0.0], 1);
assert(bz == 0.42, "the child's base sits at its own oz, got {bz}");
assert(tz == 0.42, "and its tip stays there, since it turns about +z: {tz}");
assert(bx == 0.0 && by == 0.0, "with x and y untouched: ({bx}, {by})");
assert(tx == 0.5 && ty == 0.0, "and the length still runs along +x: ({tx}, {ty})");
// ⚠ The offset rides the PARENT's rotation, not its own — the same rule the
// 2-D code follows, and a z offset under a planar parent is unmoved by it.
(_, _, qz, _, _, _) = rig_world_seg3(r, [0.25, 0.0], 1);
assert(qz == 0.42, "turning the parent about +z leaves a z offset where it was: {qz}");
}
// ⚠ The axis is stored AS GIVEN and normalised only at use — `hex_part`'s rule,
// and this is the one test that can tell the two apart.
fn test_the_axis_is_stored_as_given_and_normalised_at_use() {
twice = rig_new();
twice = rig_bone3(twice, -1, 0.0, 0.0, 0.0, 1.0, 0.0, 2.0, 0.0, -0.5, 0.5);
assert(rig_write(twice, "a").contains("axis 0 2 0"),
"the number the author wrote is the number stored: {rig_write(twice, \"a\")}");
unit = rig_new();
unit = rig_bone3(unit, -1, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, -0.5, 0.5);
(_, _, _, ux, uy, uz) = rig_world_seg3(unit, [0.3], 0);
(_, _, _, tx, ty, tz) = rig_world_seg3(twice, [0.3], 0);
assert(ux == tx && uy == ty && uz == tz,
"and a longer axis poses identically, because it is normalised at use");
}
// ⚠ A validation the planar rig had no way to express: there, the axis was
// implicit and could not be got wrong.
fn test_an_axis_of_zero_length_is_refused() {
bad = rig_new();
bad = rig_bone3(bad, -1, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, -0.5, 0.5);
assert(!rig_admissible(bad), "a joint with nothing to turn about is not admissible");
// the control: one non-zero component is enough, exactly as `has_axis` asks
ok = rig_new();
ok = rig_bone3(ok, -1, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.000001, -0.5, 0.5);
assert(rig_admissible(ok), "and a small axis is still an axis");
assert(rig_admissible(planar_arm()), "and every 0.1.0 rig is still admissible");
// ⚠ It poses as the IDENTITY rather than being repaired to +z — the tip stays
// on the bone's own +x however far the joint is driven.
(_, _, _, bx, by, bz) = rig_world_seg3(bad, [0.25], 0);
assert(bx == 1.0 && by == 0.0 && bz == 0.0,
"an axis-less joint does not turn: ({bx}, {by}, {bz})");
}
// ⚠⚠ THE API BREAK, PINNED — 0.2.0 is NOT a drop-in for 0.1.0, and this is the
// one shape in which that bites. `struct Rig` is public and gained four
// fields, so a consumer who built one by LITERAL (the six-field shape
// `rig_new` itself uses) now omits them, and under [loft#914] they default
// silently to empty vectors. An absent axis reads as `(0,0,0)`.
//
// ⚠ It is REFUSED rather than repaired, deliberately: treating an absent axis
// as the planar default would be a silent repair, and this library refuses
// those everywhere else. A rig whose axis nobody wrote should be refused
// loudly — but a 0.1.0 consumer got `true` here, so the manifest declares the
// break (`api_compatible_with = "0.2.0"`) instead of pretending otherwise.
fn test_a_zero_one_shaped_literal_is_refused_and_that_is_the_break() {
old = Rig { rg_parent: [-1], rg_ox: [0.0], rg_oy: [0.0],
rg_len: [1.5], rg_lo: [-0.5], rg_hi: [0.5] };
assert(rig_count(old) == 1, "the literal still builds a one-bone rig");
assert(!rig_admissible(old),
"and it is now REFUSED — 0.1.0 answered true, which is the declared break");
// ⚠ The 2-D path is untouched by it: `rig_world_seg` never reads an axis, so
// a consumer that only poses in the plane keeps working. That is why
// `loft compat test` reports the 0.1.0 suite still passing.
(px, py, tx, ty) = rig_world_seg(old, [0.0], 0);
assert(px == 0.0 && py == 0.0 && tx == 1.5 && ty == 0.0,
"the planar pose is unchanged: ({px}, {py}) -> ({tx}, {ty})");
// the control: the SAME rig through the constructors is admissible
good = rig_new();
good = rig_bone(good, -1, 0.0, 0.0, 1.5, -0.5, 0.5);
assert(rig_admissible(good),
"and every consumer that builds through rig_new + rig_bone is unaffected");
}
// ⚠ `bone_planar` decides which RECORD a bone is written as, so it is asked
// with `==` and no tolerance. A near-planar bone is a spatial bone.
fn test_a_nearly_planar_bone_is_a_spatial_bone() {
r = rig_new();
r = rig_bone3(r, -1, 0.0, 0.0, 0.0000000001, 1.0, 0.0, 0.0, 1.0, -0.5, 0.5);
assert(!bone_planar(r, 0), "a bone a tenth of a nanometre out of plane is not planar");
assert(rig_write(r, "a").contains("bone3"), "so it writes the spatial record");
assert(!rig_planar(r), "and the rig is not planar either");
assert(rig_planar(planar_arm()), "the control: the 0.1.0 fixture is planar");
}
// ─────────────────────────────────────────────────────────────────────────────
// The world FRAME (0.3.0) — what `rig_world_seg3` used to throw away
// ─────────────────────────────────────────────────────────────────────────────
// ⚠ The frame IS the segment: its origin is the bone's base and `(len,0,0)`
// in it is the tip. If these ever disagree there are two implementations of
// the walk, which is the thing 0.3.0 removed.
fn test_the_frame_and_the_segment_are_one_walk() {
r = rig_new();
r = rig_bone3(r, -1, 0.0, 0.0, 0.0, 1.5, 0.0, 0.0, 1.0, -1.0, 1.0);
r = rig_bone3(r, 0, 1.5, 0.0, 0.4, 0.6, 0.0, 1.0, 0.0, -1.0, 1.0);
vals = [0.17, -0.31];
(bx, by, bz, tx, ty, tz) = rig_world_seg3(r, vals, 1);
f = rig_world_frame3(r, vals, 1);
(fx, fy, fz) = frame_point(f, 0.0, 0.0, 0.0);
(gx, gy, gz) = frame_point(f, 0.6, 0.0, 0.0);
assert(bx == fx && by == fy && bz == fz,
"the frame's origin IS the bone's base: ({bx},{by},{bz}) vs ({fx},{fy},{fz})");
assert(tx == gx && ty == gy && tz == gz,
"and (len,0,0) in it IS the tip: ({tx},{ty},{tz}) vs ({gx},{gy},{gz})");
// the one-shot door agrees with the held frame
(px, py, pz) = rig_world_point3(r, vals, 1, 0.2, -0.3, 0.4);
(qx, qy, qz) = frame_point(f, 0.2, -0.3, 0.4);
assert(px == qx && py == qy && pz == qz, "both doors answer the same point");
}
// ⚠ A frame's basis is a ROTATION — orthonormal, right-handed. A consumer
// posing a limb through it must not have its geometry sheared or mirrored,
// and nothing else in this file would notice if it were.
fn test_a_frames_basis_is_a_rotation() {
r = rig_new();
r = rig_bone3(r, -1, 0.0, 0.0, 0.0, 1.0, 0.3, -0.7, 0.4, -1.0, 1.0);
r = rig_bone3(r, 0, 0.5, 0.2, 0.1, 1.0, 0.0, 1.0, 0.0, -1.0, 1.0);
f = rig_world_frame3(r, [0.21, 0.43], 1);
// each column is a unit vector
c0 = f.fr_00 * f.fr_00 + f.fr_10 * f.fr_10 + f.fr_20 * f.fr_20;
c1 = f.fr_01 * f.fr_01 + f.fr_11 * f.fr_11 + f.fr_21 * f.fr_21;
c2 = f.fr_02 * f.fr_02 + f.fr_12 * f.fr_12 + f.fr_22 * f.fr_22;
assert(fabs3(c0 - 1.0) < 0.000000000001, "column 0 is unit: {c0}");
assert(fabs3(c1 - 1.0) < 0.000000000001, "column 1 is unit: {c1}");
assert(fabs3(c2 - 1.0) < 0.000000000001, "column 2 is unit: {c2}");
// and they are mutually perpendicular
d01 = f.fr_00 * f.fr_01 + f.fr_10 * f.fr_11 + f.fr_20 * f.fr_21;
d02 = f.fr_00 * f.fr_02 + f.fr_10 * f.fr_12 + f.fr_20 * f.fr_22;
d12 = f.fr_01 * f.fr_02 + f.fr_11 * f.fr_12 + f.fr_21 * f.fr_22;
assert(fabs3(d01) < 0.000000000001 && fabs3(d02) < 0.000000000001
&& fabs3(d12) < 0.000000000001,
"the columns are perpendicular: {d01}, {d02}, {d12}");
// ⚠ RIGHT-handed: column0 x column1 = column2. A mirrored basis is
// orthonormal too, so the check above cannot see one.
cx = f.fr_10 * f.fr_21 - f.fr_20 * f.fr_11;
cy = f.fr_20 * f.fr_01 - f.fr_00 * f.fr_21;
cz = f.fr_00 * f.fr_11 - f.fr_10 * f.fr_01;
assert(fabs3(cx - f.fr_02) < 0.000000000001
&& fabs3(cy - f.fr_12) < 0.000000000001
&& fabs3(cz - f.fr_22) < 0.000000000001,
"and right-handed: ({cx},{cy},{cz}) vs ({f.fr_02},{f.fr_12},{f.fr_22})");
}
// ⚠⚠ THE ONE A CONSUMER ACTUALLY NEEDS: a point OFF the bone's axis, posed.
// `rig_world_seg3` could never answer this — the tip is on the axis — and it
// is exactly what a limb hung on a hinge is.
fn test_a_point_off_the_axis_swings_where_trigonometry_says() {
r = rig_new();
r = rig_bone3(r, -1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0);
// a lateral hinge, like a canopy's
r = rig_bone3(r, 0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.5);
// a point 1 m FORWARD of the hinge, shut
(sx, sy, sz) = rig_world_point3(r, [0.0, 0.0], 1, 0.0, 1.0, 0.0);
assert(fabs3(sx) < 0.000000000001 && fabs3(sy - 1.0) < 0.000000000001
&& fabs3(sz - 1.0) < 0.000000000001,
"shut, it is 1 m forward at the hinge's height: ({sx},{sy},{sz})");
// a quarter turn about +x takes +y to +z
(ox, oy, oz) = rig_world_point3(r, [0.0, 0.25], 1, 0.0, 1.0, 0.0);
assert(fabs3(ox) < 0.000000000001 && fabs3(oy) < 0.000000000001
&& fabs3(oz - 2.0) < 0.000000000001,
"a quarter turn stands it straight up, 1 m above the hinge: ({ox},{oy},{oz})");
// ⚠ TURNS, NOT DEGREES — the assertion that can tell them apart. At 0.25
// read as degrees the point would barely move; here it travels a quarter of
// the way round, 1.414 m from where it started.
dy = oy - sy; dz = oz - sz;
moved = sqrt(dy * dy + dz * dz) ?? 0.0;
assert(moved > 1.41 && moved < 1.42,
"0.25 is a QUARTER TURN and moves it {moved} m, not a fraction of a degree");
}
tests/03-worked-examples.loft
// Copyright (c) 2026 Jurjen Stellingwerff
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Worked examples for `hex_body` (@PLN141). Each `test_*` below is a REAL,
// run-every-CI demonstration of a contract the signature cannot state; the
// function documents itself by CITING the tag with `// Example: @HXB-00N`, and
// `check_doc_drift.sh examples` fails if a citation goes dangling. These are
// teaching examples, distinct from the conformance suites in `01-hex-body.loft`
// and `02-rig-bone3.loft`.
//
// WHAT THIS PACKAGE ASKS OF A CALLER, and why a signature cannot carry it. A
// body here is a RIG — bones, and the limits in the joints between them — and
// never a pose. Everything follows from that one decision:
//
// the pose is COMPUTED from a joint-value vector you pass in, by a pure
// function, so two callers can pose one rig two ways
// at the same instant and neither disturbs the other
// the values are YOURS positional, unchecked, and silently defaulted —
// the joint limits are a doorstep the caller calls
// there are TWO WALKS a 2-D one that adds angles and a 3-D one that
// composes rotations, and they are separate ON PURPOSE
//
// The rigs below are two and three bones at quarter turns, so every world
// position in these tests is one you can work out on paper.
use hex_body::*;
const W = 0.5; // the half-width every proxy example uses
fn fabs3(x: float) -> float { if x < 0.0 { 0.0 - x } else { x } }
// The fixture: an arm. Bone 0 is 3 long from the origin; bone 1 hangs off its
// tip and is 2 long. Both are planar — the axis every 2-D rig leaves implicit.
fn arm() -> Rig {
r = rig_new();
r = rig_bone(r, -1, 0.0, 0.0, 3.0, 0.0 - 1.0, 1.0);
r = rig_bone(r, 0, 3.0, 0.0, 2.0, 0.0 - 0.25, 0.25);
r
}
// The same arm with its second bone hinged about +y instead — out of the plane,
// and 0.5 above it. An admissible rig that the 2-D walk cannot represent.
fn spatial_arm() -> Rig {
r = rig_new();
r = rig_bone(r, -1, 0.0, 0.0, 3.0, 0.0 - 1.0, 1.0);
r = rig_bone3(r, 0, 3.0, 0.0, 0.5, 2.0, 0.0, 1.0, 0.0, 0.0 - 1.0, 1.0);
r
}
// The smallest call that does something real: pose an arm and read where its
// forearm ended up.
fn main() {
r = arm();
(x0, y0, x1, y1) = rig_world_seg(r, [0.0, 0.25], 1);
assert(x0 == 3.0 && y0 == 0.0, "the forearm hangs off the upper arm's tip");
assert(x1 == 3.0 && y1 == 2.0, "and a quarter turn stands it straight up, got ({x1},{y1})");
}
// @HXB-001 — A BODY IS A RIG, NEVER A POSE, and the type says so by having
// nowhere to put one. `Rig` carries bones and joint limits; the joint VALUES
// arrive as a separate vector at every query, and computing a pose writes
// nothing back. So two callers can hold one rig and pose it differently at the
// same instant — a server stepping the authoritative body while a renderer
// draws last frame's — and neither sees the other.
//
// The computation is a PURE function of `(rig, values)`: no elapsed time, no
// call count, no previous value. That is what makes a pose replayable, and it
// is the scope boundary the package draws — it answers where a part is NOW,
// never what it will do next.
fn test_a_body_is_a_rig_and_the_pose_is_computed() {
r = arm();
straight = [0.0, 0.0];
bent = [0.0, 0.25];
(_a0, _b0, sx, sy) = rig_world_seg(r, straight, 1);
(_c0, _d0, bx, by) = rig_world_seg(r, bent, 1);
assert(sx == 5.0 && sy == 0.0, "straight out, the tip is 3 + 2 along x, got ({sx},{sy})");
assert(bx == 3.0 && by == 2.0, "bent a quarter turn, it is 2 up from the elbow, got ({bx},{by})");
// Posing one way did not disturb the other: ask again in the other order.
(_e0, _f0, sx2, sy2) = rig_world_seg(r, straight, 1);
assert(sx2 == sx && sy2 == sy, "the same values give byte-identical numbers, always");
// …and the rig itself is untouched by either — there is no pose in it to touch.
assert(rig_count(r) == 2, "two bones");
assert((r.rg_len[1] ?? 0.0) == 2.0, "the forearm is still 2 long");
j = rig_joint(r, 1);
assert(j.jt_value == 0.0, "and a joint read off the rig has no value of its own — only limits");
assert(j.jt_lo == 0.0 - 0.25 && j.jt_hi == 0.25, "the limits, got {j.jt_lo}..{j.jt_hi}");
}
// @HXB-002 — THE VALUES VECTOR IS POSITIONAL, UNCHECKED, AND SILENTLY
// DEFAULTED. Every kinematics call reads `values[k] ?? 0.0`, so a vector that
// is too short poses the missing joints at zero and returns a complete,
// ordinary-looking body. A value outside the joint's own limits poses too:
// the limits are a DOORSTEP the caller calls, not a guard the pose enforces.
//
// The sharpest case is a value that lands back where it started. Joint values
// are TURNS, so 9.0 is nine revolutions — outside every limit in this rig, and
// its pose is indistinguishable from the neutral one. A pose cannot tell you
// its values were wrong.
fn test_the_values_vector_is_positional_and_unchecked() {
r = arm();
empty: vector<float> = [];
(_a, _b, ex, ey) = rig_world_seg(r, empty, 1);
(_c, _d, zx, zy) = rig_world_seg(r, [0.0, 0.0], 1);
assert(ex == zx && ey == zy, "an EMPTY vector poses the whole rig neutral, not an error");
assert(ex == 5.0, "and answers a perfectly ordinary body, got ({ex},{ey})");
// Nine turns is outside the joint's limits, and it poses anyway.
j = rig_joint(r, 1);
assert(!joint_fits(j, 9.0), "9.0 turns does not fit a +-0.25 joint");
(_e, _f, nx, ny) = rig_world_seg(r, [0.0, 9.0], 1);
assert(fabs3(nx - 5.0) < 1e-12 && fabs3(ny) < 1e-12,
"yet its pose is the neutral one to the last ulp, got ({nx},{ny})");
// The doorstep the caller owes: a refusal names an offer and a residual.
assert(joint_offer(j, 9.0) == 0.25, "the nearest admissible value is the limit");
assert(fabs3(joint_residual(j, 9.0) - 8.75) < 1e-12, "and the residual is 8.75 turns");
assert(joint_fits(j, joint_offer(j, 9.0)), "the offer itself fits");
}
// @HXB-003 — POSE A LIMB THROUGH ONE FRAME, NOT ONE TREE WALK PER POINT. A
// bone's `Frame` is where its joint sits and how its own axes lie, and every
// point hung on that bone — a mesh, a mount, a muzzle — is that frame applied
// to a local offset. `rig_world_point3` is the one-shot door and it walks the
// whole tree each time; `rig_world_frame3` walks it once and hands you the
// frame to reuse.
//
// The numbers are identical either way, which is what makes the choice purely
// about cost: `rig_world_seg3` is itself exactly two points of that frame.
fn test_hold_the_frame_not_the_walk() {
r = arm();
r = rig_bone3(r, 1, 2.0, 0.0, 0.0, 0.5, 1.0, 0.0, 0.0, 0.0 - 1.0, 1.0);
vals = [0.13, 0.21, 0.07];
f = rig_world_frame3(r, vals, 2);
same = true;
for k in 0..5 {
lx = 0.1 * (k as float);
ly = 0.2 - 0.05 * (k as float);
lz = 0.03 * (k as float);
(ax, ay, az) = frame_point(f, lx, ly, lz);
(bx, by, bz) = rig_world_point3(r, vals, 2, lx, ly, lz);
if ax != bx || ay != by || az != bz { same = false; }
}
assert(same, "five points through one frame are byte-identical to five walks");
// And the segment is the frame's origin and its tip, nothing more.
(s0x, s0y, s0z, s1x, s1y, s1z) = rig_world_seg3(r, vals, 2);
(o0x, o0y, o0z) = frame_point(f, 0.0, 0.0, 0.0);
(o1x, o1y, o1z) = frame_point(f, r.rg_len[2] ?? 0.0, 0.0, 0.0);
assert(s0x == o0x && s0y == o0y && s0z == o0z, "the base is the frame's origin");
assert(s1x == o1x && s1y == o1y && s1z == o1z, "the tip is (len,0,0) in it");
assert(s0x == f.fr_x && s0y == f.fr_y && s0z == f.fr_z, "which is where the joint sits");
}
// @HXB-004 — THE AXIS IS STORED AS YOU GAVE IT, AND AN ABSENT ONE IS NEVER
// REPAIRED. A revolute axis carries a direction and no length: `(0,0,2)` and
// `(0,0,1)` are the same hinge, because the walk normalises where it needs a
// unit vector rather than at the door. For the planar axis that divide is by
// exactly 1.0, which is why a planar bone's numbers are untouched by the 3-D
// path existing at all.
//
// An axis of `(0,0,0)` is a joint with nothing to turn about. It poses as the
// IDENTITY — the bone simply does not turn, whatever its value — and
// `rig_admissible` refuses the rig. It is NOT quietly read as `+z`: a rig
// whose axis nobody wrote is refused loudly, the same way a malformed text is.
fn test_the_axis_is_as_given_and_an_absent_one_is_refused() {
unit = rig_new();
unit = rig_bone3(unit, -1, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 1.0, 0.0 - 1.0, 1.0);
twice = rig_new();
twice = rig_bone3(twice, -1, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 2.0, 0.0 - 1.0, 1.0);
none = rig_new();
none = rig_bone3(none, -1, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0 - 1.0, 1.0);
v = [0.125];
(_a, _b, _c, ux, uy, uz) = rig_world_seg3(unit, v, 0);
(_d, _e, _f, tx, ty, tz) = rig_world_seg3(twice, v, 0);
assert(ux == tx && uy == ty && uz == tz, "a doubled axis is the same hinge, bit for bit");
assert(fabs3(ux - 1.4142135623730951) < 1e-12, "an eighth turn puts the tip at 2/sqrt(2), got {ux}");
(_g, _h, _i, nx, ny, nz) = rig_world_seg3(none, v, 0);
assert(nx == 2.0 && ny == 0.0 && nz == 0.0, "no axis means no turn — the identity, got ({nx},{ny},{nz})");
assert(!rig_admissible(none), "and the rig is refused rather than repaired to +z");
assert(rig_admissible(unit) && rig_planar(unit), "while the planar rig is admissible and planar");
assert(!rig_planar(spatial_arm()), "and a rig hinged about +y is not planar");
}
// @HXB-005 — THE 2-D PATH IS FOR PLANAR RIGS, AND IT SAYS SO WITH NUMBERS
// RATHER THAN A REFUSAL. `rig_world_seg` composes joints by ADDING ANGLES, so
// it reads neither the stored axis nor `oz`: hand it a rig hinged about `+y`
// and it answers as though that hinge were about `+z`, in the plane. The rig
// is admissible, the call returns four ordinary floats, and nothing says the
// body it describes is not the body you built.
//
// The collision proxy is built on that same walk, so it inherits the mistake:
// `bone_obb` boxes a bone that is somewhere else, and `bone_shape_has` answers
// for it. Measured on the arm below, the two tips are 2.5 apart at a quarter
// turn and 3.2 apart at three quarters — on a bone 2 long.
//
// So `rig_planar` is not a curiosity: it is the precondition of the entire 2-D
// half of this library, and a caller with a spatial rig must use
// `rig_world_seg3` / `rig_world_frame3` and derive its own proxy.
fn test_the_two_dimensional_path_is_planar_only() {
r = spatial_arm();
assert(rig_admissible(r), "the spatial arm is a perfectly admissible rig");
assert(!rig_planar(r), "and it is not planar — which is the question to ask first");
vals = [0.0, 0.25];
(_a, _b, px, py) = rig_world_seg(r, vals, 1);
(_c, _d, _e, sx, sy, sz) = rig_world_seg3(r, vals, 1);
assert(px == 3.0 && py == 2.0, "the 2-D walk puts the tip up the y axis, got ({px},{py})");
assert(sx == 3.0 && sy == 0.0 && sz == 0.0 - 1.5,
"the bone is actually below the plane, at ({sx},{sy},{sz})");
gap = sqrt((px - sx) * (px - sx) + (py - sy) * (py - sy) + sz * sz) ?? 0.0;
assert(fabs3(gap - 2.5) < 1e-12, "2.5 apart on a bone 2 long, got {gap}");
// The worst of it, over a full turn of that joint.
worst = 0.0;
for k in 0..9 {
v = (k as float) * 0.125;
(_f, _g, qx, qy) = rig_world_seg(r, [0.0, v], 1);
(_h, _i, _j, ax, ay, az) = rig_world_seg3(r, [0.0, v], 1);
d = sqrt((qx - ax) * (qx - ax) + (qy - ay) * (qy - ay) + az * az) ?? 0.0;
if d > worst { worst = d; }
}
assert(fabs3(worst - 3.2015621187164243) < 1e-9, "worst separation 3.20, got {worst}");
assert(worst > (r.rg_len[1] ?? 0.0), "further apart than the bone is long");
// …and the proxy that a consumer would collide against is built on the 2-D walk.
o = bone_obb(r, vals, 1, W);
assert(fabs3(o.ob_cx - 3.0) < 1e-12 && fabs3(o.ob_cy - 1.0) < 1e-12,
"the box is centred on the 2-D segment, got ({o.ob_cx},{o.ob_cy})");
assert(!obb_contains(o, sx, sz), "so it does not contain the bone's real position in its own plane");
// The planar arm, by contrast, is exactly what both walks describe.
p = arm();
(_k, _l, ppx, ppy) = rig_world_seg(p, vals, 1);
(_m, _n, _o2, qsx, qsy, qsz) = rig_world_seg3(p, vals, 1);
assert(qsz == 0.0 && fabs3(ppx - qsx) < 1e-12 && fabs3(ppy - qsy) < 1e-12,
"a planar rig agrees on both paths");
}
// @HXB-006 — A PROXY CONTAINS ITS SHAPE, AND ITS OVERSHOOT IS EXACTLY STATED.
// The collision proxy is DERIVED from the geometry rather than hand-authored: a
// bone's shape is the capsule of half-width `w` around its posed segment, and
// its proxy is the oriented box that bounds that capsule. Containment is the
// half that must never fail — a shape point outside the proxy is a MISSED
// overlap — and the price of it is overshoot, which is bounded and worth
// knowing exactly: `w * (sqrt(2) - 1)` at the four corners, and nothing
// anywhere else.
fn test_a_proxy_contains_its_shape_with_a_stated_overshoot() {
r = arm();
vals = [0.0, 0.0];
o = bone_obb(r, vals, 0, W);
(x0, y0, x1, y1) = rig_world_seg(r, vals, 0);
assert(fabs3(o.ob_hl - (1.5 + W)) < 1e-12, "the box grows by w at each end for the caps");
assert(o.ob_hw == W, "and is w wide");
nshape = 0;
missed = 0;
worst = 0.0;
for gi in 0..161 {
for gj in 0..81 {
gx = 0.0 - 1.0 + (gi as float) * 0.03125;
gy = 0.0 - 1.25 + (gj as float) * 0.03125;
d = seg_dist(x0, y0, x1, y1, gx, gy);
inside = obb_contains(o, gx, gy);
if d <= W {
nshape = nshape + 1;
if !inside { missed = missed + 1; }
}
if inside && d - W > worst { worst = d - W; }
}
}
assert(nshape > 3000, "the grid really covers the capsule, {nshape} points");
assert(missed == 0, "and NOT ONE of them is outside the proxy");
assert(worst <= W * 0.41421356237309515 + 1e-9,
"the overshoot never exceeds w*(sqrt2-1), got {worst}");
assert(worst > W * 0.41 - 1e-9, "and reaches it at the corners — the bound is tight, got {worst}");
assert(bone_shape_has(r, vals, 0, W, 1.5, 0.4), "a point in the capsule is in the shape");
assert(!bone_shape_has(r, vals, 0, W, 1.5, 0.6), "one beyond w is not");
}
// @HXB-007 — A WHEEL'S ANGLE IS COMPUTED FROM ITS TRAVEL, WHICH IS WHY IT DOES
// NOT SLIP. The rolling identity `r * theta = distance` holds here BY
// CONSTRUCTION, not by tuning: the joint value is a function of how far the
// wheel has gone, so there is no place for an error to accumulate. What is
// left is one float round trip — a divide by `2*pi*r` and a multiply back — so
// the residual skid is machine epsilon, not algebraic zero, and this is the
// one place in the body where imprecision is licensed at all.
//
// The contrast is the whole point, and it grows with the journey: adding a
// per-step increment instead drifts, and by 20000 steps it is thousands of
// times further off than the derived value ever gets.
fn test_a_wheel_rolls_by_construction_not_by_tuning() {
radius = 2.5;
step = 0.37;
worst_derived = 0.0;
worst_acc = 0.0;
acc = 0.0;
for k in 0..20001 {
travel = (k as float) * step;
sk = wheel_skid(radius, wheel_value(travel, radius, 0.0), travel);
if sk > worst_derived { worst_derived = sk; }
if k > 0 { acc = acc + wheel_value(step, radius, 0.0); }
ska = wheel_skid(radius, acc, travel);
if ska > worst_acc { worst_acc = ska; }
}
assert(worst_derived < 1e-11, "7400 units of travel, derived: still machine dust ({worst_derived})");
assert(worst_acc > worst_derived * 1000.0,
"the same journey accumulated is over a thousand times worse ({worst_acc})");
// The control that the measurement can fail at all: ask for a skid.
slipped = wheel_skid(radius, wheel_value(10.0, radius, 0.25), 10.0);
assert(slipped > 2.0, "a quarter of deliberate slip is a real distance, got {slipped}");
assert(fabs3(wheel_angle(1.0) - 6.283185307179586) < 1e-12, "a value of 1.0 is one whole turn");
}
// @HXB-008 — INDEX ORDER IS THE RIG'S CANONICAL LABELLING, AND A SPATIAL BONE
// CHANGES ITS RECORD TAG. A tree can be relabelled and describe the same rig,
// so the text fixes the labels: the root is bone 0 and every bone's parent is
// an EARLIER bone. The parser enforces it — a rig written out of order is
// REFUSED, not sorted — which is what makes `write(read(R))` a byte identity.
//
// The tag is the compatibility half. A planar bone writes the bytes this
// library has always written, so a planar rig round-trips across versions
// unchanged; a spatial bone writes `bone3`, so a reader that predates the third
// dimension REFUSES the rig instead of silently reading its planar projection —
// which is the same choice as refusing an absent axis rather than assuming one.
fn test_index_order_is_the_labelling_and_a_spatial_bone_says_so() {
p = rig_new();
p = rig_bone(p, -1, 0.0, 0.0, 1.5, 0.0 - 0.5, 0.5);
assert(rig_write(p, "arm") == "rig arm bones 1\nbone 0 parent -1 at 0 0 len 1.5 lim -0.5 0.5",
"the planar record, byte for byte: [{rig_write(p, \"arm\")}]");
s = rig_new();
s = rig_bone3(s, -1, 0.0, 0.0, 0.25, 1.5, 1.0, 0.0, 0.0, 0.0 - 0.5, 0.5);
assert(rig_write(s, "arm") == "rig arm bones 1\nbone3 0 parent -1 at 0 0 0.25 len 1.5 axis 1 0 0 lim -0.5 0.5",
"and the spatial one carries its own tag: [{rig_write(s, \"arm\")}]");
assert(rig_eq(p, rig_read(rig_write(p, "arm"))), "both round-trip through their own text");
assert(rig_eq(s, rig_read(rig_write(s, "arm"))), "floats included");
// Out of topological order: refused, and a refusal is an EMPTY rig.
bad = "rig arm bones 2\nbone 1 parent -1 at 0 0 len 1 lim 0 0\nbone 0 parent 0 at 0 0 len 1 lim 0 0";
assert(rig_count(rig_read(bad)) == 0, "a rig whose bones are out of order is refused");
assert(rig_count(rig_read(rig_write(p, "arm"))) == 1, "while the writer's own text still reads");
// A float spelled any way but the writer's is refused too — the reader takes
// exactly what the writer emits or the byte diff stops meaning anything.
for spelling in ["1.0", "+1.5", "01.5", "1.50", "1.5e0", "x"] {
t = "rig arm bones 1\nbone 0 parent -1 at 0 0 len {spelling} lim -0.5 0.5";
assert(rig_count(rig_read(t)) == 0, "`len {spelling}` is not how this library spells 1.5");
}
assert(rig_count(rig_read("rig arm bones 1\nbone 0 parent -1 at 0 0 len 1.5 lim -0.5 0.5 ")) == 0,
"nor is a trailing space");
// …but the PARENT is not the reader's business: a forward parent round-trips
// faithfully, so the reader takes it and the DOORSTEP is what refuses it.
forward = "rig arm bones 2\nbone 0 parent 1 at 0 0 len 1 lim 0 0\nbone 1 parent -1 at 3 0 len 2 lim 0 0";
fr = rig_read(forward);
assert(rig_count(fr) == 2, "a forward parent READS — nothing about it is misspelled");
assert(rig_write(fr, "arm") == forward, "and it re-writes byte for byte");
assert(!rig_admissible(fr), "while the doorstep refuses it: a parent must be an EARLIER bone");
// Which is why the doorstep is not optional. Posed anyway, bone 0 reads its
// parent's frame before that parent exists, and gets zeros — a body at the
// origin, with no complaint.
(_g, _h, gx, gy) = rig_world_seg(fr, [0.0, 0.0], 0);
assert(gx == 1.0 && gy == 0.0, "it poses from the origin, got ({gx},{gy})");
}