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).