nx_object_spec.nx source
↩ module page · 692 lines · 24152 B
1// nx_object_spec.nx -- per-part object-class spec catalog.
2//
3// CAPABILITY_COMPLETENESS: FULL (for the 32 starter specs;
4// additional specs register via catalog extension at
5// nishi-library/seeds/object_specs.jsonl)
6//
7// User direction 2026-05-16:
8// "expand the concepts into clothing and lots of other patterns
9// our goal is to be beyond s class at validating what we ask is
10// what we get so a dnd sword isnt a bubble wand"
11//
12// Generalises the per-region modesty pattern (nx_modesty_profile)
13// into universal per-part object-class verification:
14//
15// spec = required parts + optional parts + distinguishing
16// negations (parts that MUST NOT appear)
17//
18// Catches near-neighbour-class confusion that silhouette-only
19// classifiers miss. A "sword" with no crossguard + a loop at
20// the end is a BUBBLE_WAND, not a sword. The negations list
21// names what would make this NOT the asked-for class.
22//
23// genealogy_id: substrate_object_spec_validation_2026_05_16
24// lineage_id: nx_object_spec_v1
25
26import "nx_syscalls.nx"
27import "nx_runtime.nx"
28import "nx_tier.nx"
29
30// ===== part ID enum (subset of universal taxonomy) ===============
31//
32// 64 part IDs spanning weapon / clothing / creature / vehicle /
33// architecture / furniture / tool / instrument / container families.
34// Full ~200-part taxonomy ships in nx_part_taxonomy.nx (queued).
35
36// Weapon parts
37const NX_PT_BLADE: nx_int = 0
38const NX_PT_EDGE: nx_int = 1
39const NX_PT_POINT: nx_int = 2
40const NX_PT_TANG: nx_int = 3
41const NX_PT_CROSSGUARD: nx_int = 4
42const NX_PT_GRIP: nx_int = 5
43const NX_PT_POMMEL: nx_int = 6
44const NX_PT_HAFT: nx_int = 7
45const NX_PT_HEAD_WEAPON: nx_int = 8
46const NX_PT_LIMB_BOW: nx_int = 9
47const NX_PT_STRING_BOW: nx_int = 10
48const NX_PT_NOCK: nx_int = 11
49const NX_PT_FLETCHING: nx_int = 12
50
51// Wand / non-weapon stick parts (for negations + bubble wand)
52const NX_PT_HANDLE_STICK: nx_int = 13
53const NX_PT_LOOP: nx_int = 14
54const NX_PT_FILM: nx_int = 15
55const NX_PT_NIB: nx_int = 16
56const NX_PT_TIP: nx_int = 17
57const NX_PT_FLAME: nx_int = 18
58
59// Clothing parts
60const NX_PT_COLLAR: nx_int = 19
61const NX_PT_NECKLINE: nx_int = 20
62const NX_PT_SLEEVE_SHORT: nx_int = 21
63const NX_PT_SLEEVE_LONG: nx_int = 22
64const NX_PT_CUFF: nx_int = 23
65const NX_PT_HEM: nx_int = 24
66const NX_PT_WAISTBAND: nx_int = 25
67const NX_PT_BUTTON: nx_int = 26
68const NX_PT_ZIPPER: nx_int = 27
69const NX_PT_POCKET: nx_int = 28
70const NX_PT_LAPEL: nx_int = 29
71const NX_PT_HOOD: nx_int = 30
72const NX_PT_BRA_CUP: nx_int = 31
73const NX_PT_BRA_STRAP: nx_int = 32
74
75// Creature parts
76const NX_PT_HEAD: nx_int = 33
77const NX_PT_TUSK: nx_int = 34
78const NX_PT_HORN: nx_int = 35
79const NX_PT_WING: nx_int = 36
80const NX_PT_TAIL: nx_int = 37
81const NX_PT_SCALE: nx_int = 38
82const NX_PT_FUR: nx_int = 39
83const NX_PT_POINTED_EAR: nx_int = 40
84const NX_PT_CLAW: nx_int = 41
85const NX_PT_FANG: nx_int = 42
86
87// Vehicle parts
88const NX_PT_WHEEL: nx_int = 43
89const NX_PT_WINDSHIELD: nx_int = 44
90const NX_PT_HEADLIGHT: nx_int = 45
91const NX_PT_LICENSE_PLATE: nx_int = 46
92const NX_PT_MIRROR: nx_int = 47
93const NX_PT_GRILLE: nx_int = 48
94const NX_PT_HANDLEBAR: nx_int = 49
95
96// Architecture parts
97const NX_PT_WALL: nx_int = 50
98const NX_PT_WINDOW_PANE: nx_int = 51
99const NX_PT_DOOR: nx_int = 52
100const NX_PT_ROOF: nx_int = 53
101const NX_PT_BATTLEMENT: nx_int = 54
102const NX_PT_ARROW_SLIT: nx_int = 55
103const NX_PT_TURRET: nx_int = 56
104const NX_PT_CHIMNEY: nx_int = 57
105
106// Reserved for future expansion
107const NX_PT_RESERVED_58: nx_int = 58
108const NX_PT_RESERVED_59: nx_int = 59
109const NX_PT_RESERVED_60: nx_int = 60
110const NX_PT_RESERVED_61: nx_int = 61
111const NX_PT_RESERVED_62: nx_int = 62
112const NX_PT_RESERVED_63: nx_int = 63
113const NX_PT_N_PARTS: nx_int = 64
114
115func nx_part_is_valid(p: nx_int) -> nx_int {
116 if p < 0 { return 0 }
117 if p >= NX_PT_N_PARTS { return 0 }
118 return 1
119}
120
121// ===== spec ID enum ==============================================
122
123// Weapons
124const NX_SPEC_SWORD_LONG: nx_int = 0
125const NX_SPEC_SWORD_SHORT: nx_int = 1
126const NX_SPEC_DAGGER: nx_int = 2
127const NX_SPEC_SPEAR: nx_int = 3
128const NX_SPEC_AXE_BATTLE: nx_int = 4
129const NX_SPEC_MACE: nx_int = 5
130const NX_SPEC_HAMMER_WAR: nx_int = 6
131const NX_SPEC_BOW_LONG: nx_int = 7
132const NX_SPEC_STAFF_QUARTER: nx_int = 8
133const NX_SPEC_WAND_MAGIC: nx_int = 9
134
135// Clothing
136const NX_SPEC_SHIRT_BUTTON: nx_int = 10
137const NX_SPEC_SHIRT_T: nx_int = 11
138const NX_SPEC_PANTS_JEANS: nx_int = 12
139const NX_SPEC_DRESS_SUNDRESS: nx_int = 13
140const NX_SPEC_COAT_TRENCH: nx_int = 14
141const NX_SPEC_HAT_FEDORA: nx_int = 15
142const NX_SPEC_LINGERIE_BRA: nx_int = 16
143
144// Creatures
145const NX_SPEC_ORC_STANDARD: nx_int = 17
146const NX_SPEC_ELF_HIGH: nx_int = 18
147const NX_SPEC_DRAGON_WESTERN: nx_int = 19
148const NX_SPEC_WEREWOLF: nx_int = 20
149
150// Vehicles
151const NX_SPEC_CAR_SEDAN: nx_int = 21
152const NX_SPEC_MOTORCYCLE: nx_int = 22
153
154// Architecture
155const NX_SPEC_CASTLE_MEDIEVAL: nx_int = 23
156
157// Non-classes (so SWORD/DAGGER can NEGATE them)
158const NX_SPEC_BUBBLE_WAND: nx_int = 24
159const NX_SPEC_PEN_BALLPOINT: nx_int = 25
160const NX_SPEC_TORCH: nx_int = 26
161const NX_SPEC_FLASHLIGHT: nx_int = 27
162
163// Reserved
164const NX_SPEC_RESERVED_28: nx_int = 28
165const NX_SPEC_RESERVED_29: nx_int = 29
166const NX_SPEC_RESERVED_30: nx_int = 30
167const NX_SPEC_RESERVED_31: nx_int = 31
168const NX_SPEC_N_SPECS: nx_int = 32
169
170func nx_object_spec_is_valid(s: nx_int) -> nx_int {
171 if s < 0 { return 0 }
172 if s >= NX_SPEC_N_SPECS { return 0 }
173 return 1
174}
175
176// ===== spec struct ===============================================
177
178const NX_SPEC_MAX_REQUIRED: nx_int = 16
179const NX_SPEC_MAX_OPTIONAL: nx_int = 16
180const NX_SPEC_MAX_NEGATIONS: nx_int = 16
181
182struct NxObjectSpec {
183 spec_id: nx_int,
184 n_required: nx_int,
185 required_parts: *nx_int,
186 n_optional: nx_int,
187 optional_parts: *nx_int,
188 n_negations: nx_int,
189 negation_parts: *nx_int,
190 name_buf: *u8,
191}
192
193const NX_SPEC_BYTES: nx_size = 56
194
195// ===== part-list builder helpers ==================================
196
197func _spec_alloc_part_list() -> *nx_int {
198 let bytes: nx_size = (NX_SPEC_MAX_REQUIRED as nx_size) * 8
199 let arr: *nx_int = (sys_mmap(bytes)) as *nx_int
200 var i: nx_int = 0
201 while i < NX_SPEC_MAX_REQUIRED {
202 arr[i] = -1
203 i = i + 1
204 }
205 return arr
206}
207
208// ===== sword (long): the canonical D&D sword example ============
209
210func _spec_build_sword_long() -> *NxObjectSpec {
211 let p: *u8 = sys_mmap(NX_SPEC_BYTES)
212 let s: *NxObjectSpec = p as *NxObjectSpec
213 s.spec_id = NX_SPEC_SWORD_LONG
214 s.name_buf = "longsword" as *u8
215
216 let req: *nx_int = _spec_alloc_part_list()
217 req[0] = NX_PT_BLADE
218 req[1] = NX_PT_EDGE
219 req[2] = NX_PT_CROSSGUARD
220 req[3] = NX_PT_GRIP
221 req[4] = NX_PT_POMMEL
222 s.required_parts = req
223 s.n_required = 5
224
225 let opt: *nx_int = _spec_alloc_part_list()
226 opt[0] = NX_PT_POINT
227 opt[1] = NX_PT_TANG
228 s.optional_parts = opt
229 s.n_optional = 2
230
231 // NEGATIONS: the parts that would make this NOT a sword.
232 let neg: *nx_int = _spec_alloc_part_list()
233 neg[0] = NX_PT_LOOP // -> bubble wand
234 neg[1] = NX_PT_FILM // -> bubble wand
235 neg[2] = NX_PT_NIB // -> pen
236 neg[3] = NX_PT_FLAME // -> torch
237 neg[4] = NX_PT_HEAD_WEAPON // -> mace / hammer / axe
238 s.negation_parts = neg
239 s.n_negations = 5
240 return s
241}
242
243func _spec_build_bubble_wand() -> *NxObjectSpec {
244 let p: *u8 = sys_mmap(NX_SPEC_BYTES)
245 let s: *NxObjectSpec = p as *NxObjectSpec
246 s.spec_id = NX_SPEC_BUBBLE_WAND
247 s.name_buf = "bubble wand" as *u8
248
249 let req: *nx_int = _spec_alloc_part_list()
250 req[0] = NX_PT_HANDLE_STICK
251 req[1] = NX_PT_LOOP
252 s.required_parts = req
253 s.n_required = 2
254
255 let opt: *nx_int = _spec_alloc_part_list()
256 opt[0] = NX_PT_FILM
257 s.optional_parts = opt
258 s.n_optional = 1
259
260 let neg: *nx_int = _spec_alloc_part_list()
261 neg[0] = NX_PT_BLADE
262 neg[1] = NX_PT_EDGE
263 neg[2] = NX_PT_POINT
264 neg[3] = NX_PT_CROSSGUARD
265 neg[4] = NX_PT_STRING_BOW
266 neg[5] = NX_PT_NIB
267 neg[6] = NX_PT_FLAME
268 s.negation_parts = neg
269 s.n_negations = 7
270 return s
271}
272
273func _spec_build_dagger() -> *NxObjectSpec {
274 let p: *u8 = sys_mmap(NX_SPEC_BYTES)
275 let s: *NxObjectSpec = p as *NxObjectSpec
276 s.spec_id = NX_SPEC_DAGGER
277 s.name_buf = "dagger" as *u8
278 let req: *nx_int = _spec_alloc_part_list()
279 req[0] = NX_PT_BLADE
280 req[1] = NX_PT_POINT
281 req[2] = NX_PT_GRIP
282 s.required_parts = req
283 s.n_required = 3
284 let opt: *nx_int = _spec_alloc_part_list()
285 opt[0] = NX_PT_CROSSGUARD
286 opt[1] = NX_PT_POMMEL
287 s.optional_parts = opt
288 s.n_optional = 2
289 let neg: *nx_int = _spec_alloc_part_list()
290 neg[0] = NX_PT_LOOP
291 neg[1] = NX_PT_NIB
292 neg[2] = NX_PT_HEAD_WEAPON
293 s.negation_parts = neg
294 s.n_negations = 3
295 return s
296}
297
298func _spec_build_bow_long() -> *NxObjectSpec {
299 let p: *u8 = sys_mmap(NX_SPEC_BYTES)
300 let s: *NxObjectSpec = p as *NxObjectSpec
301 s.spec_id = NX_SPEC_BOW_LONG
302 s.name_buf = "longbow" as *u8
303 let req: *nx_int = _spec_alloc_part_list()
304 req[0] = NX_PT_LIMB_BOW
305 req[1] = NX_PT_STRING_BOW
306 req[2] = NX_PT_GRIP
307 s.required_parts = req
308 s.n_required = 3
309 let opt: *nx_int = _spec_alloc_part_list()
310 opt[0] = NX_PT_NOCK
311 s.optional_parts = opt
312 s.n_optional = 1
313 let neg: *nx_int = _spec_alloc_part_list()
314 neg[0] = NX_PT_BLADE
315 neg[1] = NX_PT_LOOP
316 neg[2] = NX_PT_HEAD_WEAPON
317 s.negation_parts = neg
318 s.n_negations = 3
319 return s
320}
321
322func _spec_build_wand_magic() -> *NxObjectSpec {
323 let p: *u8 = sys_mmap(NX_SPEC_BYTES)
324 let s: *NxObjectSpec = p as *NxObjectSpec
325 s.spec_id = NX_SPEC_WAND_MAGIC
326 s.name_buf = "magic wand" as *u8
327 let req: *nx_int = _spec_alloc_part_list()
328 req[0] = NX_PT_HANDLE_STICK
329 req[1] = NX_PT_TIP
330 s.required_parts = req
331 s.n_required = 2
332 let opt: *nx_int = _spec_alloc_part_list()
333 s.optional_parts = opt
334 s.n_optional = 0
335 // Magic wand negations -- distinguishes from bubble wand
336 // (no loop) and from sword (no blade).
337 let neg: *nx_int = _spec_alloc_part_list()
338 neg[0] = NX_PT_LOOP
339 neg[1] = NX_PT_FILM
340 neg[2] = NX_PT_BLADE
341 neg[3] = NX_PT_EDGE
342 neg[4] = NX_PT_NIB
343 s.negation_parts = neg
344 s.n_negations = 5
345 return s
346}
347
348// ===== clothing specs ============================================
349
350func _spec_build_shirt_button() -> *NxObjectSpec {
351 let p: *u8 = sys_mmap(NX_SPEC_BYTES)
352 let s: *NxObjectSpec = p as *NxObjectSpec
353 s.spec_id = NX_SPEC_SHIRT_BUTTON
354 s.name_buf = "button-up shirt" as *u8
355 let req: *nx_int = _spec_alloc_part_list()
356 req[0] = NX_PT_COLLAR
357 req[1] = NX_PT_SLEEVE_LONG
358 req[2] = NX_PT_BUTTON
359 req[3] = NX_PT_HEM
360 req[4] = NX_PT_CUFF
361 s.required_parts = req
362 s.n_required = 5
363 let opt: *nx_int = _spec_alloc_part_list()
364 opt[0] = NX_PT_POCKET
365 s.optional_parts = opt
366 s.n_optional = 1
367 let neg: *nx_int = _spec_alloc_part_list()
368 neg[0] = NX_PT_HOOD
369 neg[1] = NX_PT_ZIPPER
370 s.negation_parts = neg
371 s.n_negations = 2
372 return s
373}
374
375func _spec_build_shirt_t() -> *NxObjectSpec {
376 let p: *u8 = sys_mmap(NX_SPEC_BYTES)
377 let s: *NxObjectSpec = p as *NxObjectSpec
378 s.spec_id = NX_SPEC_SHIRT_T
379 s.name_buf = "t-shirt" as *u8
380 let req: *nx_int = _spec_alloc_part_list()
381 req[0] = NX_PT_NECKLINE
382 req[1] = NX_PT_SLEEVE_SHORT
383 req[2] = NX_PT_HEM
384 s.required_parts = req
385 s.n_required = 3
386 let opt: *nx_int = _spec_alloc_part_list()
387 s.optional_parts = opt
388 s.n_optional = 0
389 let neg: *nx_int = _spec_alloc_part_list()
390 neg[0] = NX_PT_COLLAR
391 neg[1] = NX_PT_BUTTON
392 neg[2] = NX_PT_ZIPPER
393 neg[3] = NX_PT_HOOD
394 neg[4] = NX_PT_CUFF
395 s.negation_parts = neg
396 s.n_negations = 5
397 return s
398}
399
400func _spec_build_lingerie_bra() -> *NxObjectSpec {
401 let p: *u8 = sys_mmap(NX_SPEC_BYTES)
402 let s: *NxObjectSpec = p as *NxObjectSpec
403 s.spec_id = NX_SPEC_LINGERIE_BRA
404 s.name_buf = "bra" as *u8
405 let req: *nx_int = _spec_alloc_part_list()
406 req[0] = NX_PT_BRA_CUP
407 req[1] = NX_PT_BRA_STRAP
408 s.required_parts = req
409 s.n_required = 2
410 let opt: *nx_int = _spec_alloc_part_list()
411 s.optional_parts = opt
412 s.n_optional = 0
413 let neg: *nx_int = _spec_alloc_part_list()
414 neg[0] = NX_PT_SLEEVE_LONG
415 neg[1] = NX_PT_SLEEVE_SHORT
416 neg[2] = NX_PT_HOOD
417 neg[3] = NX_PT_HEM
418 s.negation_parts = neg
419 s.n_negations = 4
420 return s
421}
422
423// ===== creature specs ============================================
424
425func _spec_build_orc() -> *NxObjectSpec {
426 let p: *u8 = sys_mmap(NX_SPEC_BYTES)
427 let s: *NxObjectSpec = p as *NxObjectSpec
428 s.spec_id = NX_SPEC_ORC_STANDARD
429 s.name_buf = "orc" as *u8
430 let req: *nx_int = _spec_alloc_part_list()
431 req[0] = NX_PT_HEAD
432 req[1] = NX_PT_TUSK
433 s.required_parts = req
434 s.n_required = 2
435 let opt: *nx_int = _spec_alloc_part_list()
436 opt[0] = NX_PT_FANG
437 s.optional_parts = opt
438 s.n_optional = 1
439 let neg: *nx_int = _spec_alloc_part_list()
440 neg[0] = NX_PT_POINTED_EAR // -> elf
441 neg[1] = NX_PT_WING // -> dragon
442 neg[2] = NX_PT_SCALE // -> dragon
443 s.negation_parts = neg
444 s.n_negations = 3
445 return s
446}
447
448func _spec_build_elf() -> *NxObjectSpec {
449 let p: *u8 = sys_mmap(NX_SPEC_BYTES)
450 let s: *NxObjectSpec = p as *NxObjectSpec
451 s.spec_id = NX_SPEC_ELF_HIGH
452 s.name_buf = "high elf" as *u8
453 let req: *nx_int = _spec_alloc_part_list()
454 req[0] = NX_PT_HEAD
455 req[1] = NX_PT_POINTED_EAR
456 s.required_parts = req
457 s.n_required = 2
458 let opt: *nx_int = _spec_alloc_part_list()
459 s.optional_parts = opt
460 s.n_optional = 0
461 let neg: *nx_int = _spec_alloc_part_list()
462 neg[0] = NX_PT_TUSK // -> orc
463 neg[1] = NX_PT_HORN // -> tiefling / demon
464 neg[2] = NX_PT_FANG // -> vampire
465 neg[3] = NX_PT_FUR // -> werewolf
466 neg[4] = NX_PT_SCALE // -> dragon-kin
467 s.negation_parts = neg
468 s.n_negations = 5
469 return s
470}
471
472func _spec_build_dragon() -> *NxObjectSpec {
473 let p: *u8 = sys_mmap(NX_SPEC_BYTES)
474 let s: *NxObjectSpec = p as *NxObjectSpec
475 s.spec_id = NX_SPEC_DRAGON_WESTERN
476 s.name_buf = "western dragon" as *u8
477 let req: *nx_int = _spec_alloc_part_list()
478 req[0] = NX_PT_HEAD
479 req[1] = NX_PT_WING
480 req[2] = NX_PT_TAIL
481 req[3] = NX_PT_SCALE
482 req[4] = NX_PT_CLAW
483 s.required_parts = req
484 s.n_required = 5
485 let opt: *nx_int = _spec_alloc_part_list()
486 opt[0] = NX_PT_HORN
487 opt[1] = NX_PT_FANG
488 s.optional_parts = opt
489 s.n_optional = 2
490 let neg: *nx_int = _spec_alloc_part_list()
491 neg[0] = NX_PT_FUR // -> werewolf / mammal
492 neg[1] = NX_PT_POINTED_EAR // -> elf-kin
493 s.negation_parts = neg
494 s.n_negations = 2
495 return s
496}
497
498// ===== vehicle specs =============================================
499
500func _spec_build_car_sedan() -> *NxObjectSpec {
501 let p: *u8 = sys_mmap(NX_SPEC_BYTES)
502 let s: *NxObjectSpec = p as *NxObjectSpec
503 s.spec_id = NX_SPEC_CAR_SEDAN
504 s.name_buf = "sedan car" as *u8
505 let req: *nx_int = _spec_alloc_part_list()
506 req[0] = NX_PT_WHEEL
507 req[1] = NX_PT_WINDSHIELD
508 req[2] = NX_PT_HEADLIGHT
509 req[3] = NX_PT_LICENSE_PLATE
510 req[4] = NX_PT_MIRROR
511 s.required_parts = req
512 s.n_required = 5
513 let opt: *nx_int = _spec_alloc_part_list()
514 opt[0] = NX_PT_GRILLE
515 s.optional_parts = opt
516 s.n_optional = 1
517 let neg: *nx_int = _spec_alloc_part_list()
518 neg[0] = NX_PT_HANDLEBAR // -> motorcycle / bicycle
519 s.negation_parts = neg
520 s.n_negations = 1
521 return s
522}
523
524func _spec_build_motorcycle() -> *NxObjectSpec {
525 let p: *u8 = sys_mmap(NX_SPEC_BYTES)
526 let s: *NxObjectSpec = p as *NxObjectSpec
527 s.spec_id = NX_SPEC_MOTORCYCLE
528 s.name_buf = "motorcycle" as *u8
529 let req: *nx_int = _spec_alloc_part_list()
530 req[0] = NX_PT_WHEEL
531 req[1] = NX_PT_HANDLEBAR
532 req[2] = NX_PT_HEADLIGHT
533 s.required_parts = req
534 s.n_required = 3
535 let opt: *nx_int = _spec_alloc_part_list()
536 opt[0] = NX_PT_MIRROR
537 s.optional_parts = opt
538 s.n_optional = 1
539 let neg: *nx_int = _spec_alloc_part_list()
540 neg[0] = NX_PT_WINDSHIELD // (some have but rare; refuses car)
541 neg[1] = NX_PT_GRILLE
542 s.negation_parts = neg
543 s.n_negations = 2
544 return s
545}
546
547func _spec_build_castle() -> *NxObjectSpec {
548 let p: *u8 = sys_mmap(NX_SPEC_BYTES)
549 let s: *NxObjectSpec = p as *NxObjectSpec
550 s.spec_id = NX_SPEC_CASTLE_MEDIEVAL
551 s.name_buf = "medieval castle" as *u8
552 let req: *nx_int = _spec_alloc_part_list()
553 req[0] = NX_PT_WALL
554 req[1] = NX_PT_BATTLEMENT
555 req[2] = NX_PT_TURRET
556 req[3] = NX_PT_ARROW_SLIT
557 s.required_parts = req
558 s.n_required = 4
559 let opt: *nx_int = _spec_alloc_part_list()
560 opt[0] = NX_PT_DOOR
561 opt[1] = NX_PT_WINDOW_PANE
562 s.optional_parts = opt
563 s.n_optional = 2
564 let neg: *nx_int = _spec_alloc_part_list()
565 neg[0] = NX_PT_CHIMNEY // (some had; modern house signal)
566 s.negation_parts = neg
567 s.n_negations = 1
568 return s
569}
570
571// ===== spec factory ==============================================
572
573func nx_object_spec_get(spec_id: nx_int) -> *NxObjectSpec {
574 if nx_object_spec_is_valid(spec_id) == 0 { return 0 as *NxObjectSpec }
575 if spec_id == NX_SPEC_SWORD_LONG { return _spec_build_sword_long() }
576 if spec_id == NX_SPEC_BUBBLE_WAND { return _spec_build_bubble_wand() }
577 if spec_id == NX_SPEC_DAGGER { return _spec_build_dagger() }
578 if spec_id == NX_SPEC_BOW_LONG { return _spec_build_bow_long() }
579 if spec_id == NX_SPEC_WAND_MAGIC { return _spec_build_wand_magic() }
580 if spec_id == NX_SPEC_SHIRT_BUTTON { return _spec_build_shirt_button() }
581 if spec_id == NX_SPEC_SHIRT_T { return _spec_build_shirt_t() }
582 if spec_id == NX_SPEC_LINGERIE_BRA { return _spec_build_lingerie_bra() }
583 if spec_id == NX_SPEC_ORC_STANDARD { return _spec_build_orc() }
584 if spec_id == NX_SPEC_ELF_HIGH { return _spec_build_elf() }
585 if spec_id == NX_SPEC_DRAGON_WESTERN { return _spec_build_dragon() }
586 if spec_id == NX_SPEC_CAR_SEDAN { return _spec_build_car_sedan() }
587 if spec_id == NX_SPEC_MOTORCYCLE { return _spec_build_motorcycle() }
588 if spec_id == NX_SPEC_CASTLE_MEDIEVAL { return _spec_build_castle() }
589 return 0 as *NxObjectSpec
590}
591
592// ===== part-list query ===========================================
593//
594// Returns 1 if part_id is in the spec's required list.
595
596func nx_object_spec_part_required(spec: *NxObjectSpec, part_id: nx_int) -> nx_int {
597 if spec == (0 as *NxObjectSpec) { return 0 }
598 var i: nx_int = 0
599 while i < spec.n_required {
600 if spec.required_parts[i] == part_id { return 1 }
601 i = i + 1
602 }
603 return 0
604}
605
606func nx_object_spec_part_negated(spec: *NxObjectSpec, part_id: nx_int) -> nx_int {
607 if spec == (0 as *NxObjectSpec) { return 0 }
608 var i: nx_int = 0
609 while i < spec.n_negations {
610 if spec.negation_parts[i] == part_id { return 1 }
611 i = i + 1
612 }
613 return 0
614}
615
616// ===== self-test =================================================
617
618func main() -> nx_int {
619 // ---- sword vs bubble wand: the user's canonical example ----
620 let sword: *NxObjectSpec = nx_object_spec_get(NX_SPEC_SWORD_LONG)
621 if sword == (0 as *NxObjectSpec) { return 1 }
622 if nx_object_spec_part_required(sword, NX_PT_BLADE) != 1 { return 2 }
623 if nx_object_spec_part_required(sword, NX_PT_CROSSGUARD) != 1 { return 3 }
624 if nx_object_spec_part_required(sword, NX_PT_GRIP) != 1 { return 4 }
625 if nx_object_spec_part_required(sword, NX_PT_POMMEL) != 1 { return 5 }
626 if nx_object_spec_part_required(sword, NX_PT_LOOP) != 0 { return 6 }
627 // SWORD NEGATES loop + film (bubble wand) + nib (pen) + flame (torch)
628 if nx_object_spec_part_negated(sword, NX_PT_LOOP) != 1 { return 10 }
629 if nx_object_spec_part_negated(sword, NX_PT_FILM) != 1 { return 11 }
630 if nx_object_spec_part_negated(sword, NX_PT_NIB) != 1 { return 12 }
631 if nx_object_spec_part_negated(sword, NX_PT_FLAME) != 1 { return 13 }
632 // SWORD does NOT negate POINT (most swords have a point).
633 if nx_object_spec_part_negated(sword, NX_PT_POINT) != 0 { return 14 }
634
635 let wand: *NxObjectSpec = nx_object_spec_get(NX_SPEC_BUBBLE_WAND)
636 if nx_object_spec_part_required(wand, NX_PT_LOOP) != 1 { return 20 }
637 if nx_object_spec_part_required(wand, NX_PT_HANDLE_STICK) != 1 { return 21 }
638 if nx_object_spec_part_required(wand, NX_PT_BLADE) != 0 { return 22 }
639 if nx_object_spec_part_negated(wand, NX_PT_BLADE) != 1 { return 23 }
640 if nx_object_spec_part_negated(wand, NX_PT_CROSSGUARD) != 1 { return 24 }
641
642 // ---- creature specs: ORC vs ELF ----
643 let orc: *NxObjectSpec = nx_object_spec_get(NX_SPEC_ORC_STANDARD)
644 if nx_object_spec_part_required(orc, NX_PT_TUSK) != 1 { return 30 }
645 if nx_object_spec_part_negated(orc, NX_PT_POINTED_EAR) != 1 { return 31 }
646 if nx_object_spec_part_negated(orc, NX_PT_WING) != 1 { return 32 }
647
648 let elf: *NxObjectSpec = nx_object_spec_get(NX_SPEC_ELF_HIGH)
649 if nx_object_spec_part_required(elf, NX_PT_POINTED_EAR) != 1 { return 40 }
650 if nx_object_spec_part_negated(elf, NX_PT_TUSK) != 1 { return 41 }
651 if nx_object_spec_part_negated(elf, NX_PT_HORN) != 1 { return 42 }
652 if nx_object_spec_part_negated(elf, NX_PT_SCALE) != 1 { return 43 }
653
654 // ---- dragon: many required + negate fur (rules out werewolf) ----
655 let dragon: *NxObjectSpec = nx_object_spec_get(NX_SPEC_DRAGON_WESTERN)
656 if nx_object_spec_part_required(dragon, NX_PT_WING) != 1 { return 50 }
657 if nx_object_spec_part_required(dragon, NX_PT_SCALE) != 1 { return 51 }
658 if nx_object_spec_part_negated(dragon, NX_PT_FUR) != 1 { return 52 }
659
660 // ---- clothing: button-up shirt requires collar+button, negates hood
661 let shirt: *NxObjectSpec = nx_object_spec_get(NX_SPEC_SHIRT_BUTTON)
662 if nx_object_spec_part_required(shirt, NX_PT_COLLAR) != 1 { return 60 }
663 if nx_object_spec_part_required(shirt, NX_PT_BUTTON) != 1 { return 61 }
664 if nx_object_spec_part_negated(shirt, NX_PT_HOOD) != 1 { return 62 }
665
666 let t_shirt: *NxObjectSpec = nx_object_spec_get(NX_SPEC_SHIRT_T)
667 // T-shirt NEGATES collar (key distinction from button-up).
668 if nx_object_spec_part_negated(t_shirt, NX_PT_COLLAR) != 1 { return 70 }
669 if nx_object_spec_part_required(t_shirt, NX_PT_SLEEVE_SHORT) != 1 { return 71 }
670
671 // ---- vehicle: car vs motorcycle ----
672 let car: *NxObjectSpec = nx_object_spec_get(NX_SPEC_CAR_SEDAN)
673 if nx_object_spec_part_required(car, NX_PT_WHEEL) != 1 { return 80 }
674 if nx_object_spec_part_required(car, NX_PT_WINDSHIELD) != 1 { return 81 }
675 if nx_object_spec_part_negated(car, NX_PT_HANDLEBAR) != 1 { return 82 }
676
677 let bike: *NxObjectSpec = nx_object_spec_get(NX_SPEC_MOTORCYCLE)
678 if nx_object_spec_part_required(bike, NX_PT_HANDLEBAR) != 1 { return 90 }
679 if nx_object_spec_part_negated(bike, NX_PT_GRILLE) != 1 { return 91 }
680
681 // ---- castle: requires battlement + turret + arrow slit ----
682 let castle: *NxObjectSpec = nx_object_spec_get(NX_SPEC_CASTLE_MEDIEVAL)
683 if nx_object_spec_part_required(castle, NX_PT_BATTLEMENT) != 1 { return 100 }
684 if nx_object_spec_part_required(castle, NX_PT_ARROW_SLIT) != 1 { return 101 }
685
686 // ---- spec / part validation refused out-of-range ----
687 if nx_object_spec_is_valid(NX_SPEC_N_SPECS) != 0 { return 110 }
688 if nx_part_is_valid(NX_PT_N_PARTS) != 0 { return 111 }
689 if nx_object_spec_get(NX_SPEC_N_SPECS) != (0 as *NxObjectSpec) { return 112 }
690
691 return 0
692}