OverviewFilesCommitsIssuesDocsReleasesPeers

basal-agent / signal.c

266 lines · c ref:
/* signal.c — can an UNFORGEABLE-BUT-FAKEABLE display privatize catalysis, with no container
 *            and no topology?
 *
 * WHERE THIS COMES FROM. Three failures in the coupling thread (coupling.md, partition.md):
 * reciprocity is real and selected-for but insufficient; persistence was falsified by its own
 * lineage control; and a partitionable bilateral network fails because partitioning and gating turn
 * out to be ANTI-correlated -- breaking the graph to exclude parasites also cuts catalysts off from
 * each other.
 *
 * All of those tested SPATIAL boundaries: walls (containers) and edges (topology). That exhausts
 * the options in chemistry. It does not exhaust them in a digital substrate, where individuality is
 * routinely established by neither -- a writer set, a keypair, a signature. Membership there is not
 * containment or connectivity but UNFORGEABILITY: a parasite cannot join because it cannot produce
 * the credential. So the untested third kind of boundary is a VERIFICATION rule.
 *
 * THE RAZOR (issue #6). A rule saying "only catalysts may display" would encode the answer outright.
 * So the display must be CAUSALLY THE SAME STRUCTURE that makes a unit catalytic, and faking must
 * remain possible but expensive:
 *   catalysis   phi(s) = number of complementary hairpin pairs (i, L-1-i) in the sequence
 *   display     exhibit ONE such pair: pick a random index i, succeed iff bit_i != bit_{L-1-i}
 * A unit with phi=12 succeeds on its first attempt; a unit with phi=2 needs ~6; phi=1 needs ~12.
 * The expected cost of an honest display is exactly 12/phi. That IS Zahavi's handicap condition --
 * signalling is cheaper for higher quality -- but it is not written down as a cost formula anywhere.
 * It EMERGES from searching your own material. Crucially a parasite CAN still display; it just pays
 * more attempts. Whether honest signalling is stable is left open, which is the point.
 *
 * WHAT IS SUPPLIED vs WHAT MUST EMERGE:
 *  SUPPLIED: the display primitive (show one good pair), bilateral gating (a junction opens only if
 *    BOTH ends displayed), and a heritable OPTION sigma = how many attempts a unit is willing to
 *    spend. The per-attempt cost is SWEPT, not picked.
 *  MUST EMERGE: honesty. Nothing prevents a low-phi unit from spending attempts until it passes.
 *  ABSENT: containers, space, assortment, group selection, persistent structure. The junction graph
 *    is rebuilt at random every generation and replication is global Wright-Fisher.
 *
 * Chemistry identical to ribozyme.c / coupling.c / partition.c (phi = hairpin fold, parasites 3x
 * faster, F0=0.85, F1=0.999, 4800 units, 200 gens). Reference points: ribozyme well-mixed
 * 5.99->5.50; coupling FRESH k=0 4.69 / k=1 5.36; partition best 5.82. Start is always ~5.99.
 *
 * PRE-REGISTERED (written before the first run):
 *  S1 no gating (all junctions open)      -> phi FALLS. The public-goods baseline.
 *  S2 gating on honest display            -> phi RISES above its random start. The crossing.
 *  S3 sigma free to evolve                -> sigma settles somewhere non-trivial AND phi rises.
 *  S4 CONTROL, RANDOM display: success probability is the POPULATION MEAN rate rather than the
 *     unit's own phi -- same costs, same pass rate, but the display carries NO information about
 *     quality. If phi rises here too, the effect is the cost burden, not the signal, and S2/S3
 *     mean nothing.
 *  S5 TEST OF THE TEST: does the display actually separate quality? Report mean phi among units
 *     that displayed vs those that did not. If those are equal, the signal is uninformative and a
 *     null says nothing -- the mistake ribophi.c made with its not-actually-early window and
 *     partition.c made on its first run.
 *
 * This is hypothesis #4. If it fails too, the convergence across containers, topology and
 * verification is itself the result. Do not tune to force a positive.
 *
 * build:  cc -O2 -Wall -std=c11 -o signal signal.c -lm
 */
#include <math.h>
#include <stdio.h>
#include <stdint.h>

/* ---------- identical to ribozyme.c / coupling.c / partition.c ---------- */
#define L      24
#define PHIMAX (L/2)
#define TOTAL  4800
#define GEN    200
#define F0     0.85
#define F1     0.999
#define BETA   2.0

/* ---------- signalling ---------- */
#define LINKS  4
#define AMAX   12          /* maximum display attempts a unit may spend */
#define MUTS   0.03
#define MINW   0.05        /* replication weight floor, so cost cannot make weight negative */

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 double randn(void){ double u=urand(),v=urand(); if(u<1e-300)u=1e-300; return sqrt(-2*log(u))*cos(6.283185307*v); }

static int phi(uint32_t s){ int c=0; for(int i=0;i<PHIMAX;i++){ int a=(s>>i)&1, b=(s>>(L-1-i))&1; if(a!=b) c++; } return c; }
static uint32_t replicate(uint32_t t,double f){ uint32_t c=0; for(int i=0;i<L;i++){ int b=(t>>i)&1; int nb=(urand()<f)?(1-b):b; if(nb) c|=(1u<<i);} return c; }

static uint32_t seq[TOTAL], nseq[TOTAL];
static double   sg[TOTAL], nsg[TOTAL];
static int      phic[TOTAL], disp[TOTAL], att[TOTAL];
static int      lnk[TOTAL][LINKS];
static double   recv[TOTAL], fid[TOTAL], cumw[TOTAL];

/* gating: 0 = none (all junctions open), 1 = honest display, 2 = RANDOM display (control) */
static double run(int gating,int evolve_s,double sfix,double catt,double sstart,
                  double *s_end,double *phi_start,double *disp_frac,
                  double *phi_disp,double *phi_nodisp,double *mean_att)
{
	for(int i=0;i<TOTAL;i++){
		uint32_t s=0; for(int b=0;b<L;b++) if(urand()<0.5) s|=(1u<<b);
		seq[i]=s; sg[i]= evolve_s? sstart : sfix;
	}
	{ double sp=0; for(int i=0;i<TOTAL;i++) sp+=phi(seq[i]); *phi_start=sp/TOTAL; }

	double lastD=0,lastPD=0,lastPN=0,lastA=0;

	for(int g=0;g<GEN;g++){
		for(int i=0;i<TOTAL;i++) phic[i]=phi(seq[i]);

		/* population mean pass rate, for the RANDOM-display control */
		double meanp=0; for(int i=0;i<TOTAL;i++) meanp+=(double)phic[i]/PHIMAX; meanp/=TOTAL;

		/* --- display phase --- */
		for(int i=0;i<TOTAL;i++){
			int A=(int)(sg[i]*AMAX+0.5); if(A<0)A=0; if(A>AMAX)A=AMAX;
			int used=0, ok=0;
			double p = (gating==2)? meanp : (double)phic[i]/PHIMAX;   /* control: quality-blind */
			for(int a=0;a<A;a++){
				used++;
				if(urand()<p){ ok=1; break; }      /* "show one of your own good pairs" */
			}
			disp[i]= gating? ok : 1;               /* no gating -> everyone participates */
			att[i]= gating? used : 0;
		}

		/* --- fresh random wiring, bilateral gating --- */
		for(int i=0;i<TOTAL;i++) for(int l=0;l<LINKS;l++){ int j; do{ j=(int)(xs()%TOTAL); }while(j==i); lnk[i][l]=j; }
		for(int i=0;i<TOTAL;i++) recv[i]=0.0;
		for(int i=0;i<TOTAL;i++){
			for(int l=0;l<LINKS;l++){
				int j=lnk[i][l];
				if(!(disp[i]&&disp[j])) continue;             /* junction opens only if BOTH displayed */
				recv[j]+=(double)phic[i]/PHIMAX;
				recv[i]+=(double)phic[j]/PHIMAX;
			}
		}
		for(int i=0;i<TOTAL;i++){
			double R=recv[i]/(2.0*LINKS); if(R<0)R=0; if(R>1)R=1;
			fid[i]=F0+(F1-F0)*R;
		}

		/* --- replication: parasites still 3x faster; display attempts cost --- */
		double tot=0;
		for(int i=0;i<TOTAL;i++){
			double w=(1.0+BETA*(1.0-(double)phic[i]/PHIMAX))*(1.0-catt*att[i]);
			if(w<MINW) w=MINW;
			tot+=w; cumw[i]=tot;
		}
		for(int i=0;i<TOTAL;i++){
			double r=urand()*tot; int lo=0,hi=TOTAL-1;
			while(lo<hi){ int mid=(lo+hi)/2; if(cumw[mid]<r) lo=mid+1; else hi=mid; }
			nseq[i]=replicate(seq[lo],fid[lo]);
			double s2 = evolve_s ? sg[lo]+MUTS*randn() : sg[lo];
			if(s2<0)s2=0;
			if(s2>1)s2=1;
			nsg[i]=s2;
		}

		/* S5 diagnostics, measured on the generation just scored */
		{
			double sd=0,sn=0,sa=0; int nd=0,nn=0;
			for(int i=0;i<TOTAL;i++){ sa+=att[i]; if(disp[i]){ sd+=phic[i]; nd++; } else { sn+=phic[i]; nn++; } }
			lastD=(double)nd/TOTAL; lastPD= nd? sd/nd : NAN; lastPN= nn? sn/nn : NAN; lastA=sa/TOTAL;
		}
		for(int i=0;i<TOTAL;i++){ seq[i]=nseq[i]; sg[i]=nsg[i]; }
	}

	double sp=0,ss=0;
	for(int i=0;i<TOTAL;i++){ sp+=phi(seq[i]); ss+=sg[i]; }
	*s_end=ss/TOTAL; *disp_frac=lastD; *phi_disp=lastPD; *phi_nodisp=lastPN; *mean_att=lastA;
	return sp/TOTAL;
}

int main(void)
{
	unsigned long long seeds[10];
	seeds[0]=0x9e3779b97f4a7c15ULL; seeds[1]=0xd1b54a32d192ed03ULL; seeds[2]=0x2545f4914f6cdd1dULL;
	for(int i=3;i<10;i++) seeds[i]=0x9e3779b97f4a7c15ULL ^ (0x100000001b3ULL*(unsigned long long)(i+1));
	int NS=10;

	printf("=== COSTLY SIGNAL: can an unforgeable-but-fakeable display privatize catalysis? ===\n");
	printf("  Display = exhibit ONE of your own complementary pairs. phi=12 passes first try;\n");
	printf("  phi=2 needs ~6 attempts. Handicap condition EMERGES from search, is not written down.\n");
	printf("  Faking stays possible: a parasite may spend attempts until it passes.\n");
	printf("  Junction opens only if BOTH ends displayed. No container, no space, no group selection.\n");
	printf("  REFERENCE: start ~5.99. ribozyme well-mixed 5.50; coupling k=1 5.36; partition best 5.82.\n\n");

	double costs[]={0.00,0.05,0.10,0.20,0.30,0.50,0.70,1.00};
	int NC=(int)(sizeof(costs)/sizeof(costs[0]));
	struct { const char*name; int gate; int ev; double sf; } arms[]={
		{"no gating (public good)", 0, 0, 0.0},
		{"HONEST display sigma=1 ", 1, 0, 1.0},
		{"honest, sigma evolvable", 1, 1, 0.0},
		{"RANDOM display (ctrl)  ", 2, 0, 1.0},
	};
	int NA=4;
	double best=-1e9, bestc=-1; int informative=0;

	for(int c=0;c<NC;c++){
		printf("  --- attempt cost = %.2f ---\n",costs[c]);
		double e_ng=0,e_h=0,e_r=0;
		for(int a=0;a<NA;a++){
			double S=0,E=0,SS=0,DF=0,PD=0,PN=0,MA=0; double ev[16];
			for(int s=0;s<NS;s++){
				double se,ps,df,pd,pn,ma; RNG=seeds[s];
				double e=run(arms[a].gate,arms[a].ev,arms[a].sf,costs[c],0.10,&se,&ps,&df,&pd,&pn,&ma);
				ev[s]=e; E+=e; S+=ps; SS+=se; DF+=df; PD+=pd; PN+= isnan(pn)?pd:pn; MA+=ma;
			}
			S/=NS;E/=NS;SS/=NS;DF/=NS;PD/=NS;PN/=NS;MA/=NS;
			double sd=0; for(int s=0;s<NS;s++) sd+=(ev[s]-E)*(ev[s]-E); sd=sqrt(sd/(NS-1));
			printf("  %s | %.2f -> %5.2f +- %.2f | sigma %.3f | displayed %5.1f%% | phi disp/no %.2f/%.2f | att %.2f\n",
				arms[a].name,S,E,sd,SS,100.0*DF,PD,PN,MA);
			if(a==0) e_ng=E;
			if(a==1){ e_h=E; if(PD-PN>0.3) informative=1; if(E-S>best){ best=E-S; bestc=costs[c]; } }
			if(a==3) e_r=E;
		}
		printf("      honest gain over public good %+.2f | over RANDOM display %+.2f\n\n", e_h-e_ng, e_h-e_r);
	}

	printf("  S5 TEST OF THE TEST — did the display ever separate quality? %s\n",
		informative?"YES (phi of displayers exceeds non-displayers)":"NO — signal uninformative");
	printf("  best phi change from START, honest arm: %+.2f at cost %.2f  (start ~5.99)\n\n",best,bestc);

	if(!informative)
		printf("  => THE TEST DID NOT RUN. The display never distinguished high-phi from low-phi units,\n"
		       "     so nothing here speaks to whether verification can privatize catalysis. Report as an\n"
		       "     invalid experiment, NOT as a null.\n");
	else if(best>0.05)
		printf("  => phi ROSE ABOVE ITS RANDOM START (%+.2f), the first mechanism in the arc to do so with\n"
		       "     NO container. The RANDOM-display control stayed flat, so the effect is the signal and\n"
		       "     not the cost burden. But read the size: compartments took phi to 11.6, this is a rise\n"
		       "     off 5.99, and check whether sigma-evolvable tracks sigma=1 -- if selection drives sigma\n"
		       "     DOWN, honest signalling works when imposed but does not self-install.\n",best);
	else
		printf("  => HONEST NULL, AND IT IS THE FOURTH. Containers were the only thing that ever worked;\n"
		       "     wiring, persistence, topology and now verification all fail to privatize catalysis.\n"
		       "     That convergence across three DIFFERENT KINDS of boundary -- spatial, topological and\n"
		       "     credential -- is the result.\n");

	/* ================= BISTABILITY TEST =================
	   Is sigma's collapse an INVASION (non-signallers beat signallers -> genuine second-order
	   public good, and the multilevel circle is real) or a COORDINATION TRAP (both sigma=1 and
	   sigma=0 stable, we merely started low -> the circle is an artifact of the initial condition)?
	   Decisive test: start the evolvable arm HIGH and see whether it stays. */
	printf("\n=== BISTABILITY: does sigma STAY high if it starts high? ===\n");
	printf("  invasion  => sigma falls from 1.0  => second-order public good, circle REAL\n");
	printf("  trap      => sigma stays near 1.0  => coordination bistability, circle an ARTIFACT of start\n\n");
	printf("  cost | sigma start 0.10 -> end | phi  | sigma start 0.50 -> end | phi  | sigma start 1.00 -> end | phi\n");
	printf("  -----+-------------------------+------+-------------------------+------+------------------------+-----\n");
	double starts[3]={0.10,0.50,1.00};
	for(int c=0;c<NC;c++){
		double se_[3],ph_[3];
		for(int q=0;q<3;q++){
			double SS=0,E=0;
			for(int s=0;s<NS;s++){
				double se,ps,df,pd,pn,ma; RNG=seeds[s];
				E+=run(1,1,0.0,costs[c],starts[q],&se,&ps,&df,&pd,&pn,&ma);
				SS+=se;
			}
			se_[q]=SS/NS; ph_[q]=E/NS;
		}
		printf("  %.2f |      %.3f -> %.3f      | %.2f |      %.3f -> %.3f      | %.2f |     %.3f -> %.3f      | %.2f\n",
			costs[c],starts[0],se_[0],ph_[0],starts[1],se_[1],ph_[1],starts[2],se_[2],ph_[2]);
	}
	printf("\n  Read the sigma=1.00 column: if it decays toward the 0.10 column, non-signallers INVADE\n");
	printf("  and the circle is real. If it holds high with correspondingly high phi, the system is\n");
	printf("  BISTABLE and the collapse we reported was an artifact of starting at 0.10.\n");
	return 0;
}