code wiki / (root) / nx_kkfacts_lib.nx

nx_kkfacts_lib.nx source

↩ module page · 169 lines · 8653 B

1// nx_kkfacts_lib.nx -- KOIKATSU CARD FACT EXTRACTION CORE (the Honey Select / Koikatsu card 2// study's first organ; benchmark card KK_396262 measured 2026-08-04). 3// 4// A Koikatsu card is a PNG portrait with the CHARACTER DEFINITION appended after IEND: the 5// KoiKatuChara marker, then MessagePack block structures (Custom = face/body/hair, Coordinate = 6// outfits, Parameter, Status, KKEx = modded extended data). This core reads STRUCTURE ONLY: 7// portrait bounds + dims, payload size, marker, block-name census. Slider values and names stay 8// unread in v1 -- the deep (msgpack) rung comes with the k>=8 card study, and per-card creative 9// content never enters the published plane (one card = one creator; the k-wall exists for this). 10// 11// ★THE CARD FORMAT CARRIES NO MOTION: coordinates are OUTFITS, not animations -- motion lives in 12// the game engine, not the artifact. A fact this lane's compare page states outright. 13// Slots: [0] is_card [1] portrait_bytes [2] portrait_w [3] portrait_h [4] payload_bytes 14// [5] custom [6] coordinate [7] parameter [8] status [9] kkex (-1 = not measured) 15// license_tier: ORIGINAL No hw writes (Rule 26). 16import "nx_syscalls.nx" 17import "nx_img_dims.nx" 18const KK_MAGIC_4096: i64 = 4096 19 20const KK_N_SLOTS: i64 = 10 21 22func kk_b(buf: *u8, o: i64) -> i64 { return buf[o] as i64 & 0xff } 23func kk_find(buf: *u8, start: i64, end: i64, needle: *u8) -> i64 { 24 var m: i64 = 0 25 while needle[m] != (0 as u8) { m = m + 1 } 26 var i: i64 = start 27 while i + m <= end { 28 var k: i64 = 0 29 while k < m { if (buf[i+k] as i64) != (needle[k] as i64) { k = m + 9 } else { k = k + 1 } } 30 if k == m { return i } 31 i = i + 1 32 } 33 return 0 - 1 34} 35func kk_count(buf: *u8, start: i64, end: i64, needle: *u8) -> i64 { 36 var m: i64 = 0 37 while needle[m] != (0 as u8) { m = m + 1 } 38 var c: i64 = 0 39 var i: i64 = start 40 while i + m <= end { 41 var k: i64 = 0 42 while k < m { if (buf[i+k] as i64) != (needle[k] as i64) { k = m + 9 } else { k = k + 1 } } 43 if k == m { c = c + 1; i = i + m } else { i = i + 1 } 44 } 45 return c 46} 47 48// probe core: buf holds the first n bytes; file_size = true size (truncation honesty). The DIALECT 49// is the marker parameter -- one ruler for every Illusion card family, each public probe a thin 50// wrapper naming its measured marker (rule 3: the second dialect arrived 2026-08-18 and a copied 51// body would already have drifted). 52func kk_probe_marker(buf: *u8, n: i64, file_size: i64, facts: *i64, marker: *u8) -> i64 { 53 var i: i64 = 0 54 while i < KK_N_SLOTS { facts[i] = 0 - 1; i = i + 1 } 55 facts[0] = 0 56 if n < 64 { return 0 } 57 if kk_b(buf,0) != 0x89 { return 0 } 58 if kk_b(buf,1) != 0x50 { return 0 } 59 if kk_b(buf,2) != 0x4e { return 0 } 60 if kk_b(buf,3) != 0x47 { return 0 } 61 // first IEND = the portrait's end; the chara payload begins right after IEND's CRC (+8) 62 let iend: i64 = kk_find(buf, 8, n, "IEND" as *u8) 63 if iend < 0 { return 0 } 64 let pend: i64 = iend + 8 65 if pend >= file_size { return 0 } // nothing after the portrait: a plain PNG 66 // the marker sits in the first KB of the payload 67 var scan_end: i64 = pend + KK_MAGIC_4096 68 if scan_end > n { scan_end = n } 69 if kk_find(buf, pend, scan_end, marker) < 0 { return 0 } 70 facts[0] = 1 71 facts[1] = pend 72 let wh: *i64 = sys_mmap(16) as *i64 73 if nx_img_dims(buf, n, wh) == 1 { facts[2] = wh[0]; facts[3] = wh[1] } 74 facts[4] = file_size - pend 75 // block census only over a COMPLETE payload -- a truncated count is an undercount 76 if file_size <= n { 77 facts[5] = kk_count(buf, pend, n, "Custom" as *u8) 78 facts[6] = kk_count(buf, pend, n, "Coordinate" as *u8) 79 facts[7] = kk_count(buf, pend, n, "Parameter" as *u8) 80 facts[8] = kk_count(buf, pend, n, "Status" as *u8) 81 facts[9] = kk_count(buf, pend, n, "KKEx" as *u8) 82 } 83 return 0 84} 85func kk_probe(buf: *u8, n: i64, file_size: i64, facts: *i64) -> i64 { 86 return kk_probe_marker(buf, n, file_size, facts, "KoiKatuChara" as *u8) 87} 88// AI SHOUJO / HONEY SELECT 2 (the third Illusion family; watch contract kk_probe_aishoujo on 89// /compare/koikatsu). GROUNDED FROM THE BANKED FIXTURE, never guessed: knowledge/library/bepis/ 90// AI_248895.png (sha f0511393..., byte-equal to the BepisDB API's declared content pin) carries 91// after IEND+8: length bytes, then the CJK-bracketed marker U+3010 AIS_Chara U+3011, version 92// "1.0.0", two GUIDs, then a msgpack lstInfo naming KKEx/Custom/Coordinate/Parameter blocks -- 93// the same msgpack machinery the slider decoder below speaks. The needle is the ASCII core 94// "AIS_Chara" (the brackets are multi-byte UTF-8; the core measured at IEND+13 on the fixture, 95// unique in the payload head). Status may honestly census 0 in this family. 96func kk_probe_aishoujo(buf: *u8, n: i64, file_size: i64, facts: *i64) -> i64 { 97 return kk_probe_marker(buf, n, file_size, facts, "AIS_Chara" as *u8) 98} 99 100// ---- SLIDER VALUE DECODE (2026-08-18, operator: "i have cards but they do nothing" -- the 101// kk_decode_sliders watch contract published on /compare/koikatsu). The Custom block serializes its 102// shape sliders as MessagePack float32 arrays under keys like shapeValueFace / shapeValueBody: the 103// key is a fixstr whose bytes sit verbatim in the payload, and the VALUE follows immediately after 104// the key bytes -- a fixarray (0x90+n) or array16 (0xdc + BE16 count) of float32 elements 105// (0xca + 4 BE bytes). Each element converts to PERMIL by integer mantissa/exponent shift 106// arithmetic (the g2_f32mm idiom from nx_gltf2mesh -- no float unit in the path). 107// FAIL-CLOSED + HONEST-PARTIAL: missing key -> meta[0]=-1, parsed=0; no array header -> meta[0]=-2; 108// a non-float element STOPS the parse so parsed<declared is visible, never papered over. 109// K-WALL HOLDS: decoded values are for the caller's stdout/library use -- per-card creative 110// content never enters a published plane. 111const KK_MP_FIXARRAY_BASE: i64 = 0x90 // msgpack fixarray tag; low nibble carries the count 112const KK_MP_TAG_HI_MASK: i64 = 0xf0 113const KK_MP_TAG_LO_MASK: i64 = 0x0f 114const KK_MP_ARRAY16: i64 = 0xdc // BE16 count follows 115const KK_MP_F32: i64 = 0xca // 4 BE bytes follow 116const KK_MP_F32_SPAN: i64 = 5 // tag + 4 payload bytes 117const KK_F32_EXP_BIAS: i64 = 127 118const KK_F32_EXP_MASK: i64 = 255 119const KK_F32_MANT_BITS: i64 = 23 120const KK_F32_MANT_MASK: i64 = 8388607 121const KK_F32_MANT_ONE: i64 = 8388608 122const KK_PERMIL: i64 = 1000 123const KK_SHIFT_SAFE_HI: i64 = 30 // beyond this a left-shift of mant*1000 overflows i64 headroom 124const KK_SHIFT_SAFE_LO: i64 = 62 // beyond this a right-shift underflows to 0 anyway 125 126func kk_f32_permil(w: i64) -> i64 { 127 let sign: i64 = (w >> 31) & 1 128 let expo: i64 = (w >> KK_F32_MANT_BITS) & KK_F32_EXP_MASK 129 if expo == 0 { return 0 } 130 let mant: i64 = (w & KK_F32_MANT_MASK) | KK_F32_MANT_ONE 131 let sh: i64 = expo - KK_F32_EXP_BIAS 132 var v: i64 = 0 133 if sh >= KK_F32_MANT_BITS { if sh - KK_F32_MANT_BITS > KK_SHIFT_SAFE_HI { return 0 } } 134 if sh >= KK_F32_MANT_BITS { v = mant * KK_PERMIL * (1 << (sh - KK_F32_MANT_BITS)) } 135 if sh < KK_F32_MANT_BITS { if KK_F32_MANT_BITS - sh > KK_SHIFT_SAFE_LO { return 0 } } 136 if sh < KK_F32_MANT_BITS { v = (mant * KK_PERMIL) >> (KK_F32_MANT_BITS - sh) } 137 if sign == 1 { return 0 - v } 138 return v 139} 140 141// find <key> in [start,end); parse the msgpack float32 array that follows into out (permil). 142// returns parsed count; meta[0] = declared count, or -1 key absent, -2 no array header after key. 143func kk_decode_sliders(buf: *u8, start: i64, end: i64, key: *u8, out: *i64, outcap: i64, meta: *i64) -> i64 { 144 meta[0] = 0 - 1 145 let at: i64 = kk_find(buf, start, end, key) 146 if at < 0 { return 0 } 147 var m: i64 = 0 148 while key[m] != (0 as u8) { m = m + 1 } 149 var p: i64 = at + m 150 if p >= end { meta[0] = 0 - 2; return 0 } 151 var n: i64 = 0 - 1 152 let tag: i64 = kk_b(buf, p) 153 if (tag & KK_MP_TAG_HI_MASK) == KK_MP_FIXARRAY_BASE { n = tag & KK_MP_TAG_LO_MASK; p = p + 1 } 154 if tag == KK_MP_ARRAY16 { if p + 3 <= end { n = kk_b(buf,p+1)*256 + kk_b(buf,p+2); p = p + 3 } } 155 if n < 0 { meta[0] = 0 - 2; return 0 } 156 meta[0] = n 157 var k: i64 = 0 158 while k < n { 159 if p + KK_MP_F32_SPAN > end { return k } 160 if kk_b(buf, p) != KK_MP_F32 { return k } 161 if k < outcap { 162 let w: i64 = (kk_b(buf,p+1) << 24) | (kk_b(buf,p+2) << 16) | (kk_b(buf,p+3) << 8) | kk_b(buf,p+4) 163 out[k] = kk_f32_permil(w) 164 } 165 p = p + KK_MP_F32_SPAN 166 k = k + 1 167 } 168 return k 169}