code wiki / _hdl_build / nx_card_compile.nx
nx_card_compile.nx
buildroot/runtime/_hdl_build/nx_card_compile.nx
about
nx_card_compile.nx -- CompanionCard COMPILER (F917 rung-1, "first byte up"): elara_card.json (the character
sheet SSOT) compiles DOWNWARD into elara_persona.txt, which the live gen daemon re-reads per request = the
card becomes the byte-one source of who she is, no daemon deploy needed. Fail-closed: age < 18 REFUSES
(adult-companion invariant enforced at the compiler, not by convention). Rule-13: the pre-card persona is
banked ONCE to elara_persona.txt.bak-precard. Write is ATOMIC (tmp + rename; the daemon never sees a torn
file). Composition keeps the battle-tested persona laws VERBATIM (never-AI, sends pics, EN-lock, first
person, no stage directions) -- the card supplies the variable identity/personality half.
usage: nx_card_compile [dir] (dir default /volume1/ai/gen; reads elara_card.json, writes elara_persona.txt)
nx_card_compile import <card.png> [age=N] [dir] (CC2: a Tavern V2 PNG card -> the SSOT, then compile)
nx_card_compile export <dir> <out.json> (CC2: the imported V2 source back out, byte-identical)
CC2 COMMUNITY CARD IMPORT (companionchat, 2026-08-30): a Tavern V2 card is base64 JSON in a tEXt chunk keyed `chara`
(nx_png_chunks walks it, base64.nx decodes it). Every V2 field is MAPPED into the SSOT or REFUSED BY NAME in a printed
ledger -- never silently dropped; the decoded JSON is kept VERBATIM as elara_card.v2.json so `export` round-trips
byte-stable by construction; the age gate stays fail-closed -- a V2 card carries no age field, so import REFUSES unless
the card declares one or the operator passes age=N, and N < 18 refuses exactly as the native compiler does. An imported
card composes from its own description/personality/scenario and NEVER inherits the native defaults (hair, eyes,
relationship) -- those are Elara's, not the imported character's.
license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
dependencies 3 imports · 0 importers
imports: nx_syscalls.nxnx_png_chunks.nxbase64.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 22 | const CC_MAGIC_1024: i64 = 1024 |
| 24 | const CC_OUTCAP: i64 = 65536 |
| 25 | const CC_STRCAP: i64 = 2048 |
| 26 | const CC_PATHCAP: i64 = 512 |
| 27 | const CC_MODE: i64 = 420 |
| 28 | const CC_MIN_AGE: i64 = 18 |
| 29 | const CC_NONE: i64 = 0 - 1 |
| 31 | const CC_V2CAP: i64 = 16384 |
| 32 | const CC_V2_FIELDS: i64 = 12 |
| 33 | const CC_LEDGER_CAP: i64 = 4096 |
| 34 | const CC_EXIT_USAGE: i64 = 2 |
| 35 | const CC_EXIT_REFUSED: i64 = 3 |
| 36 | const CC_EXIT_IO: i64 = 4 |
| 37 | const CC_CH_QUOTE: i64 = 34 |
| 38 | const CC_CH_BSLASH: i64 = 92 |
| 39 | const CC_CH_SPACE: i64 = 32 |
| 40 | const CC_CH_NL: i64 = 10 |
| 41 | const CC_CH_COMMA: i64 = 44 |
| 42 | const CC_CH_0: i64 = 48 |
| 43 | const CC_CH_9: i64 = 57 |
| 44 | const CC_DEC_BASE: i64 = 10 |
| 45 | const CC_AGE_ARG: *u8 = "age=" as *u8 |
| 46 | const CC_V2_SPEC: *u8 = "chara_card_v2" as *u8 |
| 47 | const CC_V2_KEYWORD: *u8 = "chara" as *u8 |
| 48 | const CC_SSOT: *u8 = "elara_card.json" as *u8 |
| 49 | const CC_SSOT_BAK: *u8 = "elara_card.json.bak-preimport" as *u8 |
| 50 | const CC_V2_SRC: *u8 = "elara_card.v2.json" as *u8 |
| 262 | const CC_CH_DOT: i64 = 46 |
| 263 | const CC_CH_BANG: i64 = 33 |
| 264 | const CC_CH_QMARK: i64 = 63 |
functions
| 52 | func ccm_cat(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64 = off; var i: i64 = 0; while s[i] != (0 as u8) { dst[o] = s[i]; o = o + 1; i = i + 1 } return o } |
| 53 | func ccm_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 54 | func ccm_catn(d: *u8, o: i64, v: i64) -> i64 { let t: *u8 = sys_mmap(28); var m: i64 = v; if m < 0 { d[o] = 45 as u8; o = o + 1; m = 0 - m } var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } var i: i64 = 0; while i < k { d[o] = t[k-1-i]; o = o + 1; i = i + 1 } return o } |
| 55 | func ccm_puts(s: *u8) -> i64 { sys_write(1, s, ccm_len(s)); return 0 } |
| 56 | func ccm_eq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] == b[i] { if a[i] == (0 as u8) { return 1 } i = i + 1 } return 0 } |
| 58 | func ccm_pathjoin(dst: *u8, dir: *u8, name: *u8) -> i64 |
| 68 | func ccm_find(b: *u8, n: i64, pat: *u8) -> i64 |
| 84 | func ccm_key_at(b: *u8, n: i64, key: *u8) -> i64 |
| 96 | func ccm_json_str(b: *u8, n: i64, key: *u8, out: *u8, cap: i64) -> i64 |
| 126 | func ccm_json_int(b: *u8, n: i64, key: *u8) -> i64 |
| 148 | func ccm_json_arr(b: *u8, n: i64, key: *u8, out: *u8, cap: i64) -> i64 |
| 186 | func ccm_first_seg(s: *u8, out: *u8, cap: i64) -> i64 called by 1: ccm_compile |
| 199 | func ccm_age_words(age: i64, d: *u8, off: i64) -> i64 |
| 212 | func ccm_emit_jstr(dst: *u8, off: i64, s: *u8) -> i64 |
| 229 | func ccm_write_atomic(path: *u8, body: *u8, n: i64) -> i64 |
| 242 | func ccm_bank_once(dir: *u8, name: *u8, bakname: *u8) -> i64 called by 2: ccm_compileccm_card_import calls 6: sys_mmapccm_pathjoinsys_read_filesys_openat_wrsys_writesys_close |
| 265 | func ccm_cat_prose(dst: *u8, off: i64, s: *u8) -> i64 |
| 278 | func ccm_compile(dir: *u8) -> i64 |
| 396 | func ccm_ledger(verdict: *u8, field: *u8, note: *u8) -> i64 |
| 407 | func ccm_map_str(json: *u8, jn: i64, key: *u8, dst: *u8, off: i64, present: *i64) -> i64 |
| 420 | func ccm_refuse_field(json: *u8, jn: i64, key: *u8, why: *u8, refused: *i64) -> i64 |
| 429 | func ccm_card_import(png_path: *u8, age_arg: i64, dir: *u8) -> i64 |
| 546 | func ccm_card_export(dir: *u8, out_path: *u8) -> i64 |
| 563 | func ccm_parse_age_arg(s: *u8) -> i64 |
| 578 | func main(argc: i64, argv: *i64) -> i64 |