code wiki / (root) / nx_fb_perceptual.nx

nx_fb_perceptual.nx source

↩ module page · 231 lines · 10693 B

1// nx_fb_perceptual.nx -- per-perceptual-profile gamut packer. 2// 3// Additive overlay on the existing nx_framebuffer.nx (2026-05-17, 4// preserved per Cardinal 13). Adds the per-species color packers 5// that close the gap [[feedback-multi-species-perceptual-substrate-not-anthropocentric]] 6// names: the substrate must never bake in human-trichromat sRGB 7// as the default. Same input RGBA produces DIFFERENT packed bytes 8// per perceptual profile, and the KAT in nx_fb_perceptual_test.nx 9// asserts that parametricism is real (not cosmetic). 10// 11// Composes: 12// [[NISHI_GRAPHICS_ROADMAP]] G1 (this is one of the first stones) 13// [[feedback-multi-species-perceptual-substrate-not-anthropocentric]] 14// (the cardinal that drives non-human profiles) 15// [[feedback-bits-up-exceed-never-match]] (no libm, integer-only; 16// refuses sRGB-as-default that DirectX/Vulkan/Metal carry) 17// [[feedback-no-false-ok-substrate-honesty-audit]] (unknown profile 18// returns HUMAN_NORMAL packed bytes deterministically; never 19// silently corrupts; KAT asserts cross-profile differentiation) 20// existing nx_framebuffer.nx (2026-05-17 nx_fb_pack_rgba lineage -- 21// this is its multi-species overlay; HUMAN_NORMAL packer here 22// intentionally returns the same bytes as nx_fb_pack_rgba so the 23// compose surface is congruent) 24// 25// Scope (honest, G1): 26// - Per-profile PACKERS for HUMAN / DOG (dichromat) / HONEYBEE / 27// AVIAN-TETRACHROMAT / PRESERVE_ALL. 28// - Storage is still RGBA8888 i64 cells per the existing framebuffer. 29// G2 will introduce per-profile storage formats; G1 demonstrates 30// the packer API parametricism without storage-format churn. 31// - UV channel synthesis for honeybee + avian is PLACEHOLDER (255-R) 32// called out honestly here; G2+ real UV scene sampling replaces it. 33// 34// Reference (absorbed clean-room, no code copied): 35// IEC 61966-2-1 (sRGB) for HUMAN_NORMAL byte order baseline. 36// Rec. 601 luminance coefficients (77/150/29 over 256) for dog 37// dichromat Y' channel synthesis. 38// Jacobs 1993 "The Distribution and Nature of Colour Vision Among 39// Mammals" -- per-species photoreceptor count source. 40// Goldsmith 1990 "Optimization, constraint, and history in the 41// evolution of eyes" -- avian tetrachromat structure. 42// 43// genealogy_id: nishi-graphics-g1-perceptual + iec_61966_2_1_srgb + 44// rec_601_luma + jacobs_1993_mammal_color_vision + 45// goldsmith_1990_avian_tetrachromat 46// lineage_id: substrate_fb_perceptual_v1_bits_up_integer 47 48// nx_safety_envelope: 49// intended_use: "per-perceptual-profile gamut packer; same 50// RGBA input -> different packed bytes per 51// profile; substrate-honest unknown-profile 52// fallback to HUMAN_NORMAL; integer-only, 53// no libm dependency" 54// sil_target: SIL2 55// evidence: [kat_cross_profile_differentiation, 56// no_libm_no_skia, 57// unknown_profile_deterministic_fallback] 58// hazard_register: [bug-tape-uv-channel-placeholder-honest, 59// bug-tape-dichromat-luma-coefficient-source, 60// bug-tape-profile-aliased-bits] 61// verdict: NOT_YET_EVALUATED 62 63import "nx_syscalls.nx" 64import "nx_perceptual_profile.nx" 65const K_MAGIC_65536: i64 = 65536 66const K_MAGIC_16777216: i64 = 16777216 67const K_MAGIC_4294967296: i64 = 4294967296 68const K_MAGIC_281474976710656: i64 = 281474976710656 69 70// ===== HUMAN_NORMAL packer ======================================== 71// 72// Congruent with the existing nx_framebuffer.nx nx_fb_pack_rgba helper 73// so HUMAN_NORMAL through this dispatcher equals the existing 74// framebuffer's pack output byte-for-byte. We re-derive (not call) 75// to keep nx_fb_perceptual.nx import-graph small and to make the 76// per-profile inlining symmetric across all packers. 77func nx_fb_pack_human_normal(r: i64, g: i64, b: i64, a: i64) -> i64 { 78 var rr: i64 = r 79 if rr < 0 { rr = 0 } 80 if rr > 255 { rr = 255 } 81 var gg: i64 = g 82 if gg < 0 { gg = 0 } 83 if gg > 255 { gg = 255 } 84 var bb: i64 = b 85 if bb < 0 { bb = 0 } 86 if bb > 255 { bb = 255 } 87 var aa: i64 = a 88 if aa < 0 { aa = 0 } 89 if aa > 255 { aa = 255 } 90 return rr + gg * 256 + bb * K_MAGIC_65536 + aa * K_MAGIC_16777216 91} 92 93// ===== DOG dichromat packer ======================================= 94// 95// Two photoreceptors (yellow ~555nm and blue ~430nm). Y is Rec. 601 96// integer luminance (77*R + 150*G + 29*B + 128) / 256, fitting in 97// 8 bits. B is the blue channel as-is. Alpha discarded; the 98// dichromat profile does not carry premultiplied alpha at this stage. 99// bits 0..7 = Y' 100// bits 8..15 = B 101// bits 16..63 = 0 102func nx_fb_pack_dog_dichromat(r: i64, g: i64, b: i64, a: i64) -> i64 { 103 var rr: i64 = r 104 if rr < 0 { rr = 0 } 105 if rr > 255 { rr = 255 } 106 var gg: i64 = g 107 if gg < 0 { gg = 0 } 108 if gg > 255 { gg = 255 } 109 var bb: i64 = b 110 if bb < 0 { bb = 0 } 111 if bb > 255 { bb = 255 } 112 var y_num: i64 = 77 * rr + 150 * gg + 29 * bb + 128 113 var y: i64 = y_num / 256 114 if y > 255 { y = 255 } 115 if y < 0 { y = 0 } 116 return y + bb * 256 117} 118 119// ===== HONEYBEE packer ============================================ 120// 121// Three photoreceptors: green, blue, UV. No red sensitivity. 122// G1 honest placeholder: UV synthesized as (255 - R). G2+ replaces 123// with real scene UV sampling. 124// bits 0..7 = G 125// bits 8..15 = B 126// bits 16..23 = UV (synthesized: 255 - R) 127// bits 24..31 = A 128func nx_fb_pack_honeybee(r: i64, g: i64, b: i64, a: i64) -> i64 { 129 var rr: i64 = r 130 if rr < 0 { rr = 0 } 131 if rr > 255 { rr = 255 } 132 var gg: i64 = g 133 if gg < 0 { gg = 0 } 134 if gg > 255 { gg = 255 } 135 var bb: i64 = b 136 if bb < 0 { bb = 0 } 137 if bb > 255 { bb = 255 } 138 var aa: i64 = a 139 if aa < 0 { aa = 0 } 140 if aa > 255 { aa = 255 } 141 let uv: i64 = 255 - rr 142 return gg + bb * 256 + uv * K_MAGIC_65536 + aa * K_MAGIC_16777216 143} 144 145// ===== AVIAN tetrachromat packer ================================== 146// 147// Four photoreceptors: R + G + B + UV. G1 honest placeholder for UV 148// is (255 - R). G2+ real scene UV. 149// bits 0..7 = R 150// bits 8..15 = G 151// bits 16..23 = B 152// bits 24..31 = UV (synthesized: 255 - R) 153func nx_fb_pack_avian_tetrachromat(r: i64, g: i64, b: i64, a: i64) -> i64 { 154 var rr: i64 = r 155 if rr < 0 { rr = 0 } 156 if rr > 255 { rr = 255 } 157 var gg: i64 = g 158 if gg < 0 { gg = 0 } 159 if gg > 255 { gg = 255 } 160 var bb: i64 = b 161 if bb < 0 { bb = 0 } 162 if bb > 255 { bb = 255 } 163 let uv: i64 = 255 - rr 164 return rr + gg * 256 + bb * K_MAGIC_65536 + uv * K_MAGIC_16777216 165} 166 167// ===== PRESERVE_ALL packer ======================================== 168// 169// "No profile declared" substrate default. 16 bits per channel 170// (expanded 8->16 by byte-replication so a downstream profile-aware 171// final-render stage can collapse precision deterministically). 172// bits 0..15 = R16 (low byte = high byte = R) 173// bits 16..31 = G16 174// bits 32..47 = B16 175// bits 48..63 = A16 176func nx_fb_pack_preserve_all(r: i64, g: i64, b: i64, a: i64) -> i64 { 177 var rr: i64 = r 178 if rr < 0 { rr = 0 } 179 if rr > 255 { rr = 255 } 180 var gg: i64 = g 181 if gg < 0 { gg = 0 } 182 if gg > 255 { gg = 255 } 183 var bb: i64 = b 184 if bb < 0 { bb = 0 } 185 if bb > 255 { bb = 255 } 186 var aa: i64 = a 187 if aa < 0 { aa = 0 } 188 if aa > 255 { aa = 255 } 189 let r16: i64 = rr * 256 + rr 190 let g16: i64 = gg * 256 + gg 191 let b16: i64 = bb * 256 + bb 192 let a16: i64 = aa * 256 + aa 193 return r16 + g16 * K_MAGIC_65536 + b16 * K_MAGIC_4294967296 + a16 * K_MAGIC_281474976710656 194} 195 196// ===== Profile dispatcher ========================================= 197// 198// Routes by profile id. Unknown / undeclared profile returns 199// HUMAN_NORMAL packed bytes deterministically per substrate-honesty 200// (never silent corruption; always well-defined bytes). 201func nx_fb_pack_for_profile(profile: i64, r: i64, g: i64, b: i64, a: i64) -> i64 { 202 if profile == NX_PERCEPT_HUMAN_NORMAL { return nx_fb_pack_human_normal(r, g, b, a) } 203 if profile == NX_PERCEPT_HUMAN_PRESBYCUSIS { return nx_fb_pack_human_normal(r, g, b, a) } 204 if profile == NX_PERCEPT_DOG_VIZSLA { return nx_fb_pack_dog_dichromat(r, g, b, a) } 205 if profile == NX_PERCEPT_DOG_GSP { return nx_fb_pack_dog_dichromat(r, g, b, a) } 206 if profile == NX_PERCEPT_DOG_WEIMARANER { return nx_fb_pack_dog_dichromat(r, g, b, a) } 207 if profile == NX_PERCEPT_DOG_POINTER { return nx_fb_pack_dog_dichromat(r, g, b, a) } 208 if profile == NX_PERCEPT_DOG_SETTER { return nx_fb_pack_dog_dichromat(r, g, b, a) } 209 if profile == NX_PERCEPT_DOG_RETRIEVER { return nx_fb_pack_dog_dichromat(r, g, b, a) } 210 if profile == NX_PERCEPT_DOG_TERRIER { return nx_fb_pack_dog_dichromat(r, g, b, a) } 211 if profile == NX_PERCEPT_DOG_SIGHTHOUND { return nx_fb_pack_dog_dichromat(r, g, b, a) } 212 if profile == NX_PERCEPT_DOG_SCENTHOUND { return nx_fb_pack_dog_dichromat(r, g, b, a) } 213 if profile == NX_PERCEPT_DOG_HERDING { return nx_fb_pack_dog_dichromat(r, g, b, a) } 214 if profile == NX_PERCEPT_DOG_GENERIC { return nx_fb_pack_dog_dichromat(r, g, b, a) } 215 if profile == NX_PERCEPT_CAT_DOMESTIC { return nx_fb_pack_dog_dichromat(r, g, b, a) } 216 if profile == NX_PERCEPT_CAT_FERAL { return nx_fb_pack_dog_dichromat(r, g, b, a) } 217 if profile == NX_PERCEPT_HONEYBEE { return nx_fb_pack_honeybee(r, g, b, a) } 218 if profile == NX_PERCEPT_BUMBLEBEE { return nx_fb_pack_honeybee(r, g, b, a) } 219 if profile == NX_PERCEPT_CHICKEN { return nx_fb_pack_avian_tetrachromat(r, g, b, a) } 220 if profile == NX_PERCEPT_SONGBIRD_PASSERINE { return nx_fb_pack_avian_tetrachromat(r, g, b, a) } 221 if profile == NX_PERCEPT_RAPTOR { return nx_fb_pack_avian_tetrachromat(r, g, b, a) } 222 if profile == NX_PERCEPT_WATERFOWL_DUCK { return nx_fb_pack_avian_tetrachromat(r, g, b, a) } 223 if profile == NX_PERCEPT_WATERFOWL_GOOSE { return nx_fb_pack_avian_tetrachromat(r, g, b, a) } 224 if profile == NX_PERCEPT_GAMEBIRD_PHEASANT { return nx_fb_pack_avian_tetrachromat(r, g, b, a) } 225 if profile == NX_PERCEPT_GAMEBIRD_QUAIL { return nx_fb_pack_avian_tetrachromat(r, g, b, a) } 226 if profile == NX_PERCEPT_PRESERVE_ALL { return nx_fb_pack_preserve_all(r, g, b, a) } 227 return nx_fb_pack_human_normal(r, g, b, a) 228} 229 230// Compile-only smoke + KAT live in nx_fb_perceptual_test.nx (driven 231// by bench/nx_fb_perceptual_smoke.sh through nx_smoke_lib.sh).