code wiki / (root) / nx_concept_embedding.nx

nx_concept_embedding.nx source

↩ module page · 377 lines · 12294 B

1// nx_concept_embedding.nx -- typed concept manifest + storage contract. 2// 3// Closes the STORAGE half of the image-ingestion loop: 4// 5// nx_image_feature_extract N images -> N feature vectors 6// nx_image_feature_centroid N vectors -> centroid (concept signature) 7// nx_concept_embedding centroid + provenance -> manifest 8// <nishi-library writer> manifest -> content-addressed storage 9// nx_cosine_similarity new image vs stored concept -> match 10// nx_render_target_match rendered vs target -> verdict 11// 12// USE CASES (all three of user 2026-05-15's verticals): 13// 14// RETAIL VIRTUAL TRY-ON: 15// name = "lingerie_velvet_red_xs" 16// centroid = mean(features across 5 photos of the product) 17// source_hashes = sha256 of each product photo 18// n_examples = 5 19// Later prompt: "model wearing [concept:lingerie_velvet_red_xs]" 20// -> blend stored centroid into prompt embedding (IP-adapter path) 21// -> render with the actual product visualized 22// 23// VIDEO-GAME ASSET CATALOG: 24// name = "weapon_runic_greatsword" 25// centroid = mean(concept art frames) 26// -> in-engine asset gen conditioned on the concept 27// 28// STORY-CHARACTER REFERENCE: 29// name = "char_elara" 30// centroid = mean(prior renders the user approved) 31// -> future story renders condition on this concept for identity 32// stability across the arc 33// 34// MANIFEST STRUCTURE (canonical-bytes-serializable, content-addressable): 35// schema_version v1 forever; new version = new struct kind 36// name_buf + name_len concept's human-readable handle 37// centroid NX_IMAGE_FEATURE_LEN nx_int (Q10) cells 38// n_examples how many images contributed to the centroid 39// fidelity_q10 mean fidelity across the N source extracts 40// source_hash_buf 32 * n_examples bytes (sha256 per source img) 41// created_iso_buf optional ISO-8601 timestamp (caller-set) 42// 43// CONTENT-ADDRESSING: caller computes sha256 of canonical_bytes(manifest) 44// via the existing nx_sha256.nx primitive. The hash IS the concept's 45// global identifier in nishi-library's CAS store. Two callers 46// producing the same centroid from the same source images get the 47// same manifest hash -- substrate-level idempotency without 48// coordination. 49// 50// genealogy_id: gal_2022_textual_inversion + ye_2023_ip_adapter + 51// salton_1971_smart + ipfs_benet_2014 + 52// nix_dolstra_2004 53// lineage_id: concept_embedding_manifest_q10 54 55// nx_safety_envelope: 56// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 57// sil_target: SIL1 58// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 59// verdict: NOT_YET_EVALUATED 60 61import "nx_syscalls.nx" 62import "nx_tier.nx" 63import "nx_image_feature_extract.nx" 64import "nx_cosine_similarity.nx" 65 66const NX_CE_SCHEMA_VERSION: nx_int = 1 67const NX_CE_HASH_BYTES: nx_int = 32 // sha256 = 32 bytes 68const NX_CE_Q: nx_int = 1024 69 70// Maximum name length. Beyond this, the manifest is truncated (caller 71// must check name_len == requested len). 72const NX_CE_NAME_MAX: nx_int = 128 73 74// Match-quality bands when comparing two concept embeddings via 75// centroid cosine. Reuses the NX_STRSIM_*-style semantics but 76// concept-specific. 77const NX_CE_BAND_DISTINCT: nx_int = 0 78const NX_CE_BAND_LOOSE: nx_int = 1 79const NX_CE_BAND_RELATED: nx_int = 2 80const NX_CE_BAND_NEAR_DUPLICATE: nx_int = 3 81const NX_CE_BAND_IDENTICAL: nx_int = 4 82const NX_CE_N_BANDS: nx_int = 5 83 84struct ConceptEmbedding { 85 schema_version: nx_int, 86 name_buf: *u8, 87 name_len: nx_int, 88 centroid: *nx_int, // NX_IMAGE_FEATURE_LEN cells 89 n_examples: nx_int, 90 fidelity_q10: nx_int, 91 source_hash_buf: *u8, // 32 * n_examples bytes 92 created_iso_buf: *u8, // optional; may be empty 93 created_iso_len: nx_int, 94} 95 96// ===== Builder ======================================================== 97// 98// Caller allocates centroid + source_hash_buf (and optionally 99// created_iso_buf) and passes them in. The substrate doesn't own the 100// storage of the input buffers; it owns the struct cells. 101 102func nx_concept_embedding_build( 103 name_buf: *u8, name_len: nx_int, 104 centroid: *nx_int, 105 n_examples: nx_int, 106 fidelity_q10: nx_int, 107 source_hash_buf: *u8, 108 created_iso_buf: *u8, created_iso_len: nx_int, 109 out: *ConceptEmbedding 110) -> nx_int { 111 if name_len < 0 { return 1 } 112 if name_len > NX_CE_NAME_MAX { return 2 } 113 if n_examples < 0 { return 3 } 114 out.schema_version = NX_CE_SCHEMA_VERSION 115 out.name_buf = name_buf 116 out.name_len = name_len 117 out.centroid = centroid 118 out.n_examples = n_examples 119 out.fidelity_q10 = fidelity_q10 120 out.source_hash_buf = source_hash_buf 121 out.created_iso_buf = created_iso_buf 122 out.created_iso_len = created_iso_len 123 return 0 124} 125 126// ===== Canonical bytes =============================================== 127// 128// Emits a byte sequence the caller will sha256 to get the manifest's 129// content-address. Format: 130// 131// "ce.v1\n" 132// "name=" name_buf "\n" 133// "n_examples=" n_examples "\n" 134// "fidelity_q10=" fidelity_q10 "\n" 135// "centroid=" comma-separated nx_int values "\n" 136// "source_hashes=" hex-of-each-32-byte-hash separated by comma "\n" 137// "created=" created_iso_buf "\n" 138// 139// Caller supplies a buffer + max length; substrate fills it and 140// returns the byte count written, or -1 if the buffer is too small. 141 142func _ce_write_byte(buf: *u8, max: nx_int, pos: nx_int, b: u8) -> nx_int { 143 if pos >= max { return -1 } 144 buf[pos] = b 145 return pos + 1 146} 147 148func _ce_write_bytes(buf: *u8, max: nx_int, pos: nx_int, src: *u8, n: nx_int) -> nx_int { 149 var p: nx_int = pos 150 var i: nx_int = 0 151 while i < n { 152 if p >= max { return -1 } 153 buf[p] = src[i] 154 p = p + 1 155 i = i + 1 156 } 157 return p 158} 159 160// Write a decimal integer. Returns new pos or -1 on overflow. 161func _ce_write_int(buf: *u8, max: nx_int, pos: nx_int, v: nx_int) -> nx_int { 162 if v == 0 { 163 return _ce_write_byte(buf, max, pos, 48) 164 } 165 var n: nx_int = v 166 var neg: nx_int = 0 167 if n < 0 { 168 neg = 1 169 n = -n 170 } 171 // Count digits. 172 var digits: nx_int = 0 173 var tmp: nx_int = n 174 while tmp > 0 { 175 digits = digits + 1 176 tmp = tmp / 10 177 } 178 let total: nx_int = digits + neg 179 if pos + total > max { return -1 } 180 var p: nx_int = pos 181 if neg == 1 { 182 buf[p] = 45 // '-' 183 p = p + 1 184 } 185 // Write digits right-to-left into the slot. 186 var k: nx_int = p + digits - 1 187 while n > 0 { 188 let d: nx_int = n - (n / 10) * 10 189 buf[k] = 48 + d // ascii '0' + digit 190 n = n / 10 191 k = k - 1 192 } 193 return p + digits 194} 195 196// Hex-encode a byte to two ASCII chars. 197func _ce_hex_nibble(b: u8) -> u8 { 198 if b < 10 { return 48 + b } 199 return 87 + b // 'a' + (b - 10) 200} 201 202func _ce_write_hex(buf: *u8, max: nx_int, pos: nx_int, src: *u8, n: nx_int) -> nx_int { 203 var p: nx_int = pos 204 var i: nx_int = 0 205 while i < n { 206 if p + 2 > max { return -1 } 207 let hi: u8 = src[i] / 16 208 let lo: u8 = src[i] - hi * 16 209 buf[p] = _ce_hex_nibble(hi) 210 buf[p + 1] = _ce_hex_nibble(lo) 211 p = p + 2 212 i = i + 1 213 } 214 return p 215} 216 217func nx_concept_embedding_canonical_bytes( 218 m: *ConceptEmbedding, 219 out: *u8, max: nx_int 220) -> nx_int { 221 var p: nx_int = 0 222 223 // Header 224 let lit_hdr: *u8 = sys_mmap(8) 225 lit_hdr[0] = 99 // c 226 lit_hdr[1] = 101 // e 227 lit_hdr[2] = 46 // . 228 lit_hdr[3] = 118 // v 229 lit_hdr[4] = 49 // 1 230 lit_hdr[5] = 10 // \n 231 p = _ce_write_bytes(out, max, p, lit_hdr, 6) 232 if p < 0 { return -1 } 233 234 // name=<name>\n 235 let lit_name: *u8 = sys_mmap(8) 236 lit_name[0] = 110 // n 237 lit_name[1] = 97 // a 238 lit_name[2] = 109 // m 239 lit_name[3] = 101 // e 240 lit_name[4] = 61 // = 241 p = _ce_write_bytes(out, max, p, lit_name, 5) 242 if p < 0 { return -1 } 243 p = _ce_write_bytes(out, max, p, m.name_buf, m.name_len) 244 if p < 0 { return -1 } 245 p = _ce_write_byte(out, max, p, 10) 246 if p < 0 { return -1 } 247 248 // n_examples=<n>\n 249 let lit_n: *u8 = sys_mmap(16) 250 lit_n[0] = 110 // n 251 lit_n[1] = 95 // _ 252 lit_n[2] = 101 // e 253 lit_n[3] = 120 // x 254 lit_n[4] = 97 // a 255 lit_n[5] = 109 // m 256 lit_n[6] = 112 // p 257 lit_n[7] = 108 // l 258 lit_n[8] = 101 // e 259 lit_n[9] = 115 // s 260 lit_n[10] = 61 // = 261 p = _ce_write_bytes(out, max, p, lit_n, 11) 262 if p < 0 { return -1 } 263 p = _ce_write_int(out, max, p, m.n_examples) 264 if p < 0 { return -1 } 265 p = _ce_write_byte(out, max, p, 10) 266 if p < 0 { return -1 } 267 268 // centroid=<v0>,<v1>,...,<v15>\n 269 let lit_c: *u8 = sys_mmap(16) 270 lit_c[0] = 99 // c 271 lit_c[1] = 101 // e 272 lit_c[2] = 110 // n 273 lit_c[3] = 116 // t 274 lit_c[4] = 114 // r 275 lit_c[5] = 111 // o 276 lit_c[6] = 105 // i 277 lit_c[7] = 100 // d 278 lit_c[8] = 61 // = 279 p = _ce_write_bytes(out, max, p, lit_c, 9) 280 if p < 0 { return -1 } 281 var i: nx_int = 0 282 while i < NX_IMAGE_FEATURE_LEN { 283 if i > 0 { 284 p = _ce_write_byte(out, max, p, 44) // , 285 if p < 0 { return -1 } 286 } 287 p = _ce_write_int(out, max, p, m.centroid[i]) 288 if p < 0 { return -1 } 289 i = i + 1 290 } 291 p = _ce_write_byte(out, max, p, 10) 292 if p < 0 { return -1 } 293 294 // source_hashes=<hex>,<hex>...\n 295 let lit_sh: *u8 = sys_mmap(16) 296 lit_sh[0] = 115 // s 297 lit_sh[1] = 111 // o 298 lit_sh[2] = 117 // u 299 lit_sh[3] = 114 // r 300 lit_sh[4] = 99 // c 301 lit_sh[5] = 101 // e 302 lit_sh[6] = 115 // s 303 lit_sh[7] = 61 // = 304 p = _ce_write_bytes(out, max, p, lit_sh, 8) 305 if p < 0 { return -1 } 306 var j: nx_int = 0 307 while j < m.n_examples { 308 if j > 0 { 309 p = _ce_write_byte(out, max, p, 44) 310 if p < 0 { return -1 } 311 } 312 let off: nx_int = j * NX_CE_HASH_BYTES 313 // m.source_hash_buf is *u8; we need pointer to start + offset. 314 // Use a small loop that advances byte-by-byte (substrate doesn't 315 // support pointer arithmetic on *u8 in const-expr; we write hex 316 // by reading via index). 317 var bb: nx_int = 0 318 while bb < NX_CE_HASH_BYTES { 319 if p + 2 > max { return -1 } 320 let b: u8 = m.source_hash_buf[off + bb] 321 let hi: u8 = b / 16 322 let lo: u8 = b - hi * 16 323 out[p] = _ce_hex_nibble(hi) 324 out[p + 1] = _ce_hex_nibble(lo) 325 p = p + 2 326 bb = bb + 1 327 } 328 j = j + 1 329 } 330 p = _ce_write_byte(out, max, p, 10) 331 if p < 0 { return -1 } 332 333 // fidelity=<n>\n 334 let lit_f: *u8 = sys_mmap(16) 335 lit_f[0] = 102 // f 336 lit_f[1] = 105 // i 337 lit_f[2] = 100 // d 338 lit_f[3] = 101 // e 339 lit_f[4] = 108 // l 340 lit_f[5] = 105 // i 341 lit_f[6] = 116 // t 342 lit_f[7] = 121 // y 343 lit_f[8] = 61 // = 344 p = _ce_write_bytes(out, max, p, lit_f, 9) 345 if p < 0 { return -1 } 346 p = _ce_write_int(out, max, p, m.fidelity_q10) 347 if p < 0 { return -1 } 348 p = _ce_write_byte(out, max, p, 10) 349 if p < 0 { return -1 } 350 351 return p 352} 353 354// ===== Compare two concepts via centroid cosine ====================== 355// 356// Returns signed Q10 cosine + sealed-enum band classifier. 357 358func nx_concept_embedding_compare(a: *ConceptEmbedding, b: *ConceptEmbedding) -> nx_int { 359 return nx_cosine_similarity( 360 a.centroid, NX_IMAGE_FEATURE_LEN, 361 b.centroid, NX_IMAGE_FEATURE_LEN 362 ) 363} 364 365func nx_concept_embedding_classify(similarity_q10: nx_int) -> nx_int { 366 if similarity_q10 < 256 { return NX_CE_BAND_DISTINCT } 367 if similarity_q10 < 512 { return NX_CE_BAND_LOOSE } 368 if similarity_q10 < 768 { return NX_CE_BAND_RELATED } 369 if similarity_q10 < 921 { return NX_CE_BAND_NEAR_DUPLICATE } 370 return NX_CE_BAND_IDENTICAL 371} 372 373func nx_concept_embedding_band_is_valid(band: nx_int) -> nx_int { 374 if band < 0 { return 0 } 375 if band >= NX_CE_N_BANDS { return 0 } 376 return 1 377}