/* linkage.c — rung 2 -> 3: do INDEPENDENT replicators become a LINKED sequence on their own?
*
* THE GAP THIS FILLS. The ladder was tested as 1->2 (open, 14+ experiments), 3->4 (rung34.c,
* EMERGED), 4->5 (rung45.c, honest negative). 2->3 was never tested as an emergence: rung2.c is a
* low-dimensional Eigen replicator-with-mutation and rung3.c is an exact quasispecies over Hamming
* classes -- both equations SOLVED, separately. The transition between them is "a few forms become
* a combinatorial form-space", i.e. Maynard Smith & Szathmary's independent-replicators ->
* chromosomes.
*
* WHY IT MIGHT ESCAPE THE ARC'S WALL. coupling/partition/signal established that a boundary which
* must be MAINTAINED cannot pay for itself: signal.c's investment collapses to the same low value
* from every starting point (measured -- invasion, not a coordination trap). Linkage is a boundary
* of a different kind. It is not maintained; it is STRUCTURAL. Linked genes share fate because they
* are one string, and defection is not a strategy available to them. So the question is whether a
* boundary nobody has to pay upkeep on can arise.
*
* THE TAUTOLOGY TRAP, AND THE CONTROL THAT AVOIDS IT. If a unit's fitness is computed from its own
* genes, linkage privatizes BY DEFINITION and "linkage evolves when combination is valuable" merely
* restates the setup. So everything here rests on arm B:
* arm A combinatorial benefit computed from YOUR OWN genes -> linkage can pay
* arm B the same benefit computed from a RANDOM DRAW of the pool, of the same size
* -> length costs exactly as much, and buys exactly nothing
* Length carries an identical replication penalty in both. If length rises in A and not in B, the
* driver is fate-sharing and not the mere fact that longer units are scored on more genes.
*
* WHAT IS SUPPLIED vs WHAT MUST EMERGE:
* SUPPLIED: fusion and fission as mutations, a combinatorial fitness (>= KREQ distinct types), and
* a replication penalty for length. The penalty is SWEPT, not picked.
* MUST EMERGE: linkage itself. Every unit starts at LENGTH 1 -- fully independent replicators.
* Nothing rewards being long as such; long units replicate strictly slower.
* ABSENT: containers, compartments, space, group selection. Global Wright-Fisher over units.
*
* PRE-REGISTERED:
* L1 arm A: mean length RISES above 1 and the fraction of units reaching KREQ distinct types rises.
* L2 arm B (control): mean length stays at ~1 at every cost. If B rises too, the effect is an
* artifact of scoring longer units on more genes and L1 means nothing.
* L3 there is a length cost above which even A collapses to 1 -- linkage is not free.
* L4 TEST OF THE TEST: fusion/fission must actually generate length variation. If length is pinned
* at 1 or at MAXLEN regardless of arm, the test did not run and a null says nothing.
*
* build: cc -O2 -Wall -std=c11 -o linkage linkage.c -lm
*/
#include <math.h>
#include <stdio.h>
#include <stdint.h>
#define POP 4000
#define TYPES 8
#define MAXLEN 8
#define KREQ 4 /* distinct types needed for the combinatorial payoff */
#define BONUS 2.0
#define GEN 300
#define MUT 0.02 /* per-gene type mutation */
#define PFUSE 0.05
#define PFISS 0.05
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 uint8_t gene[POP][MAXLEN], ngene[POP][MAXLEN];
static int len[POP], nlen[POP];
static double wgt[POP], cum[POP];
static int distinct_of(const uint8_t*g,int n){ int seen[TYPES]; for(int t=0;t<TYPES;t++) seen[t]=0; int d=0; for(int i=0;i<n;i++) if(!seen[g[i]]){ seen[g[i]]=1; d++; } return d; }
/* mode 0 = arm A (own genes). mode 1 = arm B control (same-size random draw from the pool). */
static void run(int mode,double lcost,double *len_end,double *frac_k,double *len_max,double *len_min)
{
for(int i=0;i<POP;i++){ len[i]=1; gene[i][0]=(uint8_t)(xs()%TYPES); }
for(int g=0;g<GEN;g++){
/* control draw size is the POPULATION MEAN length, NOT the individual's own length, so in
arm B length carries the identical cost and buys literally nothing. Drawing len[i] genes
(the first attempt) left length buying the payoff by another route -- a confounded control. */
int dsz;
{ double m=0; for(int i=0;i<POP;i++) m+=len[i]; dsz=(int)(m/POP+0.5); if(dsz<1)dsz=1; if(dsz>MAXLEN)dsz=MAXLEN; }
double tot=0;
for(int i=0;i<POP;i++){
int d;
if(mode==0) d=distinct_of(gene[i],len[i]);
else {
uint8_t draw[MAXLEN];
for(int k=0;k<dsz;k++){ int u=(int)(xs()%POP); int q=(int)(xs()%(unsigned long long)len[u]); draw[k]=gene[u][q]; }
d=distinct_of(draw,dsz);
}
double fit = 1.0 + ((d>=KREQ)? BONUS : 0.0);
double pen = 1.0/(1.0+lcost*(len[i]-1)); /* longer replicates strictly slower */
double w = fit*pen;
tot+=w; cum[i]=tot; wgt[i]=w;
}
for(int i=0;i<POP;i++){
double r=urand()*tot; int lo=0,hi=POP-1;
while(lo<hi){ int mid=(lo+hi)/2; if(cum[mid]<r) lo=mid+1; else hi=mid; }
int n=len[lo];
uint8_t tmp[2*MAXLEN];
for(int k=0;k<n;k++) tmp[k]=gene[lo][k];
if(urand()<PFUSE && n<MAXLEN){ /* FUSION: concatenate another unit */
int o=(int)(xs()%POP); int m=len[o];
for(int k=0;k<m && n<MAXLEN;k++) tmp[n++]=gene[o][k];
}
if(urand()<PFISS && n>1){ /* FISSION: keep a contiguous piece */
int cut=1+(int)(xs()%(unsigned long long)(n-1));
if(urand()<0.5){ n=cut; }
else { for(int k=0;k<n-cut;k++) tmp[k]=tmp[cut+k]; n=n-cut; }
}
for(int k=0;k<n;k++) if(urand()<MUT) tmp[k]=(uint8_t)(xs()%TYPES);
nlen[i]=n; for(int k=0;k<n;k++) ngene[i][k]=tmp[k];
}
for(int i=0;i<POP;i++){ len[i]=nlen[i]; for(int k=0;k<len[i];k++) gene[i][k]=ngene[i][k]; }
}
double sl=0; int nk=0, mx=0, mn=MAXLEN;
for(int i=0;i<POP;i++){
sl+=len[i];
if(len[i]>mx) mx=len[i];
if(len[i]<mn) mn=len[i];
if(distinct_of(gene[i],len[i])>=KREQ) nk++;
}
*len_end=sl/POP; *frac_k=(double)nk/POP; *len_max=mx; *len_min=mn;
}
int main(void)
{
unsigned long long seeds[8];
seeds[0]=0x9e3779b97f4a7c15ULL; seeds[1]=0xd1b54a32d192ed03ULL; seeds[2]=0x2545f4914f6cdd1dULL;
for(int i=3;i<8;i++) seeds[i]=0x9e3779b97f4a7c15ULL ^ (0x100000001b3ULL*(unsigned long long)(i+1));
int NS=8;
printf("=== LINKAGE (rung 2 -> 3): do independent replicators concatenate on their own? ===\n");
printf(" Every unit STARTS AT LENGTH 1. Fusion/fission are mutations. Payoff needs %d distinct\n",KREQ);
printf(" of %d types in one unit. Longer units replicate STRICTLY SLOWER. No compartments,\n",TYPES);
printf(" no space, no group selection; global Wright-Fisher over units.\n");
printf(" arm A = payoff from YOUR OWN genes. arm B = same payoff from a same-size RANDOM POOL\n");
printf(" DRAW of the POPULATION-MEAN size: length costs identically and buys NOTHING in B.\n\n");
printf(" length cost | A mean len | A frac>=%d | A len range | B mean len | B frac>=%d\n",KREQ,KREQ);
printf(" ------------+------------+-----------+-------------+------------+-----------\n");
double costs[]={0.00,0.02,0.05,0.10,0.20,0.40,0.80};
int NC=(int)(sizeof(costs)/sizeof(costs[0]));
int roseA=0, varied=0, sepcount=0, ncmp=0; double bestlen=0, bestcost=-1, minratio=1e9;
for(int c=0;c<NC;c++){
double la=0,fa=0,lb=0,fb=0,mx=0,mn=0;
for(int s=0;s<NS;s++){
double L,F,M,N; RNG=seeds[s]; run(0,costs[c],&L,&F,&M,&N); la+=L; fa+=F; mx+=M; mn+=N;
RNG=seeds[s]; run(1,costs[c],&L,&F,&M,&N); lb+=L; fb+=F;
}
la/=NS;fa/=NS;lb/=NS;fb/=NS;mx/=NS;mn/=NS;
printf(" %.2f | %.2f | %.3f | %.1f-%.1f | %.2f | %.3f\n",
costs[c],la,fa,mn,mx,lb,fb);
/* An absolute bar on B is the wrong statistic: this is a TWO-ARM comparison, and the
question is whether length pays MORE when the genes are your own. Judge the separation,
and only where cost bites (cost 0 has nothing selecting against length) and where A has
not itself collapsed (above the ceiling neither arm sustains length). */
if(costs[c]>0.0 && la>1.5){
roseA=1; ncmp++;
double ratio = (lb>0)? la/lb : 1e9;
if(ratio<minratio) minratio=ratio;
if(la-lb>1.0) sepcount++;
}
if(mx-mn>=2) varied=1;
if(la>bestlen){ bestlen=la; bestcost=costs[c]; }
}
printf("\n");
printf(" L4 TEST OF THE TEST — did length actually vary? %s\n", varied?"YES":"NO — test did not run");
printf(" L1 arm A length rose above 1? %s (best %.2f at cost %.2f)\n", roseA?"YES":"NO",bestlen,bestcost);
printf(" L2 A exceeds control B where cost bites? %d/%d costs, min A/B ratio %.2f\n", sepcount,ncmp,minratio);
printf("\n");
if(!varied)
printf(" => THE TEST DID NOT RUN. Fusion/fission never generated real length variation, so\n"
" nothing here speaks to whether linkage can emerge. Invalid experiment, not a null.\n");
else if(roseA && ncmp>0 && sepcount==ncmp && minratio>2.0)
printf(" => LINKAGE EMERGED, AND THE CONTROL RULES OUT THE TAUTOLOGY. Units start independent and\n"
" concatenate on their own despite a strict replication penalty for length, while the\n"
" pool-draw control -- identical cost, no fate-sharing -- stays at length 1. This is a\n"
" boundary that is STRUCTURAL rather than maintained: nobody pays upkeep, and defection\n"
" is not an available strategy because linked genes cannot replicate at different rates.\n"
" Contrast signal.c, where the maintained boundary collapsed from EVERY starting point.\n");
else if(roseA && sepcount<ncmp)
printf(" => CONFOUNDED. Length rose in the control too, so the effect is that longer units are\n"
" scored on more genes, not fate-sharing. Claim nothing about linkage.\n");
else
printf(" => HONEST NULL. Linkage did not emerge at any cost: the replication penalty for length\n"
" outweighs the combinatorial payoff throughout, so independent replicators stay\n"
" independent. A structural boundary is no easier to originate than a maintained one.\n");
return 0;
}