nx_mvault_tag.nx source
↩ module page · 101 lines · 3853 B
1// nx_mvault_tag.nx -- extensible, searchable metadata planes for vault items.
2//
3// DESIGN (operator 2026-07-17: "hammer searchability + extensibility BEFORE the
4// heavy reclassify ... performer/pose/body-type ... booru but far beyond ...
5// lossless language aka hip-to-waist ratios ... feed the future joy classifier"):
6// three ADDITIVE planes over the immutable core record, so new auto-tag
7// capabilities slot in with ZERO schema migration --
8// FACETS numeric fields (size/recency/popularity/dims) -> SORT KEYS for
9// fast large/recent/popular finding (byte-lex order == numeric).
10// TAGS booru-style OPEN namespaces "ns:value" (performer:/pose:/hair:/
11// body:/ethnicity:/nipple:/cup:...) -> inverted index per namespace.
12// ATTRS lossless quantitative "key=ivalue/scale" (hip_waist_ratio=700/1000
13// = exact 0.700, no-float) -> range-searchable. The booru-beyond bit.
14//
15// All CID-keyed + additive; searchability rides the librarian info-finding index
16// (coordinate, don't rebuild). This module is deliberately OPEN -- it hardcodes
17// NO tag list, so performer/pose/body/etc. are just data. license_tier: ORIGINAL
18
19import "nx_syscalls.nx"
20
21// append unsigned decimal of v to dst[off..]; returns new offset.
22func mv_u_dec(dst: *u8, off: i64, v: i64) -> i64 {
23 if v == 0 { dst[off] = 48 as u8; return off + 1 }
24 let tmp: *u8 = sys_mmap(24)
25 var m: i64 = v
26 var k: i64 = 0
27 while m > 0 { tmp[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
28 var o: i64 = off
29 var j: i64 = k - 1
30 while j >= 0 { dst[o] = tmp[j]; o = o + 1; j = j - 1 }
31 return o
32}
33
34// ===== TAGS: booru-style namespaced, OPEN =====
35// "ns:value" -> out (NUL-terminated); returns length.
36func mv_tag_make(out: *u8, ns: *u8, value: *u8) -> i64 {
37 var o: i64 = 0
38 var i: i64 = 0
39 while ns[i] != (0 as u8) { out[o] = ns[i]; o = o + 1; i = i + 1 }
40 out[o] = 58 as u8; o = o + 1 // ':'
41 i = 0
42 while value[i] != (0 as u8) { out[o] = value[i]; o = o + 1; i = i + 1 }
43 out[o] = 0 as u8
44 return o
45}
46
47// length of the namespace (chars before ':'); -1 if untagged.
48func mv_tag_ns_len(tag: *u8) -> i64 {
49 var i: i64 = 0
50 while tag[i] != (0 as u8) {
51 if tag[i] == (58 as u8) { return i }
52 i = i + 1
53 }
54 return 0 - 1
55}
56
57// ===== ATTRS: lossless quantitative =====
58// "key=ivalue/scale"; the exact value is ivalue/scale (scale keeps decimals,
59// so nothing is rounded away -- "lossless").
60func mv_attr_make(out: *u8, key: *u8, ivalue: i64, scale: i64) -> i64 {
61 var o: i64 = 0
62 var i: i64 = 0
63 while key[i] != (0 as u8) { out[o] = key[i]; o = o + 1; i = i + 1 }
64 out[o] = 61 as u8; o = o + 1 // '='
65 o = mv_u_dec(out, o, ivalue)
66 out[o] = 47 as u8; o = o + 1 // '/'
67 o = mv_u_dec(out, o, scale)
68 out[o] = 0 as u8
69 return o
70}
71
72// ===== FACETS: sortable keys for large/recent/popular =====
73// "<field>|" + 8 big-endian bytes of uvalue => byte-lexicographic order equals
74// numeric order, so a byte-sorted index answers range + top-N. Binary key (may
75// contain NUL); caller stores with the returned length.
76func mv_facet_key(out: *u8, field: *u8, uvalue: i64) -> i64 {
77 var o: i64 = 0
78 var i: i64 = 0
79 while field[i] != (0 as u8) { out[o] = field[i]; o = o + 1; i = i + 1 }
80 out[o] = 124 as u8; o = o + 1 // '|'
81 var s: i64 = 56
82 while s >= 0 {
83 out[o] = ((uvalue >> s) & 0xff) as u8
84 o = o + 1
85 s = s - 8
86 }
87 return o
88}
89
90// byte-compare two equal-length buffers: -1 / 0 / 1
91func mv_bcmp(a: *u8, b: *u8, n: i64) -> i64 {
92 var i: i64 = 0
93 while i < n {
94 let av: i64 = a[i]
95 let bv: i64 = b[i]
96 if av < bv { return 0 - 1 }
97 if av > bv { return 1 }
98 i = i + 1
99 }
100 return 0
101}