code wiki / (root) / nx_kkfacts_lib.nx

nx_kkfacts_lib.nx source

↩ module page · 81 lines · 3701 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: buf holds the first n bytes; file_size = true size (truncation honesty). 49func kk_probe(buf: *u8, n: i64, file_size: i64, facts: *i64) -> i64 { 50 var i: i64 = 0 51 while i < KK_N_SLOTS { facts[i] = 0 - 1; i = i + 1 } 52 facts[0] = 0 53 if n < 64 { return 0 } 54 if kk_b(buf,0) != 0x89 { return 0 } 55 if kk_b(buf,1) != 0x50 { return 0 } 56 if kk_b(buf,2) != 0x4e { return 0 } 57 if kk_b(buf,3) != 0x47 { return 0 } 58 // first IEND = the portrait's end; the chara payload begins right after IEND's CRC (+8) 59 let iend: i64 = kk_find(buf, 8, n, "IEND" as *u8) 60 if iend < 0 { return 0 } 61 let pend: i64 = iend + 8 62 if pend >= file_size { return 0 } // nothing after the portrait: a plain PNG 63 // the marker sits in the first KB of the payload 64 var scan_end: i64 = pend + KK_MAGIC_4096 65 if scan_end > n { scan_end = n } 66 if kk_find(buf, pend, scan_end, "KoiKatuChara" as *u8) < 0 { return 0 } 67 facts[0] = 1 68 facts[1] = pend 69 let wh: *i64 = sys_mmap(16) as *i64 70 if nx_img_dims(buf, n, wh) == 1 { facts[2] = wh[0]; facts[3] = wh[1] } 71 facts[4] = file_size - pend 72 // block census only over a COMPLETE payload -- a truncated count is an undercount 73 if file_size <= n { 74 facts[5] = kk_count(buf, pend, n, "Custom" as *u8) 75 facts[6] = kk_count(buf, pend, n, "Coordinate" as *u8) 76 facts[7] = kk_count(buf, pend, n, "Parameter" as *u8) 77 facts[8] = kk_count(buf, pend, n, "Status" as *u8) 78 facts[9] = kk_count(buf, pend, n, "KKEx" as *u8) 79 } 80 return 0 81}