IPL change log
Version history of IPL — Interpretable Pose Latent, newest first. Every entry records what was Added, Changed, Clarified or Editorial, and a Compatibility line for the vector P (layout, ids, ranges, defaults). Section links point into the current specification, IPL 1.1.2. Version numbers follow the IPL policy of §8: in X.Y.Z, X marks a paradigm shift, Y a major change, Z a minor change.
Line: 1.0.0 → 1.0.1 → 1.1.0 (major change: composition grammar introduced) → 1.1.1 → 1.1.2 (current).
1.1.2 — 2026-10-15
Minor change. User functions complete the composition grammar; the arm axes definition is updated for all three axes (units L = 2 · r · d: X from hand length, Y from torso length, Z from arm length); the document is renamed, re-symbolled and dual-licensed, and the version policy is restated as X.Y.Z. Supersedes 1.1.1.
Added
- User functions
%name(args)(§7.8) — a lexicon entry{name, arity, rules}expanded as a text pre-pass before tokenizing, so the token grammar never sees%. Name[A-Za-z_][A-Za-z0-9_]*, at most 64 characters, unique case-insensitively; 1–200 ordered{pattern, template}rules, first match wins; templates use\1–\9,\{1}–\{9}and\\; arity 0–9 is metadata only. Limits: 32 passes, 500 substitutions per script, patterns ≤ 2000 and templates ≤ 4000 characters, a portable regex subset (no backreferences, lookaround or named groups;(?:only).usercallandidententer the EBNF of §7.3 as a pre-pass production. - Diagnostics for user functions (§7.9) — blocking
unknown user function,no rule matches,bad pattern,expansion too deep,expanded too many items. - Construct index (§7.10) —
user-functionrow, since 1.1.2, with the%num(123) | YEARexample.
Changed
- Updated arm axes definition (§3.2, §4.1) — the definitions of all three arm axes X, Y and Z (
a_hand_x,a_hand_y,a_hand_z) are updated together: the X unit is 2 · r · hand length (wrist → middle fingertip), the Y unit 2 · r · torso length (neck → navel), the Z unit 2 · r · arm length (shoulder → wrist, extended). Each axis uses its own calibrated reference distancedbetween body sites measured in fixed reference poses: X =wrist_r→middle_tip_r(open bind pose), Y =neck→navel(bind pose), Z =shoulder_r→wrist_r(arm fully extended). The physical unit per axis isL = 2 · r · d, whereris a fixed per-axis reference ratio defined by the official reference avatar (based), so the established numeric scale is retained. Distances MUST NOT be remeasured from the animated posture; the right-side reference defines the common units for both arms. Shoulder width alone no longer defines arm placement. - Conformance (§6) — a conforming consumer MUST apply the fixed-reference axis definitions and normalization of §4.1; a consumer lacking a valid reference measurement SHOULD fall back to its legacy shoulder-width unit for that axis and report the missing calibration (no calibrated-axis conformance for that avatar).
- Versioning (§8) — restated as the IPL X.Y.Z policy: X = paradigm shift (including any breaking registry edit: an axis removed, an id reused, or a meaning or range redefined), Y = major change (additive: new axes with defaults, a new normative section), Z = minor change; ids are never reused within the same X; every release records supersedes and compatibility. The updated arm axes definition of this release is recorded there as a minor change that retains layout, ids, ranges and defaults while rendered placement may differ: storage compatibility does not imply identical geometry.
Clarified
- §7 preamble: the Since tags 1.1.0 / 1.1.1 / 1.1.2 on grammar constructs name the tier in which each construct entered the grammar; all three tiers are part of this document.
- §7.3: after expansion a script contains no
%call; the EBNF marks tier entries (1.1.1,1.1.2, pre-pass). - Registry and model text corrected against the implementation: §4.3 finger
curl/foldpoles restated as −1 = hyperextended (bent back), 0 = straight, +1 = curled (0 was always the straight default; earlier text read −1 = extended); §4.8eye_lookatvalues enumerated; §1 scope lists the body; §3.1 states the total of 71 dimensions; the §3.4 diagram showscontactandanchor; the JSON registryvalue_conventioncorrected; §4.2 groupwnamed palm facing as in the 1.0.0 annex, with its normativewreconstruction rule restated.
Editorial
- Name: the standard is titled IPL — Interpretable Pose Latent (front matter, §9), matching the Phase-1 TOR annex.
- Symbol: the parameter vector is written as bold upright roman P throughout (front matter, §2, §3, §9); earlier editions used a Greek capital pi. Same object, notation only.
- License: the specification is open to implement, encode, decode and exchange without permission or fee; the document text, the site ipl.signthai.com and the .docx editions are © 2026 AAT research team, DHCB, National Science and Technology Development Agency (NSTDA), Thailand, all rights reserved (front matter, License).
- §10 now points to this change-log page instead of carrying the history inline.
Compatibility: additive. Vector layout, ids, ranges and defaults unchanged; hyper-condition set unchanged. Rendered arm placement can differ on avatars whose calibrated proportions differ from the reference avatar. Scripts valid under 1.1.1 compose identically; % becomes a reserved character.
1.1.1 — 2026-10-08
Minor change. Special functions and operators join the composition grammar, and mouthing becomes continuous: the viseme hyper condition turns into a sparse map so mouth keys crossfade. Supersedes 1.1.0.
Added
- Anchor block
$anchor(site){A | B}(§7.7.1) — stamps theanchorhyper condition (1.0.1) on each arm keyframe the covered glosses author ({l: {site}}/{r: {site}});site∈neck,forehead,mouth,navel,shoulder_l,shoulder_r;neckstamps nothing;fixed_positionglosses and!-filled subgroups are not stamped; additive withcontact, which keeps priority. - Mirror block
$mirror(speed,label){A | B}(§7.7.2) — side-swaps_l/_rtracks and dimensions, negatesh_yaw,h_roll,b_twist,b_lean, and swaps the sides of eye look-at,contactandanchor; mirroring twice is the identity; exactly one body. - Buoy
$buoy(speed,label){primary}{secondary}(§7.7.3) — primary plays as written with every member treated as!; secondary plays fully mirrored, and on every subgroup it does not drive the primary's final keyframe value is held. - Compound
$compound(speed,label){P}{S}and the shorthandP ; S(§7.7.4) — one placed item from a primary and a secondary; target hand = primary's dominant side (left_dominant) or the opposite whenSis a$mirror;S's finger track replacesP's on the target hand, other subgroups fill only wherePdrives nothing; both time-scaled to the longer; nesting ≤ 32 (compound-limit); viseme-operand rule withviseme-too-shortandcompound-noop. - Viseme
$viseme(word),$viseme(word,speed)(§7.7.5) — a mouth-only item from Thai script or romanized Thai carrying only thevisemehyper condition; chunks split on whitespace and-; tone marks ignored;ๆrepeats the chunk; limits 512 code points and 128 syllables (viseme-limit); diagnosticsviseme-guessed/viseme-incomplete(mutually exclusive). - Minimum-gap join
A || B(§7.7.6) —BjoinsAat the composition's minimum gapm, skipping distance scaling and the spelling rule;BMUST be a gloss or a block construct; misplaced||is an error. - Neutral fill
NAME!(§7.7.7) — every subgroup the gloss does not drive is keyed with the neutral pose at its first and last frame, plus one neutral keyrampInbefore andrampOutafter (default 6 each) within the same captioned unit;$shiftand$anchorskip filled subgroups;!is a gloss suffix only. - Reserved characters
; { } !and the;compound operator (§7.2); native keywords$anchor,$mirror,$buoy,$compound,$viseme(lowercase, case-sensitive); block-body rule (§7.7): a marker, an empty body or a||inside a body is an error. - Diagnostics (§7.9) — blocking
bad mirror speed,bad buoy speed,bad compound speed,bad viseme args,bad viseme speed,reserved label,empty block,block member,block arity,unknown anchor site,compound operand,compound shorthand,compound-limit,viseme-limit,viseme unreadable,viseme-too-short,misplaced join; non-blockingviseme-guessed,viseme-incomplete,compound-noop. - Construct index (§7.10) — rows
anchor,mirror,buoy,compound,viseme,min-gap-join,neutral-fill, since 1.1.1, with the$anchor(mouth){EAT} || WATER! | $compound(1,caption){A}{B} | SIGN ; $viseme(sa-wat-dee)example.
Changed
visemedomain (§4.8) — from oneviseme_*shape id plus a 0…1 amount to a sparse map ofviseme_*ids to shares, e.g.{viseme_PP: 0.7, viseme_aa: 0.3}; an absent id is 0,{}is no mouthing, shares sum to at most 1; several shapes may be active at once, and because the value is continuous two mouth keys crossfade on interpolation instead of snapping; an over-budget map is clamped per entry then scaled back onto 1. The former single id + amount is the one-entry map.- Mouth deltas under mouthing (§4.7) — with a viseme active, the artist
viseme_*morph stays the primary shape andm_*is a bounded delta from it: the share-weighted implied baselinebis derived from the viseme mix and decode sends onlyclamp(b + authored, −1, 1) − b; an authored zero leaves the artist viseme untouched. Seed table:viseme_aa → m_jaw +0.7;viseme_PP → m_jaw −0.7, m_press +0.7; other pairs imply zero until tuned. - Timeline (§7.5) —
$visemecounts as a timed item; the gap precedence list gainsmfor an item joined with||. - EBNF (§7.3) —
itemgains theoperand ; operandform,glossthe trailing!, and the productionsanchor,mirror,buoy,compound,cop,viseme,args,site.
Compatibility: additive; P unchanged (no axis added, removed or redefined). A 1.1.0 script composes identically. Consumers that read the hyper condition viseme must accept the map form; a single-entry map reproduces the 1.1.0 value. Minor change: the additions are compose-time constructs and a hyper-condition refinement, and the pure decode of P is untouched.
1.1.0 — 2026-10-01
Composition grammar introduced. Major change, and the release that opens the 1.1 line: the one-line script language that places stored keyframe runs of P — glosses and expressions — on a timeline becomes a normative part of the IPL document, with its basic constructs. The vector P itself is unchanged. Supersedes 1.0.1.
Added
- Composition grammar, §7 (§7) — a new normative section: §7.1 Principle (constructs only place, time-scale, offset, mirror, hold/fill or reference existing keyframes; composition is compose-time; a decoder never needs to parse a script; the script SHOULD be stored with its result), the terms lexicon, gloss, subgroup (
a_l a_r w_l w_r g_l g_r h b f m), gloss flags (draft,spelling,fixed_position,left_dominant), neutral pose and frame. - Lexical conventions (§7.2) — tokens split on
|at bracket/brace depth 0; reserved characters| ( ) [ ] # $ @; no comments, escaping or quoting; plain-decimal numbers; positional comma-separated optional arguments with a trailinglabelslot; case-insensitive gloss and expression names, lowercase native keywords. - Grammar summary (§7.3) — informative EBNF for
script,item,gloss,name,group,member,run,unit,spell,num4,pause,shift,reset,expr. - Name resolution (§7.4) — exact match; explicit variation
NAME#varwith fallback toNAME; bare variation family picked uniformly at random per compose (concrete members preferred, bare name kept on serialization);unknown gloss/ambiguous gloss name; the same rules for@name; aliases (one hop to the main gloss) and composed glosses (stored script + composed keys, no chains, no bare families, keys re-based to frame 0, expressions baked asclamp(pose ⊕ Σ αᵢ·expressionᵢ)overh b f m,NAME!fills from registry defaults). - Timeline and automatic gaps (§7.5) — a gloss at speed
soccupiesmax(1, round(native / s))frames; between timed items and at lead-in / tail-out the composer insertsgap = min(max(15, m), m + round(4·d))withm = max(1, round(minGap))(default minGap 2) anddthe pose distance over normalized P (wrist orientation as a quaternion chord), measured with the active shift; 3 frames between consecutivespellingglosses; timeline lengthmax(2, end + 1). - Basic constructs (§7.6): sequence
A | B(§7.6.1); glossNAME,NAME(speed),NAME(speed,label)with speed > 0, default 1 (§7.6.2); pause$pause,$pause(N), N ≥ 1, bare = 15 frames (§7.6.3); shift$shift(x,y,z,twist)/$reset— stateful offseta_hand_x_l += x,a_hand_x_r −= x,a_hand_y/z += y/z,b_twist += twist, clamped, skippingfixed_positionglosses (§7.6.4); group[A | B](speed,gap,label)with fixed gap default 3, no nesting (§7.6.5); spelling run$spell(x,y,z,twist,label){A | B | …}with default shift (0.1, 0, 0, 0) scaled by(N − 1 − i)/(N − 1)per letter, shift state zero afterwards (§7.6.6); expressions@name(alpha,rampIn,rampOut),@none,@breakon 3 reference lanes,0 < α ≤ 1, default ramp 6 frames, applied at decode as references overh b f m(§7.6.7). - Errors and diagnostics (§7.9) — blocking vs. diagnostic classes;
unknown gloss,ambiguous gloss name,unknown expression,ambiguous expression name,bad gloss speed,bad group args,bad group gap,bad spell args,bad pause,bad shift,bad expression alpha,bad expression ramp,bad group,empty group,no nested groups,group member,expression lanes,unknown function,nothing to place,variation family in composed gloss,composed gloss chain. - Construct index and examples (§7.10) — rows
sequence,gloss,variation,alias-composed,auto-gap,pause,shift,group,spell,expression, since 1.1.0; the$shift(0.3,0,0,0.2) | HELLO | MY NAME(0.75) | @smile(0.5) | $spell{ก | ข} | @none | $pause(15) | $resetexample. The construct index is also published in the JSON registry of each version.
Clarified
- Composition never adds a latent dimension, changes the registry (§4) or redefines a dimension (§7.1); offsets from
$shiftapply to placed keyframes only, P's definitions are unchanged (§7.6.4).
Compatibility: additive; P unchanged — no axis added, removed or redefined; hyper-condition set unchanged (eye_lookat, viseme, contact, anchor). Every 1.0.x vector and decoder remains valid. Major change (Y bump) because the document gains a new normative capability with MUST-level rules, not because the vector changed.
1.0.1 — 2026-07-16
Minor change. Adds the anchor hyper condition. Supersedes 1.0.0. Hyper-condition additions ride alongside P without adding axes (owner policy, 2026-07-16; §8).
Added
anchorhyper condition (§3.4, §4.8) — a post-processing condition per arm side,{l?: {site}, r?: {site}}over body sites: the arm's position (a_hand_*) is measured from the site instead of the neck. Each entry rides its own arm's keyframes; an arm whose keyframes carry no anchor is never moved. Solved on the fully decoded body by translating the anchored arm's target by the world delta site − neck on the posed rig and re-solving; the torso MUST NOT move. Allowed sitesforehead,mouth,navel,shoulder_l,shoulder_r;neckis the default and a no-op; hand sites excluded. Prioritycontact>anchoron the contact's mover side; an unresolvable site falls back to the neck.- Conformance (§6) — a consumer that supports
anchorMUST resolve it as post-processing per arm side, MUST givecontactpriority on its mover side, MUST NOT alter P, and falls back to the neck for an unresolvable site.
Changed
- Versioning policy (§8) — the lowest digit now explicitly covers additions to the hyper-condition set: they ride alongside P without adding axes, so old vectors and pure-latent decoders are unaffected.
Compatibility: additive; P unchanged. Hyper-condition set: eye_lookat, viseme, contact, anchor.
1.0.0 — 2026-06
Initial public release, delivered as the IPL definition annex of the Phase-1 TOR. Supersedes none. Baseline.
Added
- Representation model (§3) — a flat, named, normalized, rig-agnostic parameter vector partitioned into seven continuous groups
a w g h b f m(arm, palm facing, fingers, head, body, face, mouth), sided groups as_l/_rpairs. - Value convention (§3.2) — every continuous dimension in [−1, 1]; bipolar
real = n · max, unipolar(n+1)/2 · max, circular degrees wrapping atmax; neck-centric right-handed body frame (+x body-right, +y up, +z toward viewer); handedness significant, x-like axes negated for the right side at decode. - Pose ⊕ Expression (§3.3) — an additive expression layer over the affect-capable groups
h b f m,effective = clamp(pose ⊕ α · expression). - Hyper conditions (§3.4, §4.8) —
eye_lookatandvisemeas decode-time conditions, andcontact{mover, target}as a post-processing condition solved on the fully decoded body over the fixed vocabulary of 32 body sites (neck,navel,forehead,mouth, per sideshoulder,elbow,wrist,palm,{thumb,index,middle,ring,pinky}_{base,tip}), site offsets calibrated per rig; mover MUST be a hand site; at most one contact per keyframe. - Parameter registry (§4) — the normative id / side / definition / poles / dof / default table for every dimension of
a w g h b f m. - Encoding conventions (§5) — registry order is the canonical serialization order;
id → valueJSON encoding; unknown ids ignored; continuous dimensions interpolate independently, hyper conditions hold; absent dimensions take the registry default. - Conformance (§6) — conforming producer and consumer; a consumer MAY ignore hyper conditions, and one that supports
contactMUST solve it as post-processing without moving the target side or torso. - Versioning (§8) — three-digit version numbers: the first digit for a removed, reused or redefined axis or a changed group model (breaks existing vectors); the second for added axes; the third for clarifications. (Restated as the X.Y.Z paradigm-shift / major / minor policy in 1.1.2.)
- Identifiers (§9) — prose term IPL, namespace
signthai.com/ipl, provisional media typeapplication/vnd.ipl+json.
Compatibility: baseline. The internal pre-release 1.0/1.1 drafts are collapsed into this release; the published 1.0.0 includes hyper conditions (eye_lookat, viseme, contact).