code wiki / _hdl_build / nx_game_taming.nx
nx_game_taming.nx source
↩ module page · 309 lines · 16492 B
1// nx_game_taming.nx -- THE MULTIPLE PATHS to winning a companion (ws=game-interact rung 4, operator:
2// "i like multiple ways of taming -- from dominating in fighting, to items like the pal balls, and also
3// taming from capture ... train them or release them").
4//
5// FIVE paths, each a genuinely different skill, each able to FAIL:
6// 1 DOMINANCE -- beat her decisively in combat (nx_game_breed brd_duel: margin >= species submit_bar)
7// 2 ITEM -- a bind-charm; odds are the real HP-dependent catch curve (composed from
8// nx_creature_world's cw_catch_a -- the ODDS ORACLE, not a second copy)
9// 3 SEDUCTION -- a non-combat contest of approaches vs her ARCHETYPE; reading her wrong costs you
10// 4 TRAINING -- the slow path: move trust/willpower over many interactions until she stays willingly
11// 5 RELEASE -- let her go; it costs you her, moves your reputation, and she REMEMBERS
12//
13// ★THE ARCHITECTURAL LAW (the A2 reconciliation): every path makes its own DECISION, then funnels
14// through the ONE canonical transfer verb `brd_capture`, which preserves genome + bond state exactly.
15// There is exactly one way a creature changes hands, so the paths can never drift apart.
16//
17// ★BOND IS NOT OWNERSHIP: capturing her by force gives you a companion with LOW TRUST and HIGH WILL --
18// she is yours and she will not obey. Trust is earned afterward (path 4) whatever path took her. That is
19// the depth: the fast paths cost you loyalty, the slow path costs you time.
20// All thresholds are DATA rows. All integer, deterministic, caller-seeded. LIB, no main.
21// license_tier: ORIGINAL
22import "nx_syscalls.nx"
23import "nx_game_breed.nx"
24import "nx_creature_world.nx"
25// WIRING, NOT NEW CODE (2026-07-31). This file calls gx_phenotype, gx_breed_modern, GX_HORN, GX_TAIL and
26// GX_REC -- all five already declared in nx_game_genetics.nx, none of them reachable without this line.
27// nx_cc FAILS OPEN on undefined identifiers (seq1012), so the build never said so; found by
28// `nx_undefscan scan nx_game_taming corpus`, which separates ELSEWHERE-IN (a missing import, one line)
29// from NOWHERE (unwritten code) instead of lumping both into one scary number.
30import "nx_game_genetics.nx"
31
32// ---- path ids ----
33const TM_DOMINANCE: i64 = 0
34const TM_ITEM: i64 = 1
35const TM_SEDUCE: i64 = 2
36const TM_TRAIN: i64 = 3
37const TM_RELEASE: i64 = 4
38const TM_NPATH: i64 = 5
39
40// ---- archetype rows: DATA, stride 6. An archetype is HOW she must be read.
41// [best_approach, worst_approach, seduce_bar, item_rate, train_rate, pride]
42// best/worst are approach ids; seduce_bar = disposition needed; item_rate = her catch rate (Pokemon
43// scale 0..255); train_rate = permil trust gained per good session; pride = extra will regained on insult.
44const TM_ARCH_STRIDE: i64 = 6
45const TM_NARCH: i64 = 4
46const TM_ARCH_PROUD: i64 = 0
47const TM_ARCH_SHY: i64 = 1
48const TM_ARCH_WILD: i64 = 2
49const TM_ARCH_CLEVER: i64 = 3
50
51// ---- approaches (the seduction verbs) ----
52const TM_AP_GIFT: i64 = 0
53const TM_AP_FLATTER: i64 = 1
54const TM_AP_IMPRESS: i64 = 2
55const TM_AP_CHALLENGE: i64 = 3
56const TM_NAP: i64 = 4
57
58func tm_arch_default(tbl: *i64) -> i64 {
59 // PROUD: respects a challenge, despises flattery. Hard to seduce, hard to catch, slow to trust.
60 tbl[0]=TM_AP_CHALLENGE; tbl[1]=TM_AP_FLATTER; tbl[2]=750; tbl[3]=60; tbl[4]=40; tbl[5]=120
61 // SHY: opens to gifts, terrified by a challenge. Easy to catch, quick to bond once safe.
62 tbl[TM_ARCH_STRIDE+0]=TM_AP_GIFT; tbl[TM_ARCH_STRIDE+1]=TM_AP_CHALLENGE; tbl[TM_ARCH_STRIDE+2]=550; tbl[TM_ARCH_STRIDE+3]=170; tbl[TM_ARCH_STRIDE+4]=90; tbl[TM_ARCH_STRIDE+5]=40
63 // WILD: impressed by strength, unmoved by words. Middling catch, stubborn will.
64 tbl[2*TM_ARCH_STRIDE+0]=TM_AP_IMPRESS; tbl[2*TM_ARCH_STRIDE+1]=TM_AP_GIFT; tbl[2*TM_ARCH_STRIDE+2]=700; tbl[2*TM_ARCH_STRIDE+3]=110; tbl[2*TM_ARCH_STRIDE+4]=55; tbl[2*TM_ARCH_STRIDE+5]=90
65 // CLEVER: enjoys flattery she can see through, bored by brute display.
66 tbl[3*TM_ARCH_STRIDE+0]=TM_AP_FLATTER; tbl[3*TM_ARCH_STRIDE+1]=TM_AP_IMPRESS; tbl[3*TM_ARCH_STRIDE+2]=650; tbl[3*TM_ARCH_STRIDE+3]=90; tbl[3*TM_ARCH_STRIDE+4]=70; tbl[3*TM_ARCH_STRIDE+5]=70
67 return TM_NARCH
68}
69func tm_best(tbl: *i64, a: i64) -> i64 { return tbl[a*TM_ARCH_STRIDE + 0] }
70func tm_worst(tbl: *i64, a: i64) -> i64 { return tbl[a*TM_ARCH_STRIDE + 1] }
71func tm_seduce_bar(tbl: *i64, a: i64) -> i64{ return tbl[a*TM_ARCH_STRIDE + 2] }
72func tm_item_rate(tbl: *i64, a: i64) -> i64 { return tbl[a*TM_ARCH_STRIDE + 3] }
73func tm_train_rate(tbl: *i64, a: i64) -> i64{ return tbl[a*TM_ARCH_STRIDE + 4] }
74func tm_pride(tbl: *i64, a: i64) -> i64 { return tbl[a*TM_ARCH_STRIDE + 5] }
75
76// her archetype is DERIVED from her genome+species so it is stable and discoverable, never re-rolled
77func tm_archetype(ros: *i64, h: i64) -> i64 {
78 let g: i64 = en_get(ros, h, C_GENO)
79 let sp: i64 = en_get(ros, h, C_SPEC)
80 var a: i64 = (sp + gx_phenotype(g, GX_HORN) * 2 + gx_phenotype(g, GX_TAIL)) % TM_NARCH
81 if a < 0 { a = 0 - a }
82 return a
83}
84
85func tm_rng(s: *i64) -> i64 {
86 var x: i64 = s[0]
87 x = x ^ (x << 13); x = x ^ (x >> 7); x = x ^ (x << 17)
88 s[0] = x
89 if x < 0 { return 0 - x }
90 return x
91}
92func tm_clamp(v: i64, lo: i64, hi: i64) -> i64 {
93 if v < lo { return lo }
94 if v > hi { return hi }
95 return v
96}
97
98// ---- outcome codes (shared by every path so the caller has ONE switch) ----
99const TM_OK: i64 = 1 // she is yours
100const TM_FAIL: i64 = 0 // attempt failed, she is still wild
101const TM_RESIST: i64 = 2 // failed AND she hardened (will/guard up)
102const TM_REFUSED: i64 = 3 // the attempt was not legal (bad handle, roster full, no item)
103
104// =====================================================================================================
105// PATH 2 -- ITEM CAPTURE. Odds are NOT invented here: cw_catch_a is the real HP-dependent catch curve
106// (3*maxHP - 2*curHP) * rate * ball / (3*maxHP), from nx_creature_world. We set her catch rate from her
107// ARCHETYPE and let the proven oracle decide. Weakening her first is what makes it work -- that is the
108// whole reason the combat and capture systems are one loop and not two.
109// =====================================================================================================
110func tm_item_try(ros: *i64, h: i64, tbl: *i64, c: *i64, ball: i64, s: *i64) -> i64 {
111 if en_valid(ros, h) == 0 { return TM_REFUSED }
112 if ball <= 0 { return TM_REFUSED }
113 cw_set_catch(c, tm_item_rate(tbl, tm_archetype(ros, h)))
114 let cbs: *i64 = sys_mmap(16) as *i64
115 cbs[0] = tm_rng(s)
116 if cw_try_catch(c, ball, cbs) == 1 { return TM_OK }
117 // a failed charm teaches her the shape of the trap: her guard rises, next throw is harder
118 en_set(ros, h, C_WILL, tm_clamp(en_get(ros, h, C_WILL) + 40, 0, 1000))
119 en_set(ros, h, C_DISP, tm_clamp(en_get(ros, h, C_DISP) - 25, 0, 1000))
120 return TM_RESIST
121}
122// the odds an item throw succeeds right now, in permil -- the UI shows this BEFORE you spend the charm
123func tm_item_odds_permil(ros: *i64, h: i64, tbl: *i64, c: *i64, ball: i64) -> i64 {
124 if en_valid(ros, h) == 0 { return 0 }
125 cw_set_catch(c, tm_item_rate(tbl, tm_archetype(ros, h)))
126 var a: i64 = cw_catch_a(c, ball)
127 if a > 255 { a = 255 }
128 if a < 0 { a = 0 }
129 return a * 1000 / 256
130}
131
132// =====================================================================================================
133// PATH 3 -- SEDUCTION. A contest, not a purchase. Each turn you pick an approach; her ARCHETYPE decides
134// what it is worth. Her best approach moves disposition hard, her worst moves it BACKWARD and stiffens
135// her will. You must read her -- and reading her is possible, because archetype is derived from visible
136// traits (horns/tail), so an observant player can infer it. Reaching her seduce_bar = she comes willingly,
137// which is why this path yields the HIGHEST starting trust of any capture route.
138// =====================================================================================================
139func tm_seduce_step(ros: *i64, h: i64, tbl: *i64, approach: i64, charisma: i64, s: *i64) -> i64 {
140 if en_valid(ros, h) == 0 { return TM_REFUSED }
141 let a: i64 = tm_archetype(ros, h)
142 var delta: i64 = 20 + charisma / 20
143 if approach == tm_best(tbl, a) { delta = 90 + charisma / 8 }
144 if approach == tm_worst(tbl, a) { delta = 0 - (60 + tm_pride(tbl, a) / 2) }
145 // her willpower blunts everything; a strong-willed woman is not swayed by a good line
146 let resist: i64 = en_get(ros, h, C_WILL) / 25
147 if delta > 0 { delta = delta - resist }
148 if delta < 0 { delta = delta - 0 }
149 // a little variance so it is never a solved sequence, but never enough to overturn a right read
150 delta = delta + (tm_rng(s) % 21) - 10
151 let nd: i64 = tm_clamp(en_get(ros, h, C_DISP) + delta, 0, 1000)
152 en_set(ros, h, C_DISP, nd)
153 if delta < 0 { en_set(ros, h, C_WILL, tm_clamp(en_get(ros, h, C_WILL) + tm_pride(tbl, a) / 3, 0, 1000)) }
154 if nd >= tm_seduce_bar(tbl, a) { return TM_OK }
155 if delta < 0 { return TM_RESIST }
156 return TM_FAIL
157}
158
159// =====================================================================================================
160// PATH 4 -- TRAINING. The slow path, and the one that actually makes her YOURS. Activities move trust and
161// willpower; what you do matters more than how often. A companion taken by force starts here at zero.
162// =====================================================================================================
163const TM_ACT_FEED: i64 = 0
164const TM_ACT_SPAR: i64 = 1
165const TM_ACT_WORK: i64 = 2
166const TM_ACT_PRAISE: i64 = 3
167const TM_ACT_REST: i64 = 4
168const TM_NACT: i64 = 5
169// activity rows: DATA, stride 2 = [trust_delta_scaled, will_delta]
170func tm_act_default(at: *i64) -> i64 {
171 at[0]=10; at[1]=0-6 // FEED : steady trust, softens will
172 at[2]=8; at[3]=0-10 // SPAR : bonds through shared effort, tires resistance
173 at[4]=4; at[5]=6 // WORK : useful but she resents being used
174 at[6]=12; at[7]=0-4 // PRAISE : strongest single trust move
175 at[8]=3; at[9]=0-14 // REST : little trust, big will relief
176 return TM_NACT
177}
178func tm_train(ros: *i64, h: i64, tbl: *i64, at: *i64, activity: i64, s: *i64) -> i64 {
179 if en_valid(ros, h) == 0 { return TM_REFUSED }
180 if activity < 0 { return TM_REFUSED }
181 if activity >= TM_NACT { return TM_REFUSED }
182 let a: i64 = tm_archetype(ros, h)
183 let rate: i64 = tm_train_rate(tbl, a)
184 var dt: i64 = at[activity*2 + 0] * rate / 50
185 // trust earned while she is still fighting you is worth less -- willpower is the brake
186 dt = dt - en_get(ros, h, C_WILL) / 120
187 if dt < 0 { dt = 0 }
188 dt = dt + (tm_rng(s) % 5)
189 en_set(ros, h, C_TRUST, tm_clamp(en_get(ros, h, C_TRUST) + dt, 0, 1000))
190 en_set(ros, h, C_WILL, tm_clamp(en_get(ros, h, C_WILL) + at[activity*2 + 1], 0, 1000))
191 return TM_OK
192}
193// ★OBEDIENCE: whether she actually does what you ask. Trust vs will, with a floor of pure chance so a
194// freshly-forced companion is genuinely unreliable. THIS is what makes a fast capture cost something.
195func tm_obeys(ros: *i64, h: i64, s: *i64) -> i64 {
196 if en_valid(ros, h) == 0 { return 0 }
197 let t: i64 = en_get(ros, h, C_TRUST)
198 let w: i64 = en_get(ros, h, C_WILL)
199 var chance: i64 = 500 + (t - w) / 2
200 chance = tm_clamp(chance, 50, 990)
201 if (tm_rng(s) % 1000) < chance { return 1 }
202 return 0
203}
204// she will breed / fight willingly only past a real bond
205const TM_WILLING_BAR: i64 = 600
206func tm_willing(ros: *i64, h: i64) -> i64 {
207 if en_valid(ros, h) == 0 { return 0 }
208 if en_get(ros, h, C_TRUST) < TM_WILLING_BAR { return 0 }
209 return 1
210}
211// escape risk: a high-will, low-trust captive can leave. Consequence for taking the fast path.
212func tm_escape_check(ros: *i64, h: i64, s: *i64) -> i64 {
213 if en_valid(ros, h) == 0 { return 0 }
214 let risk: i64 = (en_get(ros, h, C_WILL) - en_get(ros, h, C_TRUST)) / 3
215 if risk <= 0 { return 0 }
216 if (tm_rng(s) % 1000) < risk { return 1 }
217 return 0
218}
219
220// =====================================================================================================
221// PATH 5 -- RELEASE. Letting her go is a real move with real consequences, not a delete key.
222// The released registry remembers her genome + how she was treated, so the world has memory.
223// registry: [0]=count then rows stride 4 = [genome_vec, trust_at_release, spec, returned]
224// =====================================================================================================
225const TM_REL_STRIDE: i64 = 4
226func tm_rel_count(reg: *i64) -> i64 { return reg[0] }
227func tm_release(ros: *i64, h: i64, reg: *i64, regcap: i64, repbox: *i64) -> i64 {
228 if en_valid(ros, h) == 0 { return TM_REFUSED }
229 let n: i64 = reg[0]
230 if n >= regcap { return TM_REFUSED }
231 let trust: i64 = en_get(ros, h, C_TRUST)
232 reg[1 + n*TM_REL_STRIDE + 0] = comp_vec(ros, h)
233 reg[1 + n*TM_REL_STRIDE + 1] = trust
234 reg[1 + n*TM_REL_STRIDE + 2] = en_get(ros, h, C_SPEC)
235 reg[1 + n*TM_REL_STRIDE + 3] = 0
236 reg[0] = n + 1
237 // releasing a companion who TRUSTED you costs reputation; freeing one you never tamed earns a little
238 if trust >= TM_WILLING_BAR { repbox[0] = repbox[0] - 30 }
239 if trust < TM_WILLING_BAR { repbox[0] = repbox[0] + 10 }
240 en_destroy(ros, h)
241 return TM_OK
242}
243// a released companion may find you again -- and she remembers how it went. Returns her registry index
244// or -1. A well-treated release comes back MORE disposed to you; a betrayal comes back hostile.
245func tm_maybe_return(reg: *i64, s: *i64) -> i64 {
246 let n: i64 = reg[0]
247 if n <= 0 { return 0-1 }
248 var i: i64 = 0
249 while i < n {
250 if reg[1 + i*TM_REL_STRIDE + 3] == 0 {
251 let trust: i64 = reg[1 + i*TM_REL_STRIDE + 1]
252 var chance: i64 = 60 + trust / 8
253 if (tm_rng(s) % 1000) < chance {
254 reg[1 + i*TM_REL_STRIDE + 3] = 1
255 return i
256 }
257 }
258 i = i + 1
259 }
260 return 0-1
261}
262// re-admit a returning companion: her memory of you sets where the relationship restarts
263func tm_readmit(ros: *i64, reg: *i64, idx: i64) -> i64 {
264 if idx < 0 { return 0-1 }
265 if idx >= reg[0] { return 0-1 }
266 let h: i64 = en_spawn(ros)
267 if h == EN_NULL { return 0-1 }
268 let vec: i64 = reg[1 + idx*TM_REL_STRIDE + 0]
269 let trust: i64 = reg[1 + idx*TM_REL_STRIDE + 1]
270 // unpack the identity vector (((sp*32+iv0)*32+iv1)*32+iv2)*32+iv3
271 en_set(ros, h, C_IV3, vec % 32)
272 en_set(ros, h, C_IV2, (vec / 32) % 32)
273 en_set(ros, h, C_IV1, (vec / 1024) % 32)
274 en_set(ros, h, C_IV0, (vec / 32768) % 32)
275 en_set(ros, h, C_SPEC, reg[1 + idx*TM_REL_STRIDE + 2])
276 en_set(ros, h, C_XP, 0); en_set(ros, h, C_LVL, 1)
277 en_set(ros, h, C_NICK, en_count(ros) - 1)
278 en_set(ros, h, C_TRUST, tm_clamp(trust / 2, 0, 1000))
279 en_set(ros, h, C_WILL, 300)
280 en_set(ros, h, C_DISP, tm_clamp(300 + trust / 2, 0, 1000))
281 return h
282}
283
284// =====================================================================================================
285// THE UNIFIED ENTRY: whatever path decided, the transfer is ALWAYS brd_capture, and the path sets the
286// starting bond. Forced paths hand you a resentful companion; a willing path hands you a partner.
287// =====================================================================================================
288func tm_acquire(dst: *i64, src: *i64, h: i64, path: i64) -> i64 {
289 if en_valid(src, h) == 0 { return 0-1 }
290 // set the bond BEFORE the transfer so brd_capture carries it across verbatim
291 if path == TM_DOMINANCE { en_set(src, h, C_TRUST, 100); en_set(src, h, C_WILL, 700) }
292 if path == TM_ITEM { en_set(src, h, C_TRUST, 150); en_set(src, h, C_WILL, 600) }
293 if path == TM_SEDUCE { en_set(src, h, C_TRUST, 550); en_set(src, h, C_WILL, 250) }
294 if path == TM_TRAIN { en_set(src, h, C_TRUST, 650); en_set(src, h, C_WILL, 200) }
295 return brd_capture(dst, src, h)
296}
297func tm_path_name(p: i64) -> *u8 {
298 if p == TM_DOMINANCE { return "dominance" as *u8 }
299 if p == TM_ITEM { return "bind-charm" as *u8 }
300 if p == TM_SEDUCE { return "seduction" as *u8 }
301 if p == TM_TRAIN { return "training" as *u8 }
302 return "release"
303}
304func tm_arch_name(a: i64) -> *u8 {
305 if a == TM_ARCH_PROUD { return "proud" as *u8 }
306 if a == TM_ARCH_SHY { return "shy" as *u8 }
307 if a == TM_ARCH_WILD { return "wild" as *u8 }
308 return "clever"
309}