OverviewFilesCommitsIssuesDocsReleasesPeers

basal-agent commits / 7cb6698

basal-agent: rafcomp.c — Test B; two rungs in one model, and Test A reverses
Chains rung 1 into rung 4. chemoton.c derived compartment-level selection but still SUPPLIED phi as a per-unit fold->catalysis map; rung1.c separately shows RAFs self-organising from random chemistry. Here catalytic output is the size of the maxRAF of the reactions a compartment actually holds — closure is computed, never assigned. The reason it might fail is the point: phi is intrinsic to a unit, so assortment shuffles good units without damaging them. RAF membership is a property of the SET, so splitting a compartment can destroy the closure that made the parent productive even though every reaction survives — colliding with Test A, which measured that assortment variance is what purges parasites. 5 seeds: dens divsz | base | FISSION | fis ctrl | PROPAGULE | prop ctrl | mean sz 0.04 16 | 0.03 | 1.16 | 0.00 | 0.87 | 0.00 | 1.32 0.08 16 | 0.10 | 4.68 | 0.01 | 3.49 | 0.00 | 5.26 0.12 16 | 0.13 | 4.71 | 0.01 | 3.52 | 0.00 | 5.30 T1 RAFs form (up to 4.71). T2 coupled beats the random-subset baseline 6/6, by ~36x at the top cell. T3 the closure-blind control sits at 0.00-0.01 everywhere — without selection on closure, compartments hold essentially no RAF. T4 THE REVERSAL: fission beats the propagule bottleneck 5 cells to 0, with the gap widening as compartments grow (~0.2 at divsz 8, ~1.2 at 16). => Test A: quality per-unit -> maximise variance, bottleneck wins. Test B: quality collective -> variance destroys it, fission wins. OPTIMAL ASSORTMENT IS A TRADE-OFF, NOT A MAXIMUM. Both measured in the same machinery with the same controls, pointing opposite ways because the KIND of quality differs — a sharper statement than either alone. And it closes a loop with linkage.c: the resolution to the tension is linkage, which we already showed emerges. Keeping co-functional parts together preserves combinations against assortment while still allowing variance between units. That is the chromosome argument, arrived at by chaining our own rungs. HONEST BOUND: two rungs co-present and interacting, not one PRODUCING the other. The self-building chain stays open, and the reaction network is supplied as in rung1.c — what emerges is which subset closes, not the chemistry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7cb6698be42b7d1a60d2b4f2295498417056a4a4
by plankatron <fleeky@gmail.com> · 2026-08-01T00:05:41.000Z

Changed files (3) +337 -0

.gitignore +1 -0
……@@ -44,3 +44,4 @@ signal
4444linkage
4545byproduct
4646chemoton
47rafcomp
rafcomp.c +273 -0
……@@ -0,0 +1,273 @@
1/* rafcomp.c — TEST B: chain rung 1 into rung 4. Does compartment-level selection still work when
2 * catalytic quality is a COLLECTIVE closure property instead of a per-unit one?
3 *
4 * WHY. SYNTHESIS's standing admission is that no rung ever produced the next — every rung is an
5 * isolated island. chemoton.c derives compartment-level selection from growth coupling, but it still
6 * SUPPLIES phi as a per-unit fold->catalysis map. rung1.c separately shows autocatalytic sets (RAFs)
7 * self-organising from random chemistry. This chains them: catalytic output is read off an EMERGENT
8 * RAF rather than a supplied fold rule, putting rung 1 and rung 4 in ONE model.
9 *
10 * THE REASON IT MIGHT FAIL, AND WHY THAT IS THE POINT. phi is intrinsic to a unit: a compartment's
11 * quality is the average of its units' qualities, so assortment shuffles good units around without
12 * destroying what makes them good. RAF membership is NOT intrinsic — it is a property of the SET.
13 * A reaction is in the RAF only if its reactants are producible and it is catalyzed by something
14 * producible, all within the set present. So splitting a compartment can DESTROY the closure that
15 * made the parent productive, even though every individual reaction survives.
16 *
17 * That collides head-on with Test A, which measured that ASSORTMENT VARIANCE is what purges
18 * parasites (propagule beats fission by +1.14 phi at matched realized size). Variance is needed to
19 * purge parasites; variance destroys collective closure. Test B asks which wins.
20 *
21 * WHAT IS SUPPLIED vs WHAT MUST EMERGE:
22 * SUPPLIED: a random catalytic reaction network (rung1.c's given chemistry), a food set, the
23 * growth/division/death machinery from chemoton.c, and a conserved material pool. Catalysis
24 * density is SWEPT.
25 * MUST EMERGE: the RAF itself (nothing designates which reactions are "good" — closure is computed,
26 * not assigned), and compartment-level differential reproduction (nothing samples parents by
27 * fitness; compartments divide because they grew).
28 * UNTOUCHED: the tragedy. Reactions OUTSIDE the RAF are cheap — they carry no catalytic obligation
29 * — so they replicate faster, exactly as low-phi parasites did.
30 *
31 * PRE-REGISTERED:
32 * T1 TEST OF THE TEST: do RAFs form inside compartments at all? Report mean maxRAF size against the
33 * random-subset baseline. If no compartment ever closes a RAF, nothing was selected on and a
34 * null says nothing.
35 * T2 coupled uptake raises mean RAF size above the random baseline. Compartment selection survives
36 * the move from per-unit to collective quality.
37 * T3 CONTROL: uptake blind to RAF size, everything else identical. Must stay at baseline.
38 * T4 THE REVERSAL: propagule (high variance) should be WORSE than fission here, opposite to Test A,
39 * because a single-unit bottleneck cannot carry a closure. If propagule still wins, collective
40 * function is more robust to assortment than expected and Test A's finding generalises.
41 *
42 * Do not tune to force a positive. A null with T1 passing is a real result: it would say the ladder
43 * does not chain, and that chemoton's success depended on quality being per-unit.
44 *
45 * build: cc -O2 -Wall -std=c11 -o rafcomp rafcomp.c -lm
46 */
47#include <math.h>
48#include <stdio.h>
49#include <stdint.h>
50#include <string.h>
51
52/* ---- reaction network (rung1.c shape, scaled so per-compartment RAF is cheap to compute) ---- */
53#define NMOL 30
54#define NFOOD 6
55#define MRXN 60
56
57/* ---- compartment machinery (chemoton.c shape) ---- */
58#define MAXCOMP 2400
59#define MAXSZ 24
60#define GEN 200
61#define BETA 2.0
62#define COSTUNIT 1.0
63#define TOTALMAT 4000.0
64#define UPTAKE 0.55
65#define DEATH 0.10
66#define MUTR 0.02 /* chance a copied reaction is replaced by a random one */
67
68static unsigned long long RNG;
69static unsigned long long xs(void){ RNG^=RNG<<13; RNG^=RNG>>7; RNG^=RNG<<17; return RNG; }
70static double urand(void){ return (double)(xs()>>11)*(1.0/9007199254740992.0); }
71static int rint_(int n){ return (int)(xs()%(unsigned)n); }
72
73static int r1[MRXN],r2[MRXN],prod[MRXN];
74static unsigned char cat[MRXN][NMOL];
75
76static void build_net(double p){
77 for(int j=0;j<MRXN;j++){
78 r1[j]=rint_(NMOL); r2[j]=rint_(NMOL);
79 prod[j]=NFOOD+rint_(NMOL-NFOOD);
80 for(int m=0;m<NMOL;m++) cat[j][m]=(urand()<p)?1:0;
81 }
82}
83
84/* maxRAF over a SUBSET of reactions (prune-to-fixpoint, rung1.c's algorithm).
85 inraf[] is filled with 1 for reactions surviving in the closure. Returns its size. */
86static int max_raf_subset(const int *rx,int n,unsigned char *inraf)
87{
88 unsigned char act[MAXSZ], W[NMOL];
89 for(int i=0;i<n;i++) act[i]=1;
90 for(;;){
91 for(int m=0;m<NMOL;m++) W[m]=(m<NFOOD)?1:0;
92 int grew=1;
93 while(grew){ grew=0;
94 for(int i=0;i<n;i++){ if(!act[i]) continue; int j=rx[i];
95 if(!W[r1[j]]||!W[r2[j]]||W[prod[j]]) continue;
96 int cz=0; for(int m=0;m<NMOL;m++) if(cat[j][m]&&W[m]){cz=1;break;}
97 if(cz){ W[prod[j]]=1; grew=1; }
98 }
99 }
100 int changed=0;
101 for(int i=0;i<n;i++){ if(!act[i]) continue; int j=rx[i];
102 int sup=W[r1[j]]&&W[r2[j]];
103 int cz=0; for(int m=0;m<NMOL;m++) if(cat[j][m]&&W[m]){cz=1;break;}
104 if(!(sup&&cz)){ act[i]=0; changed=1; }
105 }
106 if(!changed) break;
107 }
108 int c=0; for(int i=0;i<n;i++){ inraf[i]=act[i]; c+=act[i]; }
109 return c;
110}
111
112typedef struct { int rx[MAXSZ]; unsigned char inraf[MAXSZ]; int n; int raf; double mat; } Comp;
113static Comp C[MAXCOMP];
114static int ncomp;
115static double pool;
116static double demand[MAXCOMP];
117
118static double run(int coupled,int divsz,int propagule,double *raf_end,double *ncomp_end,
119 double *msize,double *ndiv,double *matcheck)
120{
121 int seed_n = divsz/2>2?divsz/2:2;
122 ncomp=600;
123 for(int c=0;c<ncomp;c++){
124 C[c].n=seed_n; C[c].mat=0;
125 for(int k=0;k<seed_n;k++) C[c].rx[k]=rint_(MRXN);
126 }
127 { long u=0; for(int c=0;c<ncomp;c++) u+=C[c].n; pool=TOTALMAT-u*COSTUNIT; }
128 double totdiv=0;
129
130 for(int g=0;g<GEN;g++){
131 for(int c=0;c<ncomp;c++) C[c].raf = max_raf_subset(C[c].rx,C[c].n,C[c].inraf);
132
133 double D=0;
134 for(int c=0;c<ncomp;c++){
135 double out = coupled ? (double)C[c].raf : 0.5*C[c].n; /* control: blind to closure */
136 demand[c]=UPTAKE*out; D+=demand[c];
137 }
138 double frac=(D>0&&D>pool)?pool/D:1.0;
139 for(int c=0;c<ncomp;c++){ double t=demand[c]*frac; C[c].mat+=t; pool-=t; }
140
141 /* growth: reactions OUTSIDE the RAF are cheap and replicate faster (the tragedy) */
142 for(int c=0;c<ncomp;c++){
143 while(C[c].mat>=COSTUNIT && C[c].n<MAXSZ && C[c].n>0){
144 double tot=0,cum[MAXSZ];
145 for(int k=0;k<C[c].n;k++){ tot += 1.0 + BETA*(C[c].inraf[k]?0.0:1.0); cum[k]=tot; }
146 double r=urand()*tot; int lo=0,hi=C[c].n-1;
147 while(lo<hi){ int mid=(lo+hi)/2; if(cum[mid]<r) lo=mid+1; else hi=mid; }
148 int nj = (urand()<MUTR)? rint_(MRXN) : C[c].rx[lo];
149 C[c].rx[C[c].n]=nj; C[c].inraf[C[c].n]=0; C[c].n++; C[c].mat-=COSTUNIT;
150 }
151 }
152 for(int c=0;c<ncomp;c++)
153 for(int k=0;k<C[c].n;){
154 if(urand()<DEATH){ C[c].rx[k]=C[c].rx[C[c].n-1]; C[c].inraf[k]=C[c].inraf[C[c].n-1]; C[c].n--; pool+=COSTUNIT; }
155 else k++;
156 }
157
158 int base=ncomp;
159 for(int c=0;c<base;c++){
160 if(C[c].n>=divsz && ncomp<MAXCOMP){
161 Comp *p=&C[c],*q=&C[ncomp];
162 if(propagule){
163 int pick=rint_(p->n); double sh=p->mat/(double)p->n;
164 q->n=0; q->rx[q->n]=p->rx[pick]; q->inraf[q->n]=0; q->n++; q->mat=sh; p->mat-=sh;
165 p->rx[pick]=p->rx[p->n-1]; p->inraf[pick]=p->inraf[p->n-1]; p->n--;
166 } else {
167 q->n=0; q->mat=p->mat*0.5; p->mat*=0.5; int w=0;
168 for(int k=0;k<p->n;k++){
169 if(urand()<0.5){ q->rx[q->n]=p->rx[k]; q->inraf[q->n]=p->inraf[k]; q->n++; }
170 else { p->rx[w]=p->rx[k]; p->inraf[w]=p->inraf[k]; w++; }
171 }
172 p->n=w;
173 }
174 ncomp++; totdiv++;
175 }
176 }
177 for(int c=0;c<ncomp;){ if(C[c].n<=0){ pool+=C[c].mat; C[c]=C[ncomp-1]; ncomp--; } else c++; }
178 if(ncomp<=0) break;
179 }
180
181 double sr=0; long u=0; double stored=0;
182 for(int c=0;c<ncomp;c++){
183 C[c].raf=max_raf_subset(C[c].rx,C[c].n,C[c].inraf);
184 sr+=C[c].raf; u+=C[c].n; stored+=C[c].mat;
185 }
186 *raf_end = ncomp? sr/ncomp : 0;
187 *ncomp_end=ncomp; *msize= ncomp? (double)u/ncomp : 0;
188 *ndiv=totdiv/GEN; *matcheck=pool+stored+u*COSTUNIT;
189 return *raf_end;
190}
191
192/* baseline: expected maxRAF of a RANDOM subset of the same size, same network */
193static double random_baseline(int n,int reps)
194{
195 double s=0; int rx[MAXSZ]; unsigned char ir[MAXSZ];
196 for(int t=0;t<reps;t++){ for(int k=0;k<n;k++) rx[k]=rint_(MRXN); s+=max_raf_subset(rx,n,ir); }
197 return s/reps;
198}
199
200int main(void)
201{
202 unsigned long long seeds[5];
203 seeds[0]=0x9e3779b97f4a7c15ULL; seeds[1]=0xd1b54a32d192ed03ULL; seeds[2]=0x2545f4914f6cdd1dULL;
204 for(int i=3;i<5;i++) seeds[i]=0x9e3779b97f4a7c15ULL ^ (0x100000001b3ULL*(unsigned long long)(i+1));
205 int NS=5;
206
207 printf("=== TEST B: rung 1 chained into rung 4 — collective quality instead of per-unit ===\n");
208 printf(" Catalytic output = size of the maxRAF of the reactions a compartment actually holds.\n");
209 printf(" Nothing designates which reactions are good: closure is COMPUTED, not assigned.\n");
210 printf(" Reactions outside the RAF carry no catalytic obligation, so they replicate %.0fx faster.\n",1+BETA);
211 printf(" THE TENSION: Test A found assortment VARIANCE purges parasites (+1.14 at matched size).\n");
212 printf(" But a RAF is a property of the SET, so variance should DESTROY closure. Which wins?\n\n");
213
214 printf(" cat.dens | divsz | rand base | FISSION raf | fis ctrl | PROPAGULE raf | prop ctrl | mean sz\n");
215 printf(" ---------+-------+-----------+-------------+----------+---------------+-----------+--------\n");
216
217 double dens[]={0.04,0.08,0.12};
218 int sizes[]={8,16};
219 int any_raf=0, coupled_wins=0, ctrl_flat=1, ncmp=0, prop_better=0, fis_better=0;
220
221 for(int d=0;d<3;d++){
222 for(int z=0;z<2;z++){
223 double bf=0,bc=0,pf=0,pc=0,ms=0,base=0,mc=0,nd=0,nc=0;
224 for(int s=0;s<NS;s++){
225 double re,n,m,dv,mm;
226 RNG=seeds[s]; build_net(dens[d]);
227 base+=random_baseline(sizes[z]/2>2?sizes[z]/2:2, 200);
228 RNG=seeds[s]; build_net(dens[d]); bf+=run(1,sizes[z],0,&re,&n,&m,&dv,&mm); ms+=m; mc+=mm; nd+=dv; nc+=n;
229 RNG=seeds[s]; build_net(dens[d]); bc+=run(0,sizes[z],0,&re,&n,&m,&dv,&mm);
230 RNG=seeds[s]; build_net(dens[d]); pf+=run(1,sizes[z],1,&re,&n,&m,&dv,&mm);
231 RNG=seeds[s]; build_net(dens[d]); pc+=run(0,sizes[z],1,&re,&n,&m,&dv,&mm);
232 }
233 bf/=NS;bc/=NS;pf/=NS;pc/=NS;ms/=NS;base/=NS;mc/=NS;nd/=NS;nc/=NS;
234 printf(" %.2f | %2d | %5.2f | %5.2f | %5.2f | %5.2f | %5.2f | %5.2f\n",
235 dens[d],sizes[z],base,bf,bc,pf,pc,ms);
236 if(bf>0.2||pf>0.2) any_raf=1;
237 ncmp++;
238 if(bf>base+0.3||pf>base+0.3) coupled_wins++;
239 if(bc>base+0.3||pc>base+0.3) ctrl_flat=0;
240 if(pf>bf+0.2) prop_better++;
241 if(bf>pf+0.2) fis_better++;
242 }
243 }
244 printf("\n");
245 printf(" T1 RAFs formed inside compartments at all : %s\n", any_raf?"YES":"NO — test did not run");
246 printf(" T2 coupled beats the random-subset baseline : %d/%d cells\n", coupled_wins,ncmp);
247 printf(" T3 CONTROL (uptake blind to closure) flat : %s\n", ctrl_flat?"YES":"NO — CONFOUNDED");
248 printf(" T4 REVERSAL: propagule better %d / fission better %d\n\n", prop_better, fis_better);
249
250 if(!any_raf)
251 printf(" => THE TEST DID NOT RUN. No compartment ever closed a RAF, so there was nothing to\n"
252 " select on. Invalid experiment, not a null. Raise catalysis density or compartment size.\n");
253 else if(!ctrl_flat)
254 printf(" => CONFOUNDED. The closure-blind control also beats baseline, so the rise is not\n"
255 " selection on collective function.\n");
256 else if(coupled_wins==0)
257 printf(" => HONEST NULL. RAFs form, but compartment selection does NOT raise closure above the\n"
258 " random baseline. The ladder does NOT chain: chemoton's success depended on catalytic\n"
259 " quality being a PER-UNIT property, and it does not survive the move to collective\n"
260 " closure — assortment destroys the very thing selection would act on.\n");
261 else if(fis_better>prop_better)
262 printf(" => CHAINS, AND TEST A REVERSES. Compartment selection survives collective quality\n"
263 " (%d/%d cells beat baseline), but FISSION now beats the propagule bottleneck\n"
264 " (%d vs %d) — the opposite of Test A. A single-unit bottleneck cannot carry a\n"
265 " closure, so the variance that purges parasites also destroys collective function.\n"
266 " The optimal assortment is a TRADE-OFF, not a maximum.\n", coupled_wins,ncmp,fis_better,prop_better);
267 else
268 printf(" => CHAINS, AND TEST A GENERALISES. Compartment selection survives collective quality\n"
269 " (%d/%d beat baseline) and the propagule bottleneck still wins (%d vs %d), so RAF\n"
270 " closure is more robust to assortment than predicted. Report as-is.\n",
271 coupled_wins,ncmp,prop_better,fis_better);
272 return 0;
273}
rafcomp.md +63 -0
……@@ -0,0 +1,63 @@
1# `rafcomp.c` — Test B: two rungs in one model, and Test A reverses
2
3Chains **rung 1 into rung 4**. `chemoton.c` derived compartment-level selection but still *supplied*
4φ as a per-unit fold→catalysis map; `rung1.c` separately shows autocatalytic sets self-organising
5from random chemistry. Here catalytic output is the **size of the maxRAF of the reactions a
6compartment actually holds** — closure is *computed*, never assigned.
7
8**Why it might fail, which is the point.** φ is intrinsic to a unit, so assortment shuffles good
9units around without damaging them. RAF membership is a property of the **set**: a reaction is in the
10RAF only if its reactants are producible and it is catalysed within the set present. Splitting a
11compartment can therefore destroy the closure that made the parent productive even though every
12individual reaction survives — colliding head-on with Test A, which measured that assortment
13variance is what purges parasites.
14
15## Results (5 seeds)
16
17| cat. density | div size | random baseline | **fission** | fis control | **propagule** | prop control | mean size |
18|---|---|---|---|---|---|---|---|
19| 0.04 | 8 | 0.02 | 0.64 | 0.00 | 0.60 | 0.00 | 0.68 |
20| 0.04 | 16 | 0.03 | **1.16** | 0.00 | 0.87 | 0.00 | 1.32 |
21| 0.08 | 8 | 0.05 | 2.59 | 0.01 | 2.35 | 0.00 | 2.73 |
22| 0.08 | 16 | 0.10 | **4.68** | 0.01 | 3.49 | 0.00 | 5.26 |
23| 0.12 | 8 | 0.06 | 2.57 | 0.01 | 2.37 | 0.01 | 2.73 |
24| 0.12 | 16 | 0.13 | **4.71** | 0.01 | 3.52 | 0.00 | 5.30 |
25
26- **T1 passed** — RAFs form, up to mean 4.71.
27- **T2 passed, 6/6 cells** — and by a wide margin: 4.71 against a random-subset baseline of 0.13, a
28 ~36× enrichment.
29- **T3 passed decisively** — the closure-blind control sits at **0.00–0.01** everywhere. Without
30 selection on closure, compartments hold essentially no RAF at all.
31- **T4: the reversal, 5 cells to 0.** Fission beats the propagule bottleneck everywhere, and the gap
32 widens with compartment size (~0.2 at div size 8, ~1.2 at 16) — exactly as expected, since at
33 div size 8 the compartments are small enough (mean 2.73) that both modes assort similarly.
34
35## The finding
36
37> **Test A: quality is per-unit → maximise variance, the bottleneck wins.
38> Test B: quality is collective → variance destroys it, fission wins.
39> Optimal assortment is a TRADE-OFF, not a maximum.**
40
41Both were measured in the same machinery with the same controls, and they point opposite ways
42because the *kind* of quality differs. That is a sharper statement than either alone.
43
44## And it closes a loop with `linkage.c`
45
46The tension has a known resolution, and we already showed it emerges: **linkage**. Keeping
47co-functional parts physically together preserves combinations against assortment, which is exactly
48what a collective closure needs — while still allowing variance *between* units. That is the
49chromosome argument (Maynard Smith & Szathmáry), and it fell out of chaining our own rungs rather
50than being imported.
51
52So the three results compose: collective function needs its parts kept together (`linkage.c`,
53rung 2→3), parasite purging needs variance (`chemoton.c` Test A), and linkage is the mechanism that
54buys both at once.
55
56## Standing
57
58**First model in the arc containing two rungs at once** — an emergent RAF (rung 1) inside
59compartments that build, grow and select themselves (rung 4), with the second selecting on the first.
60
61Honest bound: this is two rungs **co-present and interacting**, not one *producing* the other. The
62self-building chain SYNTHESIS calls open is still open. And the reaction network is supplied, as in
63`rung1.c` — what emerges is which subset closes, not the chemistry itself.