/* rung45irr.c — TEST: is division of labour blocked by REVERSIBILITY? (Law 2 applied to rung 4->5)
*
* rung45.c stalled, and rung45dup.c got specialists invading but full DoL plateauing at a second
* coordination valley. Law 2 (SYNTHESIS Part C) says why that might be: a boundary — or here a
* ROLE — that must be MAINTAINED is a strategy, and strategies get invaded. In rung45.c a
* specialist can mutate straight back to generalist (r = xs()%(F+1)), so specialisation is fully
* elective and the retreat to safe generality is always available.
*
* Life's specialists cannot go back: differentiation involves losing capability, and gene loss is
* one-way. So the one-knob test is whether making specialisation STRUCTURAL rather than elective
* stabilises DoL.
*
* REVERSIBLE (control, = rung45.c): with prob MUR, role := uniform(0..F)
* IRREVERSIBLE (test): with prob MUR, role := generalist ? uniform(0..F)
* : uniform(1..F) <- no way back
*
* NOT ENCODED: irreversibility removes the retreat-to-generalist escape, but leaves the OTHER
* failure route wide open — a compartment can still collapse to a monoculture of one specialist,
* lose a function and die. So the outcome is genuinely open: a ratchet could give division of
* labour, or could give monoculture and extinction. Viability is reported so which one happens is
* visible.
*
* build: cc -O2 -Wall -std=c11 -o rung45irr rung45irr.c -lm
*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#define NCOMP 500
#define GEN 400
#define D 12 /* compartment size */
#define F 3 /* functions (roles 1..F); role 0 = generalist */
#define SSPEC 0.30 /* specialist replication advantage (the free-rider pressure) */
#define EGEN 0.2 /* generalist efficiency per function */
#define MUR 0.05 /* role mutation rate */
static unsigned long long RNG;
static unsigned long long xs(void){ RNG^=RNG<<13; RNG^=RNG>>7; RNG^=RNG<<17; return RNG; }
static double urand(void){ return (double)(xs()>>11)*(1.0/9007199254740992.0); }
static int binom(int n,double p){ int k=0; for(int i=0;i<n;i++) if(urand()<p) k++; return k; }
static int IRREV=0, NOSEL=0;
static int comp[NCOMP][F+1]; /* role counts per compartment */
static int dau[2*NCOMP][F+1]; static double cum[2*NCOMP];
static double viability_sel(const int *n);
static double viability(const int *n){ if(NOSEL) return 1.0; return viability_sel(n); }
static double viability_sel(const int *n){
double v=1e9;
for(int k=1;k<=F;k++){ double cov=n[k]+EGEN*n[0]; if(cov<v) v=cov; } /* weakest-link: need ALL functions */
return v;
}
static void one_run(int irrev,int nosel,double *specf,double *dolf,double *viaf){
IRREV=irrev; NOSEL=nosel;
for(int c=0;c<NCOMP;c++){ comp[c][0]=D; for(int k=1;k<=F;k++) comp[c][k]=0; }
for(int g=0;g<GEN;g++){
double w[F+1]; w[0]=1.0; for(int k=1;k<=F;k++) w[k]=1.0+SSPEC;
for(int c=0;c<NCOMP;c++){
double Z=0; for(int k=0;k<=F;k++) Z+=comp[c][k]*w[k]; if(Z<1e-12)Z=1e-12;
double pr[F+1],acc=0; for(int k=0;k<=F;k++){ acc+=comp[c][k]*w[k]/Z; pr[k]=acc; }
int m[F+1]={0};
for(int i=0;i<2*D;i++){ double u=urand(); int r=0; while(r<F && u>pr[r]) r++;
if(urand()<MUR){ if(IRREV && r!=0) r=1+(int)(xs()%F); else r=(int)(xs()%(F+1)); }
m[r]++; }
for(int k=0;k<=F;k++){ int d1=binom(m[k],0.5); dau[2*c][k]=d1; dau[2*c+1][k]=m[k]-d1; }
}
double tot=0; for(int i=0;i<2*NCOMP;i++){ tot+=viability(dau[i]); cum[i]=tot; }
if(tot<=0){ for(int c=0;c<NCOMP;c++) for(int k=0;k<=F;k++) comp[c][k]=0; break; }
for(int c=0;c<NCOMP;c++){ double r=urand()*tot; int lo=0,hi=2*NCOMP-1;
while(lo<hi){ int mid=(lo+hi)/2; if(cum[mid]<r) lo=mid+1; else hi=mid; }
for(int k=0;k<=F;k++) comp[c][k]=dau[lo][k]; }
}
long tot=0,spec=0; int dol=0,via=0;
for(int c=0;c<NCOMP;c++){ int s2=0;
for(int k=0;k<=F;k++){ tot+=comp[c][k]; if(k>0){ spec+=comp[c][k]; if(comp[c][k]>0) s2++; } }
if(s2==F) dol++; if(viability_sel(comp[c])>0.001) via++; }
*specf = tot? (double)spec/tot : 0;
*dolf = (double)dol/NCOMP;
*viaf = (double)via/NCOMP;
}
int main(void){
unsigned long long seeds[6]={0x9e3779b97f4a7c15ULL,0xd1b54a32d192ed03ULL,0x2545f4914f6cdd1dULL,
0x123456789abcdefULL,0xfeedface12345678ULL,0x0badc0de0badc0deULL};
int NS=6;
printf("=== RUNG 4->5 UNDER LAW 2: is division of labour blocked by REVERSIBILITY? ===\n");
printf(" rung45.c let a specialist mutate straight back to generalist, so specialisation was\n");
printf(" fully elective — and Law 2 says an elective role is a strategy and gets invaded.\n");
printf(" IRREVERSIBLE removes only the way back; the monoculture-and-die route stays open.\n");
printf(" start: every compartment = %d generalists; specialists replicate %.0f%% faster;\n",D,SSPEC*100);
printf(" a compartment needs all %d functions to be viable. %d comps, %d gens, %d seeds.\n\n",F,NCOMP,GEN,NS);
printf(" arm | spec frac | all-%d-roles | viable\n",F);
printf(" -------------+-----------+-------------+--------\n");
double a[3]={0,0,0}, b[3]={0,0,0}, d[3]={0,0,0}, e[3]={0,0,0};
for(int s=0;s<NS;s++){ double x,y,z;
RNG=seeds[s]; one_run(0,0,&x,&y,&z); a[0]+=x;a[1]+=y;a[2]+=z;
RNG=seeds[s]; one_run(1,0,&x,&y,&z); b[0]+=x;b[1]+=y;b[2]+=z;
RNG=seeds[s]; one_run(1,1,&x,&y,&z); d[0]+=x;d[1]+=y;d[2]+=z;
RNG=seeds[s]; one_run(0,1,&x,&y,&z); e[0]+=x;e[1]+=y;e[2]+=z; }
for(int i=0;i<3;i++){ a[i]/=NS; b[i]/=NS; d[i]/=NS; e[i]/=NS; }
printf(" REVERSIBLE | %.3f | %6.1f%% | %5.1f%%\n",a[0],100*a[1],100*a[2]);
printf(" IRREVERSIBLE | %.3f | %6.1f%% | %5.1f%%\n",b[0],100*b[1],100*b[2]);
printf(" ---- CONFOUND CHECK: same ratchet, selection REMOVED (flat viability) ----\n");
printf(" REVERS, nosel| %.3f | %6.1f%% | --\n",e[0],100*e[1]);
printf(" IRREV, nosel| %.3f | %6.1f%% | --\n\n",d[0],100*d[1]);
printf(" irreversibility's DoL benefit WITH selection %+.1f%%, WITHOUT selection %+.1f%%\n",
100*(b[1]-a[1]), 100*(d[1]-e[1]));
printf(" selection's effect on DoL: reversible %+.1f%%, irreversible %+.1f%%\n",
100*(a[1]-e[1]), 100*(b[1]-d[1]));
int dol_up = b[1]>a[1]+0.05, via_ok = b[2]>0.5, spec_up = b[0]>a[0]+0.05;
/* CORRECTED. b[1]-d[1] measures selection's contribution INSIDE the irreversible arm, not the
flux confound. The flux question is whether irreversibility's BENEFIT is bigger with selection
than without it: (irrev-rev | with sel) vs (irrev-rev | no sel). */
double gain_sel = b[1]-a[1], gain_nosel = d[1]-e[1];
int not_flux = gain_sel > gain_nosel + 0.05;
printf(" DoL higher when irreversible : %s (%.1f%% vs %.1f%%)\n", dol_up?"YES":"no",100*b[1],100*a[1]);
printf(" still viable : %s (%.1f%%)\n", via_ok?"YES":"NO — collapsed",100*b[2]);
printf(" specialist fraction up : %s (%.3f vs %.3f)\n\n", spec_up?"YES":"no",b[0],a[0]);
if(dol_up && via_ok && not_flux)
printf(" => REVERSIBILITY WAS THE BLOCKER, AND IT SURVIVES ITS CONFOUND CHECK. Irreversibility\n"
" raises DoL by %+.1f%% with selection but only %+.1f%% without, so roughly %.0f%% of the\n"
" gain is a genuine selection x irreversibility INTERACTION rather than ratchet flux.\n"
" The sharpest reading is in the last line: with REVERSIBLE roles selection makes DoL\n"
" WORSE (%+.1f%%) — it drives specialists back toward generality, which is Law 2's\n"
" invasion visible directly. With roles irreversible, the same selection HELPS (%+.1f%%).\n"
" Law 2 transfers from rung 1->2 to rung 4->5: an ELECTIVE role is a strategy and gets\n"
" invaded; a STRUCTURAL one cannot be. Rung 5 needs a ratchet, not a bargain.\n",
100*gain_sel,100*gain_nosel,100.0*(gain_sel-gain_nosel)/gain_sel,
100*(a[1]-e[1]),100*(b[1]-d[1]));
else if(dol_up && via_ok && !not_flux)
printf(" => IT IS THE RATCHET, NOT SELECTION. Irreversibility raises division of labour, but the\n"
" SAME rise appears with selection switched off (%.1f%% vs %.1f%%): a one-way\n"
" generalist->specialist flux accumulates specialists over %d generations whether or\n"
" not anything is being selected, and more specialists mechanically means more\n"
" compartments happen to hold all %d roles. The knob moved two variables again.\n",
100*b[1],100*d[1],GEN,F);
else if(!via_ok)
printf(" => THE RATCHET KILLS IT. Irreversibility removes the retreat, and compartments collapse\n"
" to monoculture and lose a function instead. Specialisation without a way back is\n"
" fatal here — the second failure route the design deliberately left open.\n");
else
printf(" => HONEST NULL. Irreversibility alone does not stabilise division of labour; the\n"
" coordination valley survives removing the retreat, so reversibility was not what\n"
" rung45.c was stalling on.\n");
return 0;
}