/* ladder35.c — MILESTONE 4: rungs 3 and 5 in the same type. No new machinery.
*
* The substrate now carries species, reactions, containers-as-species and counts (substrate.c,
* container.c, counts.c), expressing rungs 1, 2 and 4 and reproducing rung1.c's threshold,
* polymer.c's bootstrap problem, ribozyme.c's ordering and byproduct.c's isolation control.
* The claim was that rungs 3 and 5 need no new type. This tests it.
*
* rung 3 species ARE sequences. A replication reaction copies a species to itself; mutation is a
* variant-producing reaction to a Hamming neighbour. The rung-3 property is Eigen's ERROR
* CATASTROPHE — a sharp transition from localised on a master sequence to delocalised over
* sequence space, at mu_c ~ ln(sigma)/L.
* rung 5 a specialist is a species participating in only PART of the reaction set. Division of
* labour is complementary specialists whose union covers everything the container needs,
* none of which suffices alone.
*
* THE CHECK, as at every milestone: expressing something is worthless unless it reproduces the
* result we already have.
* L3 the master-sequence fraction must show a SHARP fall with mutation rate, and the observed
* threshold must sit near ln(sigma)/L — rung3.c's exact quasispecies value.
* L5 specialists must sustain complementary coverage that no single species provides, and
* coverage must COLLAPSE when the requirement for complementarity is removed (the control).
*
* build: cc -O2 -Wall -std=c11 -o ladder35 ladder35.c -lm
*/
#include <math.h>
#include <stdio.h>
#include <string.h>
#define LBITS 8
#define NSEQ (1<<LBITS) /* 256 sequence-species — species ARE sequences */
#define POP 4000
#define GENS 300
#define SIGMA 4.0 /* master-sequence selective advantage */
#define NROLE 3
#define NSPC 12 /* rung-5 species */
#define NCOMP 400
#define CSZ 10
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 rint_(int n){ return (int)(xs()%(unsigned)n); }
/* ---------------- RUNG 3: species are sequences; mutation is a reaction to a neighbour --------- */
static int seqpop[NSEQ], nxtpop[NSEQ];
static double fit_of(int s){ return (s==0)? SIGMA : 1.0; }
static double rung3(double mu){
memset(seqpop,0,sizeof seqpop);
for(int i=0;i<POP;i++) seqpop[rint_(NSEQ)]++;
for(int g=0;g<GENS;g++){
double tot=0; static double cum[NSEQ];
for(int s=0;s<NSEQ;s++){ tot += seqpop[s]*fit_of(s); cum[s]=tot; }
memset(nxtpop,0,sizeof nxtpop);
for(int i=0;i<POP;i++){
double r=urand()*tot; int lo=0,hi=NSEQ-1;
while(lo<hi){ int mid=(lo+hi)/2; if(cum[mid]<r) lo=mid+1; else hi=mid; }
int child=lo;
for(int b=0;b<LBITS;b++) if(urand()<mu) child^=(1<<b); /* mutation = variant reaction */
nxtpop[child]++;
}
memcpy(seqpop,nxtpop,sizeof seqpop);
}
return (double)seqpop[0]/POP;
}
/* ---------------- RUNG 5: a specialist participates in only PART of the reaction set ----------- */
static unsigned char covers[NSPC][NROLE]; /* which roles a species can perform */
static int comp[NCOMP][CSZ], ncomp2;
static void build_species(int generalists_allowed){
for(int s=0;s<NSPC;s++){
memset(covers[s],0,sizeof covers[s]);
if(generalists_allowed && s<NROLE){ for(int r=0;r<NROLE;r++) covers[s][r]=1; }
else covers[s][s%NROLE]=1; /* specialist: exactly one role */
}
}
static int coverage(const int *cell){
int seen[NROLE]; memset(seen,0,sizeof seen);
for(int k=0;k<CSZ;k++) for(int r=0;r<NROLE;r++) if(covers[cell[k]][r]) seen[r]=1;
int c=0; for(int r=0;r<NROLE;r++) c+=seen[r];
return c;
}
/* need_all=1: a container is viable only with ALL roles covered (complementarity required).
need_all=0 (CONTROL): any single role suffices — complementarity is not demanded. */
static double rung5(int need_all){
ncomp2=NCOMP;
for(int c=0;c<NCOMP;c++) for(int k=0;k<CSZ;k++) comp[c][k]=rint_(NSPC);
for(int g=0;g<200;g++){
static int nxt[NCOMP][CSZ];
double tot=0,cum[NCOMP];
for(int c=0;c<NCOMP;c++){
int cov=coverage(comp[c]);
double v = need_all ? ((cov==NROLE)?1.0:0.02) : ((cov>=1)?1.0:0.02);
tot+=v; cum[c]=tot;
}
for(int c=0;c<NCOMP;c++){
double r=urand()*tot; int lo=0,hi=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<CSZ;k++){
int src=comp[lo][rint_(CSZ)];
if(urand()<0.02) src=rint_(NSPC);
nxt[c][k]=src;
}
}
memcpy(comp,nxt,sizeof comp);
}
double full=0;
for(int c=0;c<NCOMP;c++) if(coverage(comp[c])==NROLE) full++;
return full/NCOMP;
}
int main(void)
{
printf("=== MILESTONE 4: rungs 3 and 5 in the same type, no new machinery ===\n\n");
/* ---- L3 ---- */
double muc = log(SIGMA)/LBITS;
printf(" L3 — rung 3: species ARE sequences, mutation is a variant-producing reaction.\n");
printf(" Eigen predicts an error catastrophe near mu_c = ln(sigma)/L = ln(%.0f)/%d = %.3f\n\n",SIGMA,LBITS,muc);
printf(" mu | master-sequence fraction\n");
printf(" -------+-------------------------\n");
double mus[7]={0.02,0.08,0.14,0.17,0.22,0.30,0.45};
double v[7];
for(int i=0;i<7;i++){
double a=0; for(int s=0;s<4;s++){ RNG=0x9e3779b97f4a7c15ULL+(unsigned long long)(i*97+s)*0x2545F4914F6CDD1DULL; a+=rung3(mus[i]); }
v[i]=a/4;
printf(" %.2f | %.4f %s\n",mus[i],v[i], (mus[i]<muc)?"(below mu_c)":"(above)");
}
/* Locate the threshold as the CROSSING of a low master fraction, not the biggest absolute drop.
On a monotone decreasing curve the largest absolute drop always sits where the curve is
highest, so it locates the steep start, not the catastrophe — the same wrong-statistic error
as linkage.c's "B stays near 1" bar. Interpolate where the master fraction crosses 0.05. */
double where=-1;
for(int i=0;i+1<7;i++){
if(v[i]>=0.05 && v[i+1]<0.05){
double t=(v[i]-0.05)/(v[i]-v[i+1]);
where=mus[i]+t*(mus[i+1]-mus[i]); break;
}
}
int l3 = (v[0]>0.3) && (v[6]<0.05) && where>0 && fabs(where-muc)<0.06;
printf("\n master fraction crosses 0.05 at mu = %.3f vs Eigen prediction %.3f (diff %+.3f)\n",where,muc,where-muc);
printf(" => %s\n\n", l3?"LOCALISED below, DELOCALISED above, transition near ln(sigma)/L — rung 3 expressed."
:"does NOT reproduce the error catastrophe — rung 3 is not expressed.");
/* ---- L5 ---- */
printf(" L5 — rung 5: a specialist participates in only PART of the reaction set.\n");
printf(" Division of labour = complementary specialists whose union covers what one cannot.\n\n");
build_species(0);
double spec_need = rung5(1), spec_ctrl = rung5(0);
build_species(1);
double gen_need = rung5(1);
printf(" arm | containers with ALL %d roles\n",NROLE);
printf(" --------------------------------------+---------------------------\n");
printf(" specialists, complementarity REQUIRED | %.3f\n",spec_need);
printf(" specialists, requirement REMOVED (ctl)| %.3f\n",spec_ctrl);
printf(" generalists available | %.3f\n",gen_need);
int l5 = (spec_need>0.5) && (spec_ctrl < spec_need-0.1);
printf("\n => %s\n\n", l5?"complementary coverage is sustained, and COLLAPSES when not required —\n division of labour is a reaction-participation pattern, not a new type."
:"coverage does not behave as rung 5 — not expressed.");
printf(" MILESTONE 4 %s\n", (l3&&l5)?"PASSED — rungs 1,2,3,4,5 all live in one type."
:"NOT PASSED — see which check failed above.");
printf(" Still scaffolding: nothing here CROSSES a rung. It removes the reason crossings could\n");
printf(" not compose, which is what the whole ladder has lacked.\n");
return 0;
}