nx_dr_elo.nx source
↩ module page · 94 lines · 3800 B
1// nx_dr_elo.nx -- SOVEREIGN deterministic integer Elo TOURNAMENT ranker (DR-1).
2// The Co-Scientist "Tournament of Ideas": pairwise-critique match outcomes -> Elo
3// ratings -> a ranking that decides which research vectors deserve deep compute.
4// Determinism/reproducibility = the sovereign EXCEED axis vs single-agent tools:
5// bit-exact, NO float, a fixed 41-point expected-score table
6// (E(d)=1000/(1+10^(d/400)) in permille, d=rating gap) with integer linear
7// interpolation. No hardware writes (Rule 26). Imports ONLY nx_syscalls = drift-immune.
8//
9// module: nishi-core.research.dr_elo
10// depends: nx_syscalls.nx
11// capability: CORE_COMPUTE
12// wired_status: LIBRARY (conductor wiring = DR-4; critic match-outcomes = DR-2)
13// genealogy_id: elo_1978_rating + coscientist_2026_tournament_of_ideas
14import "nx_syscalls.nx"
15
16const EL_START: i64 = 1500
17const EL_K: i64 = 32
18
19// Build the 41-entry expected-score table E(d) permille, d=-800..800 step 40.
20// E(d)=1000/(1+10^(d/400)); symmetric: E(d)+E(-d)=1000.
21func el_make_etab() -> *i64 {
22 let t: *i64 = sys_mmap(41 * 8) as *i64
23 t[0]=990; t[1]=988; t[2]=984; t[3]=980; t[4]=975; t[5]=969; t[6]=962; t[7]=952
24 t[8]=941; t[9]=926; t[10]=909; t[11]=888; t[12]=863; t[13]=834; t[14]=799
25 t[15]=760; t[16]=715; t[17]=666; t[18]=613; t[19]=557; t[20]=500; t[21]=443
26 t[22]=387; t[23]=334; t[24]=285; t[25]=240; t[26]=201; t[27]=166; t[28]=137
27 t[29]=112; t[30]=91; t[31]=74; t[32]=59; t[33]=48; t[34]=38; t[35]=31; t[36]=25
28 t[37]=20; t[38]=16; t[39]=12; t[40]=10
29 return t
30}
31
32// Expected score (permille 0..1000) for `rself` vs `ropp`. d=ropp-rself clamped
33// to [-800,800], linear-interpolated over the 41-point table.
34func el_expected(rself: i64, ropp: i64, tab: *i64) -> i64 {
35 var d: i64 = ropp - rself
36 if d < (0 - 800) { d = 0 - 800 }
37 if d > 800 { d = 800 }
38 let x: i64 = d + 800
39 let idx: i64 = x / 40
40 if idx >= 40 { return tab[40] }
41 let frac: i64 = x - idx * 40
42 let a: i64 = tab[idx]
43 let b: i64 = tab[idx + 1]
44 return a + (b - a) * frac / 40
45}
46
47// One rating update. score/expected in permille (1000 win / 500 draw / 0 loss);
48// K in points. delta = K*(score-expected)/1000.
49func el_update(rating: i64, expected: i64, score: i64, k: i64) -> i64 {
50 return rating + (k * (score - expected)) / 1000
51}
52
53// Run M pairwise matches sequentially over N players. ma/mb = player indices,
54// mo = player-A actual score in permille. Mutates ratings[] in place. Sequential
55// = deterministic (same matches -> same bits, any machine).
56func el_run_tournament(ratings: *i64, n: i64, ma: *i64, mb: *i64, mo: *i64, m: i64, k: i64, tab: *i64) -> i64 {
57 var t: i64 = 0
58 while t < m {
59 let a: i64 = ma[t]
60 let b: i64 = mb[t]
61 let sa: i64 = mo[t]
62 let sb: i64 = 1000 - sa
63 let ea: i64 = el_expected(ratings[a], ratings[b], tab)
64 let eb: i64 = el_expected(ratings[b], ratings[a], tab)
65 ratings[a] = el_update(ratings[a], ea, sa, k)
66 ratings[b] = el_update(ratings[b], eb, sb, k)
67 t = t + 1
68 }
69 return 0
70}
71
72// Rank player ids by rating DESC (ties -> lower id). out_order[n]. Selection sort
73// (post-tournament fields are small: the shortlist of research vectors).
74func el_rank(ratings: *i64, n: i64, out_order: *i64) -> i64 {
75 let used: *u8 = sys_mmap(n)
76 var i: i64 = 0
77 while i < n { used[i] = 0 as u8; i = i + 1 }
78 var r: i64 = 0
79 while r < n {
80 var best: i64 = 0 - 1
81 var j: i64 = 0
82 while j < n {
83 if used[j] == (0 as u8) {
84 if best < 0 { best = j }
85 else { if ratings[j] > ratings[best] { best = j } }
86 }
87 j = j + 1
88 }
89 out_order[r] = best
90 used[best] = 1 as u8
91 r = r + 1
92 }
93 return 0
94}