code wiki / (root) / nx_nxa_anim_lib.nx

nx_nxa_anim_lib.nx source

↩ module page · 427 lines · 19581 B

1// nx_nxa_anim_lib.nx -- THE ONE NXA CLIP EVALUATOR: channel-2 packed world-delta keys, sampled at a 2// clip time, blended toward an idle POSE, and written as the DUAL-QUATERNION ROWS the cast shader skins 3// with. Integer-only and syscall-free so the wasm engine imports it; every consumer (engine, gates, 4// native players) reads ONE implementation of the format's own runtime form. 5// 6// WHY IT EXISTS (GE23, measured 2026-09-06 on the served beach page): the WebGPU cast pipeline already 7// skins on the GPU from a per-frame joint table, and the ONLY per-frame page logic left was animEval -- 8// hand-written JavaScript sampling ANIM tracks with shortest-path nlerp, subtracting a fitted linear 9// root drift, blending toward the frozen stand pose, then forming T = b + dt - D b and the dual half 10// T x real / 2. That is the format's normative runtime form (knowledge/nxa_format_spec.md: M x = D (x - b) 11// + b + dt, shortest-path lerp between keys) expressed in the page instead of the engine. This lib is 12// that arithmetic in NishiLang, driven by the ENGINE's own stride phase rather than a page clock. 13// 14// THE UNIT TRAP, NAMED (spec + nx_nxa_play's audit): ANIM channel-2 dt LANES are MILLIMETRES packed 15// /100 to fit i16, while SKEL binds and the rows are in 0.01 mm model units, so dt lanes are scaled by 16// NA_DT_MM_TO_MODEL here. POSE entry dt words are ALREADY model units (nx_nxa_pose applies the x100 17// at freeze time; debt 1785380029) and are NOT scaled again. Two sections, two conventions, both cited. 18// 19// KEY LAYOUT (spec, channel 2, two words per key, little-endian lanes): 20// w0 = [t_ms u16][dqx i16][dqy i16][dqz i16] w1 = [dqw i16][dtx i16][dty i16][dtz i16] 21// ROW LAYOUT (what nxGpuCast uploads, per joint): real = q (q12), dual = (T x q)/2 with T in model units, 22// laid out as out[0..nj*4) real rows then out[nj*4..nj*8) dual rows; the page divides both by 4096. 23// license_tier: ORIGINAL No hw writes (Rule 26). 24import "nx_nxa_fk.nx" 25 26const NA_Q12: i64 = 4096 27const NA_LANE_MASK: i64 = 65535 28const NA_LANE_HALF: i64 = 32767 29const NA_LANE_FULL: i64 = 65536 30const NA_LANE_B1: i64 = 16 31const NA_LANE_B2: i64 = 32 32const NA_LANE_B3: i64 = 48 33const NA_DT_MM_TO_MODEL: i64 = 100 // spec: ANIM dt lanes are mm packed /100; binds are 0.01 mm 34const NA_TRK_HDR_W: i64 = 3 // per track: [joint][channel][nkeys] 35const NA_CH2: i64 = 2 36const NA_CH2_KEY_W: i64 = 2 37const NA_CH0_KEY_W: i64 = 4 // legacy verbose T keys, 32 bytes -- skipped, never sampled 38const NA_CH1_KEY_W: i64 = 5 // legacy verbose R keys, 40 bytes -- skipped, never sampled 39const NA_ROW_W: i64 = 8 // real x4 + dual x4 per joint 40const NA_IDX_W: i64 = 2 // per joint: key word offset (or -1), nkeys 41const NA_SKEL_W: i64 = 8 // spec: [parent][tx ty tz][qx qy qz qw] 42const NA_SKEL_TX: i64 = 1 43const NA_SKEL_TY: i64 = 2 44const NA_SKEL_TZ: i64 = 3 45const NA_POSE_ENTRY_W: i64 = 8 // spec: [joint][q x4][dt x3] 46const NA_RV_W: i64 = 6 // x0 y0 vx16 vy16 t0 span 47const NA_RV_FX: i64 = 65536 // drift velocity carried as units per ms x 65536 48const NA_ROOT_Z: i64 = 89000 // the page's root rule: pelvis-region bind nearest (0,0,.52H) 49const NA_ROOT_XW: i64 = 2 // ... weighting |x| twice, verbatim from loadNPC 50const NA_MPB_DEFAULT: i64 = 700 // the page's shipped default ms-per-block before a travel fit 51const NA_TRAVEL_MIN_PERMIL: i64 = 1500 // the page fits cadence only when the root travels > 1.5 blocks 52const NA_FIG_UNITS: i64 = 171530 // the reference figure's VERT extent, 0.01 mm units (1.7153 m) 53const NA_FIG_BLOCKS_CENTI: i64 = 178 // ... which the page maps to 1.78 world blocks 54const NA_SCR_W: i64 = 64 // scratch words na_rows needs from its caller 55const NA_RC_BAD_TRACKS: i64 = 0 - 1 56const NA_RC_OVERRUN: i64 = 0 - 2 57 58// sign-extend a 16-bit lane out of an i64 word 59func na_i16(w: i64, sh: i64) -> i64 { 60 var v: i64 = (w >> sh) & NA_LANE_MASK 61 if v > NA_LANE_HALF { v = v - NA_LANE_FULL } 62 return v 63} 64func na_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 65 66// index the channel-2 tracks: tidx[j*2] = key word offset into anim (or -1), tidx[j*2+1] = nkeys. 67// Bounds-checked against nw so a truncated section is REFUSED BY NAME, never read past. Returns the 68// clip duration in ms (the latest last-key time over the indexed tracks), or a negative rc. 69func na_index(anim: *i64, nw: i64, nj: i64, tidx: *i64) -> i64 { 70 var j: i64 = 0 71 while j < nj { tidx[j*NA_IDX_W] = 0 - 1; tidx[j*NA_IDX_W + 1] = 0; j = j + 1 } 72 if nw < 1 { return NA_RC_BAD_TRACKS } 73 let ntr: i64 = anim[0] 74 if ntr < 0 { return NA_RC_BAD_TRACKS } 75 var ao: i64 = 1 76 var dur: i64 = 0 77 var t: i64 = 0 78 while t < ntr { 79 if ao + NA_TRK_HDR_W > nw { return NA_RC_OVERRUN } 80 let jj: i64 = anim[ao] 81 let chn: i64 = anim[ao + 1] 82 let nk: i64 = anim[ao + 2] 83 ao = ao + NA_TRK_HDR_W 84 if nk < 0 { return NA_RC_BAD_TRACKS } 85 var kw: i64 = NA_CH0_KEY_W 86 if chn == 1 { kw = NA_CH1_KEY_W } 87 if chn == NA_CH2 { kw = NA_CH2_KEY_W } 88 // Divide remaining validated space before multiplying an external key count. 89 if nk > (nw - ao)/kw { return NA_RC_OVERRUN } 90 if chn == NA_CH2 { if nk > 0 { if jj >= 0 { if jj < nj { 91 tidx[jj*NA_IDX_W] = ao 92 tidx[jj*NA_IDX_W + 1] = nk 93 let tl: i64 = anim[ao + (nk - 1)*NA_CH2_KEY_W] & NA_LANE_MASK 94 if tl > dur { dur = tl } 95 } } } } 96 ao = ao + nk*kw 97 t = t + 1 98 } 99 return dur 100} 101 102func na_key_t(anim: *i64, ko: i64, i: i64) -> i64 { return anim[ko + i*NA_CH2_KEY_W] & NA_LANE_MASK } 103func na_key_q(anim: *i64, ko: i64, i: i64, q: *i64) -> i64 { 104 let w0: i64 = anim[ko + i*NA_CH2_KEY_W] 105 let w1: i64 = anim[ko + i*NA_CH2_KEY_W + 1] 106 q[0] = na_i16(w0, NA_LANE_B1) 107 q[1] = na_i16(w0, NA_LANE_B2) 108 q[2] = na_i16(w0, NA_LANE_B3) 109 q[3] = na_i16(w1, 0) 110 return 0 111} 112func na_key_d(anim: *i64, ko: i64, i: i64, d: *i64) -> i64 { 113 let w1: i64 = anim[ko + i*NA_CH2_KEY_W + 1] 114 d[0] = na_i16(w1, NA_LANE_B1)*NA_DT_MM_TO_MODEL 115 d[1] = na_i16(w1, NA_LANE_B2)*NA_DT_MM_TO_MODEL 116 d[2] = na_i16(w1, NA_LANE_B3)*NA_DT_MM_TO_MODEL 117 return 0 118} 119 120// sample one track at t_ms: the bracketing key pair, u in q12, shortest-path nlerp on the delta quat 121// (negate the second key when the dot is negative -- the spec's own player note), linear lerp on dt. 122// scr = 8 words. 123// Ordering is established once after na_index validates the packed section. 124// Keep the legacy two-word index ABI; callers own this one-word-per-joint sidecar. 125func na_index_order(anim: *i64, tidx: *i64, nj: i64, order: *i64) -> i64 { 126 var j: i64 = 0 127 while j < nj { 128 let ko: i64 = tidx[j*NA_IDX_W] 129 let nk: i64 = tidx[j*NA_IDX_W + 1] 130 order[j] = 0 131 if ko >= 0 { if nk > 0 { 132 order[j] = 1 133 var i: i64 = 1 134 while i < nk { 135 if na_key_t(anim, ko, i) < na_key_t(anim, ko, i - 1) { order[j] = 0 } 136 i = i + 1 137 } 138 } } 139 j = j + 1 140 } 141 return 0 142} 143 144func na_key_linear(anim: *i64, ko: i64, nk: i64, t: i64) -> i64 { 145 var c: i64 = 0 146 var i: i64 = 1 147 while i < nk { if na_key_t(anim, ko, i) <= t { c = i } i = i + 1 } 148 return c 149} 150 151// Upper bound selects the final equal-time key, matching legacy duplicate semantics. 152func na_key_ordered(anim: *i64, ko: i64, nk: i64, t: i64) -> i64 { 153 var lo: i64 = 0 154 var hi: i64 = nk 155 while lo < hi { 156 let mid: i64 = lo + (hi - lo)/2 157 if na_key_t(anim, ko, mid) <= t { lo = mid + 1 } else { hi = mid } 158 } 159 if lo == 0 { return 0 } 160 return lo - 1 161} 162 163func na_eval_track(anim: *i64, ko: i64, nk: i64, t: i64, q: *i64, d: *i64, scr: *i64) -> i64 { 164 return na_eval_key(anim, ko, nk, na_key_linear(anim, ko, nk, t), t, q, d, scr) 165} 166 167func na_eval_track_ordered(anim: *i64, ko: i64, nk: i64, t: i64, q: *i64, d: *i64, scr: *i64) -> i64 { 168 return na_eval_key(anim, ko, nk, na_key_ordered(anim, ko, nk, t), t, q, d, scr) 169} 170 171func na_eval_key(anim: *i64, ko: i64, nk: i64, c: i64, t: i64, q: *i64, d: *i64, scr: *i64) -> i64 { 172 var k1: i64 = c + 1 173 if k1 >= nk { k1 = c } 174 let t0: i64 = na_key_t(anim, ko, c) 175 let t1: i64 = na_key_t(anim, ko, k1) 176 var u: i64 = 0 177 if t1 > t0 { u = (t - t0)*NA_Q12/(t1 - t0) } 178 if u < 0 { u = 0 } 179 if u > NA_Q12 { u = NA_Q12 } 180 na_key_q(anim, ko, c, q) 181 let b: *i64 = scr 182 na_key_q(anim, ko, k1, b) 183 let dot: i64 = q[0]*b[0] + q[1]*b[1] + q[2]*b[2] + q[3]*b[3] 184 if dot < 0 { b[0] = 0 - b[0]; b[1] = 0 - b[1]; b[2] = 0 - b[2]; b[3] = 0 - b[3] } 185 q[0] = q[0] + (b[0] - q[0])*u/NA_Q12 186 q[1] = q[1] + (b[1] - q[1])*u/NA_Q12 187 q[2] = q[2] + (b[2] - q[2])*u/NA_Q12 188 q[3] = q[3] + (b[3] - q[3])*u/NA_Q12 189 nf_qnorm(q) 190 na_key_d(anim, ko, c, d) 191 let db: *i64 = ((scr as i64) + 32) as *i64 192 na_key_d(anim, ko, k1, db) 193 d[0] = d[0] + (db[0] - d[0])*u/NA_Q12 194 d[1] = d[1] + (db[1] - d[1])*u/NA_Q12 195 d[2] = d[2] + (db[2] - d[2])*u/NA_Q12 196 return 0 197} 198 199// the root joint: the page's own rule, verbatim -- the bind nearest (0, 0, .52H) with |x| weighted twice 200func na_root_joint(skel: *i64, nj: i64) -> i64 { 201 var rj: i64 = 0 - 1 202 var best: i64 = 0 203 var j: i64 = 0 204 while j < nj { 205 let s: i64 = na_abs(skel[j*NA_SKEL_W + NA_SKEL_TZ] - NA_ROOT_Z) + NA_ROOT_XW*na_abs(skel[j*NA_SKEL_W + NA_SKEL_TX]) 206 if rj < 0 { rj = j; best = s } else { if s < best { best = s; rj = j } } 207 j = j + 1 208 } 209 return rj 210} 211 212// fit the LINEAR horizontal drift of the root track once (first key to last key), so the runtime 213// subtracts only that line and the oscillatory residual -- sway, weight shift -- survives on every 214// joint. rv = [x0 y0 vx16 vy16 t0 span]; span 0 means no fit (nothing is subtracted). 215func na_fit_root(anim: *i64, tidx: *i64, rj: i64, rv: *i64, scr: *i64) -> i64 { 216 var k: i64 = 0 217 while k < NA_RV_W { rv[k] = 0; k = k + 1 } 218 if rj < 0 { return 0 } 219 let ko: i64 = tidx[rj*NA_IDX_W] 220 let nk: i64 = tidx[rj*NA_IDX_W + 1] 221 if ko < 0 { return 0 } 222 if nk < 2 { return 0 } 223 let d0: *i64 = scr 224 let dl: *i64 = ((scr as i64) + 32) as *i64 225 na_key_d(anim, ko, 0, d0) 226 na_key_d(anim, ko, nk - 1, dl) 227 let t0: i64 = na_key_t(anim, ko, 0) 228 var spn: i64 = na_key_t(anim, ko, nk - 1) - t0 229 if spn < 1 { spn = 1 } 230 rv[0] = d0[0] 231 rv[1] = d0[1] 232 rv[2] = (dl[0] - d0[0])*NA_RV_FX/spn 233 rv[3] = (dl[1] - d0[1])*NA_RV_FX/spn 234 rv[4] = t0 235 rv[5] = spn 236 return 1 237} 238 239// clip milliseconds per world block walked, DERIVED from the root's own horizontal travel over the 240// clip (the page's rule): fitted only when the root travels more than 1.5 blocks, else the shipped 241// default. Returns mpb. 242func na_mpb(anim: *i64, tidx: *i64, rj: i64, dur: i64, scr: *i64) -> i64 { 243 if rj < 0 { return NA_MPB_DEFAULT } 244 let ko: i64 = tidx[rj*NA_IDX_W] 245 let nk: i64 = tidx[rj*NA_IDX_W + 1] 246 if ko < 0 { return NA_MPB_DEFAULT } 247 if nk < 1 { return NA_MPB_DEFAULT } 248 let d: *i64 = scr 249 var x0: i64 = 0 250 var x1: i64 = 0 251 var y0: i64 = 0 252 var y1: i64 = 0 253 var i: i64 = 0 254 while i < nk { 255 na_key_d(anim, ko, i, d) 256 if i == 0 { x0 = d[0]; x1 = d[0]; y0 = d[1]; y1 = d[1] } else { 257 if d[0] < x0 { x0 = d[0] } 258 if d[0] > x1 { x1 = d[0] } 259 if d[1] < y0 { y0 = d[1] } 260 if d[1] > y1 { y1 = d[1] } 261 } 262 i = i + 1 263 } 264 let dx: i64 = x1 - x0 265 let dy: i64 = y1 - y0 266 let hyp: i64 = nf_isqrt(dx*dx + dy*dy) 267 let blocks_permil: i64 = hyp*NA_FIG_BLOCKS_CENTI*10/NA_FIG_UNITS 268 if blocks_permil <= NA_TRAVEL_MIN_PERMIL { return NA_MPB_DEFAULT } 269 var du: i64 = dur 270 if du < 1 { du = 1 } 271 return du*1000/blocks_permil 272} 273 274// THE ROWS. For every joint: sample its track at t_ms (identity when untracked, exactly as the page 275// wrote identity rows), subtract the fitted root drift from the horizontal dt, blend toward the idle 276// POSE by iw (q12; shortest path, renormalised), then T = b + dt - D b with D b from nx_nxa_fk's 277// quaternion rotate, and the dual-quaternion rows. out = nj*8 words; scr = NA_SCR_W words. 278func na_rows(anim: *i64, tidx: *i64, nj: i64, skel: *i64, pq: *i64, pd: *i64, haspose: i64, rv: *i64, t: i64, iw: i64, out: *i64, scr: *i64) -> i64 { 279 return na_rows_indexed(anim, tidx, 0 as *i64, nj, skel, pq, pd, haspose, rv, t, iw, out, scr) 280} 281 282func na_rows_indexed(anim: *i64, tidx: *i64, order: *i64, nj: i64, skel: *i64, pq: *i64, pd: *i64, haspose: i64, rv: *i64, t: i64, iw: i64, out: *i64, scr: *i64) -> i64 { 283 let q: *i64 = scr 284 let d: *i64 = ((scr as i64) + 32) as *i64 285 let db: *i64 = ((scr as i64) + 64) as *i64 286 let qs: *i64 = ((scr as i64) + 96) as *i64 287 let es: *i64 = ((scr as i64) + 256) as *i64 288 var j: i64 = 0 289 while j < nj { 290 let ko: i64 = tidx[j*NA_IDX_W] 291 let nk: i64 = tidx[j*NA_IDX_W + 1] 292 let ro: i64 = j*4 293 let du: i64 = nj*4 + j*4 294 if ko < 0 { 295 out[ro] = 0; out[ro + 1] = 0; out[ro + 2] = 0; out[ro + 3] = NA_Q12 296 out[du] = 0; out[du + 1] = 0; out[du + 2] = 0; out[du + 3] = 0 297 } else { 298 var ordered: i64 = 0 299 if (order as i64) != 0 { ordered = order[j] } 300 if ordered == 1 { na_eval_track_ordered(anim, ko, nk, t, q, d, es) } else { na_eval_track(anim, ko, nk, t, q, d, es) } 301 if rv[5] > 0 { 302 d[0] = d[0] - (rv[0] + rv[2]*(t - rv[4])/NA_RV_FX) 303 d[1] = d[1] - (rv[1] + rv[3]*(t - rv[4])/NA_RV_FX) 304 } 305 if haspose == 1 { if iw > 0 { 306 let px: i64 = pq[j*4] 307 let py: i64 = pq[j*4 + 1] 308 let pz: i64 = pq[j*4 + 2] 309 let pw: i64 = pq[j*4 + 3] 310 var sg: i64 = 1 311 if q[0]*px + q[1]*py + q[2]*pz + q[3]*pw < 0 { sg = 0 - 1 } 312 q[0] = q[0] + (sg*px - q[0])*iw/NA_Q12 313 q[1] = q[1] + (sg*py - q[1])*iw/NA_Q12 314 q[2] = q[2] + (sg*pz - q[2])*iw/NA_Q12 315 q[3] = q[3] + (sg*pw - q[3])*iw/NA_Q12 316 nf_qnorm(q) 317 d[0] = d[0] + (pd[j*3] - d[0])*iw/NA_Q12 318 d[1] = d[1] + (pd[j*3 + 1] - d[1])*iw/NA_Q12 319 d[2] = d[2] + (pd[j*3 + 2] - d[2])*iw/NA_Q12 320 } } 321 let bx: i64 = skel[j*NA_SKEL_W + NA_SKEL_TX] 322 let by: i64 = skel[j*NA_SKEL_W + NA_SKEL_TY] 323 let bz: i64 = skel[j*NA_SKEL_W + NA_SKEL_TZ] 324 nf_qrotv(q, bx, by, bz, db, qs) 325 let tx: i64 = bx + d[0] - db[0] 326 let ty: i64 = by + d[1] - db[1] 327 let tz: i64 = bz + d[2] - db[2] 328 out[ro] = q[0]; out[ro + 1] = q[1]; out[ro + 2] = q[2]; out[ro + 3] = q[3] 329 out[du] = (tx*q[3] + ty*q[2] - tz*q[1])/2 330 out[du + 1] = (0 - tx*q[2] + ty*q[3] + tz*q[0])/2 331 out[du + 2] = (tx*q[1] - ty*q[0] + tz*q[3])/2 332 out[du + 3] = (0 - tx*q[0] - ty*q[1] - tz*q[2])/2 333 } 334 j = j + 1 335 } 336 return 0 337} 338 339// In-place gait calibration uses the asset's admitted foot landmarks. The rearward 340// travel of the lower sole maps path distance to clip time; clip length is not a stride. 341const NA_GAIT_FOOT_W: i64 = 9 342const NA_GAIT_INPUT_W: i64 = 18 343const NA_GAIT_HEADER_W: i64 = 8 344const NA_GAIT_ROW_W: i64 = 3 345const NA_GAIT_DISTANCE: i64 = 1 346const NA_GAIT_CONTACT: i64 = 2 347func na_gait_sole(anim: *i64, ko: i64, nk: i64, skel: *i64, foot: *i64, t: i64, rv: *i64, out: *i64, scr: *i64) -> i64 { 348 let q: *i64 = scr 349 let d: *i64 = ((scr as i64)+32) as *i64 350 let es: *i64 = ((scr as i64)+64) as *i64 351 let rot: *i64 = ((scr as i64)+128) as *i64 352 let rs: *i64 = ((scr as i64)+160) as *i64 353 na_eval_track_ordered(anim,ko,nk,t,q,d,es) 354 if rv[5]>0 { d[0]=d[0]-(rv[0]+rv[2]*(t-rv[4])/NA_RV_FX); d[1]=d[1]-(rv[1]+rv[3]*(t-rv[4])/NA_RV_FX) } 355 let x: i64=q[0]; let y: i64=q[1]; let z: i64=q[2]; let w: i64=q[3] 356 let s: i64=foot[7]; let c: i64=foot[8] 357 q[0]=(s*w+c*x)/NA_Q12; q[1]=(c*y-s*z)/NA_Q12 358 q[2]=(s*y+c*z)/NA_Q12; q[3]=(c*w-s*x)/NA_Q12 359 nf_qnorm(q) 360 let j: i64=foot[0] 361 let bx: i64=skel[j*NA_SKEL_W+NA_SKEL_TX] 362 let by: i64=skel[j*NA_SKEL_W+NA_SKEL_TY] 363 let bz: i64=skel[j*NA_SKEL_W+NA_SKEL_TZ] 364 var p: i64=0 365 while p<2 { 366 let k: i64=1+p*3 367 nf_qrotv(q,foot[k]-bx,foot[k+1]-by,foot[k+2]-bz,rot,rs) 368 out[p*3]=rot[0]+bx+d[0]; out[p*3+1]=rot[1]+by+d[1]; out[p*3+2]=rot[2]+bz+d[2] 369 p=p+1 370 } 371 return 0 372} 373func na_gait_fit(anim: *i64,tidx: *i64,order: *i64,skel: *i64,nj: i64,rv: *i64,feet: *i64,header: *i64,table: *i64,capacity: i64,scr: *i64) -> i64 { 374 var i: i64=0 375 while i<NA_GAIT_HEADER_W {header[i]=0;i=i+1} 376 let left: i64=feet[0]; let right: i64=feet[NA_GAIT_FOOT_W] 377 if left<0||right<0||left>=nj||right>=nj||left==right {return 0-81} 378 if order[left]!=1||order[right]!=1 {return 0-82} 379 let lo: i64=tidx[left*NA_IDX_W]; let ln: i64=tidx[left*NA_IDX_W+1] 380 let ro: i64=tidx[right*NA_IDX_W]; let rn: i64=tidx[right*NA_IDX_W+1] 381 if lo<0||ro<0||ln<2||rn<2||ln>capacity {return 0-83} 382 let lp: *i64=((scr as i64)+288) as *i64 383 let rp: *i64=((scr as i64)+336) as *i64 384 let prev: *i64=((scr as i64)+384) as *i64 385 var distance: i64=0; var previous_contact: i64=0-1 386 var changes: i64=0; var supported: i64=0; var unknown: i64=0 387 i=0 388 while i<ln { 389 let t: i64=na_key_t(anim,lo,i) 390 na_gait_sole(anim,lo,ln,skel,feet,t,rv,lp,scr) 391 na_gait_sole(anim,ro,rn,skel,((feet as i64)+NA_GAIT_FOOT_W*8) as *i64,t,rv,rp,scr) 392 var l: i64=0; if lp[5]<lp[2] {l=3} 393 var r: i64=0; if rp[5]<rp[2] {r=3} 394 var contact: i64=0; if rp[r+2]<lp[l+2] {contact=1} 395 if i>0 { 396 let dl: i64=lp[l+1]-prev[l+1] 397 let dr: i64=rp[r+1]-prev[6+r+1] 398 var travel: i64=dl 399 if contact==1 {travel=dr} 400 if travel<=0 {if contact==0&&dr>0 {contact=1;travel=dr} else {if contact==1&&dl>0 {contact=0;travel=dl}}} 401 if travel>0 {distance=distance+travel;supported=supported+1} else {unknown=unknown+1;contact=2} 402 if contact<2&&previous_contact>=0&&previous_contact<2&&contact!=previous_contact {changes=changes+1} 403 } 404 table[i*NA_GAIT_ROW_W]=t;table[i*NA_GAIT_ROW_W+NA_GAIT_DISTANCE]=distance;table[i*NA_GAIT_ROW_W+NA_GAIT_CONTACT]=contact 405 var k: i64=0;while k<6 {prev[k]=lp[k];prev[6+k]=rp[k];k=k+1} 406 previous_contact=contact;i=i+1 407 } 408 if distance<=0||supported==0 {return 0-84} 409 header[0]=ln;header[1]=distance;header[2]=changes;header[3]=supported;header[4]=unknown 410 header[5]=table[(ln-1)*NA_GAIT_ROW_W]-table[0] 411 header[6]=header[5]*NA_FIG_UNITS*100/(distance*NA_FIG_BLOCKS_CENTI) 412 header[7]=1 413 return 0 414} 415func na_gait_time(table: *i64,n: i64,distance: i64) -> i64 { 416 if n<2 {return 0} 417 let total: i64=table[(n-1)*NA_GAIT_ROW_W+NA_GAIT_DISTANCE] 418 if total<=0 {return 0} 419 var d: i64=distance%total;if d<0 {d=d+total} 420 var lo: i64=0;var hi: i64=n 421 while lo<hi {let mid:i64=lo+(hi-lo)/2;if table[mid*NA_GAIT_ROW_W+NA_GAIT_DISTANCE]<=d {lo=mid+1}else{hi=mid}} 422 var a:i64=lo-1;if a<0 {a=0};var b:i64=a+1;if b>=n {b=a} 423 let da:i64=table[a*NA_GAIT_ROW_W+NA_GAIT_DISTANCE];let db:i64=table[b*NA_GAIT_ROW_W+NA_GAIT_DISTANCE] 424 let ta:i64=table[a*NA_GAIT_ROW_W];let tb:i64=table[b*NA_GAIT_ROW_W] 425 if db<=da {return ta} 426 return ta+(tb-ta)*(d-da)/(db-da) 427}