nx_manga_rating_lib.nx source
↩ module page · 145 lines · 6530 B
1// nx_manga_rating_lib.nx -- THE CONTENT RATING DECISION as a shared lib (mangagen MG24 mr_rate_row,
2// MG32 mr_adult_invariant). SINGLE RESPONSIBILITY: this file DECIDES, nx_manga_rating is a thin CLI
3// over it, and nx_manga_rating_gate tests it IN-PROCESS. One decision, one home -- so the CLI and the
4// gate cannot drift into two different answers, which is the whole reason for the split.
5//
6// COMPOSES THE INCUMBENTS AND RE-IMPLEMENTS NEITHER:
7// nx_torrent_access nx_taccess_allow(viewer_level, area) -- the fail-closed VIEWER-LEVEL decision
8// from the operator directive of 2026-06-20. OWNER 3 / FAMILY 1 / ANON 0;
9// areas SFW 0 requires FAMILY, NSFW 1 requires OWNER. This lib NEVER re-decides
10// access: it says which AREA an asset belongs in and hands that to the incumbent.
11// nx_wardrobe_state WS_ADULT_AGE -- the adult threshold already failing closed at construction in
12// the character lane. IMPORTED, never redeclared: the same constant written twice
13// is two constants to every scanner, and an age threshold that can drift between
14// two organs is a defect generator rather than a policy.
15//
16// THE LOAD-BEARING PROPERTY:
17// AN UNRATED ASSET IS TREATED AS GATED, NEVER AS SAFE. An absent rating is not evidence of innocence.
18// Every mapping fails toward the MORE restrictive area, so a missing row costs a family viewer a page
19// it could have seen -- never the reverse.
20
21import "nx_syscalls.nx"
22import "nx_torrent_access.nx"
23import "nx_wardrobe_state.nx"
24
25const MR_RATE_UNRATED: i64 = 0-1
26const MR_RATE_ALLAGES: i64 = 0
27const MR_RATE_TEEN: i64 = 1
28const MR_RATE_MATURE: i64 = 2
29const MR_RATE_ADULT: i64 = 3
30const MR_RATE_MAX: i64 = 3
31
32// EXPOSURE, as measured by the wardrobe lane (ws_exposure over the wardrobe vector: a gated zone
33// below WS_MODEST). This lib CONSUMES that measurement and never re-derives it.
34const MR_CLOTHED: i64 = 0
35const MR_EXPOSED: i64 = 1
36
37const MR_OK: i64 = 0
38const MR_E_MINOR: i64 = 0-1
39const MR_E_RATING: i64 = 0-2
40const MR_E_ROW: i64 = 0-3
41
42const MR_ROWCAP: i64 = 512
43const MR_PIPE: i64 = 124
44
45func mr_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
46
47func mr_apps(dst: *u8, o: i64, s: *u8) -> i64 {
48 var i: i64 = 0
49 while s[i] != (0 as u8) { dst[o + i] = s[i]; i = i + 1 }
50 return o + i
51}
52
53func mr_num(dst: *u8, o: i64, v: i64) -> i64 {
54 var w: i64 = o
55 var m: i64 = v
56 if m < 0 { dst[w] = 45 as u8; w = w + 1; m = 0 - m }
57 if m == 0 { dst[w] = 48 as u8; return w + 1 }
58 let t: *u8 = sys_mmap(32)
59 var k: i64 = 0
60 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
61 while k > 0 { k = k - 1; dst[w] = t[k]; w = w + 1 }
62 return w
63}
64
65func mr_atoi(s: *u8) -> i64 {
66 var i: i64 = 0
67 var neg: i64 = 0
68 if s[0] == (45 as u8) { neg = 1; i = 1 }
69 var v: i64 = 0
70 while s[i] != (0 as u8) {
71 let c: i64 = s[i] as i64
72 if c < 48 { return v }
73 if c > 57 { return v }
74 v = v * 10 + (c - 48)
75 i = i + 1
76 }
77 if neg == 1 { return 0 - v }
78 return v
79}
80
81// mr_rating_valid -- a rating is either UNRATED or inside the ladder. Anything else is a MALFORMED ROW
82// and is reported as such, never silently coerced into a verdict.
83func mr_rating_valid(r: i64) -> i64 {
84 if r == MR_RATE_UNRATED { return 1 }
85 if r < MR_RATE_ALLAGES { return 0 }
86 if r > MR_RATE_MAX { return 0 }
87 return 1
88}
89
90// mr_area_for_rating -- THE MAPPING, and the direction it fails in IS the design.
91func mr_area_for_rating(r: i64) -> i64 {
92 if r == MR_RATE_UNRATED { return NX_TAREA_NSFW }
93 if r >= MR_RATE_MATURE { return NX_TAREA_NSFW }
94 return NX_TAREA_SFW
95}
96
97// mr_may_serve -- COMPOSE, never re-decide. Area from the asset own rating, allow/deny from the
98// incumbent ruler. An invalid rating DENIES rather than defaulting to anything.
99func mr_may_serve(viewer_level: i64, r: i64) -> i64 {
100 if mr_rating_valid(r) == 0 { return NX_TACCESS_DENY }
101 let area: i64 = mr_area_for_rating(r)
102 return nx_taccess_allow(viewer_level, area)
103}
104
105// mr_depiction_legal -- MG32, AXIS 2: WHAT MAY BE DEPICTED. Age x EXPOSURE, never age x rating.
106// at or above WS_ADULT_AGE -> MR_OK, UNCONDITIONALLY. A declared adult is an adult. There is
107// deliberately NO appearance test and NO hold: a petite, slight or
108// youthful-looking adult character is lawful, and a classifier that
109// held such work for review would be a false-positive engine policing
110// legal content. The estate's own law is that a detector with false
111// positives is worse than none.
112// below, CLOTHED -> MR_OK. Minor characters exist in ordinary comics and school stories.
113// below, EXPOSED -> MR_E_MINOR, refused at construction. THE ONLY HARD LINE, and it is
114// the same one ws_set already enforces per gated zone.
115//
116// THE FIRST CUT OF THIS FUNCTION GATED ON THE WORK'S RATING and was WRONG: it would have refused a
117// fully clothed minor character in a MATURE-rated story, which is ordinary lawful content. The rating
118// of a WORK says nothing about whether a given FIGURE is lawfully depicted -- those are two axes and
119// conflating them both over-blocks lawful work and misses the case that actually matters.
120func mr_depiction_legal(subject_age: i64, exposed: i64) -> i64 {
121 if subject_age >= WS_ADULT_AGE { return MR_OK }
122 if exposed == MR_EXPOSED { return MR_E_MINOR }
123 return MR_OK
124}
125
126// mr_rate_row -- MG24. The ROW is the artifact: a rating that exists only in a caller variable is a
127// rating nobody can audit and nothing can re-check.
128// rate|<asset-cid>|<rating>|<source>|<epoch>
129func mr_rate_row(cid: *u8, r: i64, src: *u8, epoch: i64, out: *u8, cap: i64) -> i64 {
130 if mr_rating_valid(r) == 0 { return MR_E_RATING }
131 let need: i64 = mr_slen(cid) + mr_slen(src) + 48
132 if need > cap { return MR_E_ROW }
133 var o: i64 = 0
134 o = mr_apps(out, o, "rate" as *u8)
135 out[o] = MR_PIPE as u8; o = o + 1
136 o = mr_apps(out, o, cid)
137 out[o] = MR_PIPE as u8; o = o + 1
138 o = mr_num(out, o, r)
139 out[o] = MR_PIPE as u8; o = o + 1
140 o = mr_apps(out, o, src)
141 out[o] = MR_PIPE as u8; o = o + 1
142 o = mr_num(out, o, epoch)
143 out[o] = 0 as u8
144 return o
145}