OverviewFilesCommitsIssuesDocsReleasesPeers

hypercore-kb / basal-agent / threecarrier.c

336 lines · c ref:
/* threecarrier.c — INVALID AS RUN. DO NOT READ ITS NUMBERS AS A RESULT.
 *
 * Two faults, one fixed and one fatal, both recorded so the next attempt starts from them.
 *
 * FIXED: the first version set nsz[c]=newn, REPLACING each compartment with only what production
 * could afford, so nothing persisted and every arm starved to extinction — FULL ended at 0 units and
 * 0 PARASITES, which is death, not collapse-to-parasites. Units now persist, production adds, and a
 * per-unit death rate removes.
 *
 * FOURTH ATTEMPT — fluctuating environment. NOT void (T0 passed, arms differ), but the
 * PRE-REGISTERED METRIC WAS WRONG, and wrong in a way already documented in genome.md.
 *
 *   arm                         | templates | working | function
 *   STATIC, full                |   0.007   |  0.940  |  2.41
 *   FLUCTUATING every 40        |   0.013   |  0.924  |  2.28
 *   FLUCTUATING every 15        |   0.012   |  0.478  |  1.47
 *   FLUCTUATING 15, reading OFF |   0.023   |  0.294  |  1.27
 *
 * M1/M2 were both scored on TEMPLATE ABUNDANCE. That is the wrong variable: reading CONSUMES
 * templates by converting them into catalysts, so the reading-OFF arm holds MORE of them (0.023 vs
 * 0.012) precisely because they are useless there. Template count measures templates NOT BEING USED.
 * genome.md records this exact mistake; this is the third time in one session it has been made.
 *
 * ON THE RIGHT VARIABLE the effect is present: under fluctuation, turning reading ON raises the
 * working fraction 0.294 -> 0.478 and function 1.27 -> 1.47. Templates are being USED and it helps.
 * That is suggestive, not established: it was not the pre-registered test, n=5 seeds, and it needs a
 * clean re-run scored on function with its own control before it can be claimed.
 *
 * ---- third attempt ----
 * THIRD ATTEMPT, ALSO VOID — and it refines the diagnosis rather than fixing it.
 * The payoff was made EXPENSIVE TO REDISCOVER (NTARG=4 SPECIFIC types out of NTYPE=16, so a lost
 * type costs many mutations to re-find instead of one in four). Templates STILL collapse:
 * 0.007 / 0.007 / 0.010 / 0.006 across the arms, and T0 voids the comparison again.
 *
 * THE REAL OBSTACLE IS DEEPER THAN CHEAP REDISCOVERY. A template costs a slot NOW, which lowers
 * current function — and between-compartment selection scores current function EVERY generation. Its
 * benefit is DEFERRED and CONDITIONAL: it pays only if a type is later lost. Making rediscovery
 * expensive raises the deferred payoff without touching the immediate cost, so the trade is still
 * lost before it can be won.
 *
 * WHAT WOULD ACTUALLY BE NEEDED: losses must occur often enough that the deferred benefit lands
 * inside the selection horizon. That means few copies per type — small compartments — which is
 * exactly the regime fidfloor.c measured as destroying sequence information. So the requirement
 * collides with an already-measured result, and this is not a parameter away from working.
 *
 * THREE ATTEMPTS, THREE VOIDS, each revealing the previous diagnosis was incomplete. Stopping here
 * is the honest call: the question is good and the design is not converging.
 *
 * ---- second attempt ----
 * SECOND ATTEMPT, ALSO VOID — and the diagnosis was already on record before it was built.
 * K1 was fixed (growth now tracks TOTAL SIZE, so a parasite-filled compartment grows as well as a
 * catalyst-filled one, and the knockout receives MORE material than FULL) and the metrics were
 * separated (integrity = function >= 2; template retention reported on its own). A T0 guard was
 * added: do the arms differ at all?
 *
 * They do not. FULL 0.940 / K1 0.954 / K2 0.944 / K3 0.952, function 2.45-2.52 throughout. The
 * reason is in the template column: 0.007-0.012 IN EVERY ARM. Templates are absent, so there is no
 * third carrier and the two couplings that involve one cannot possibly matter — K2 has nothing to
 * read, K3 nothing whose fidelity counts. The system persists on boundary + metabolism alone.
 *
 * WHY TEMPLATES COLLAPSE, AND IT IS A REPEAT: func_of() counts only catalysts, so selection on
 * function selects AGAINST templates — they are pure cost. That is exactly genome.c's finding
 * ("nothing is expensive enough to be worth remembering; payoff is scored on the present
 * configuration so rediscovery always beats storage"), identified hours earlier and then rebuilt.
 *
 * THE FIX IS THEREFORE KNOWN AND NOT A GUESS: the payoff must reward something that is EXPENSIVE TO
 * REDISCOVER, so that holding a template beats re-finding a catalyst type. Until the fitness function
 * has that property, no three-carrier model can show the third carrier doing anything.
 *
 * ---- first attempt's fault, kept for the record ----
 * FATAL, AND WHY THE RESULT IS VOID: after that fix all four arms land within noise of each other
 * (intact 0.052 / 0.055 / 0.037 / 0.049; function 2.47 / 2.46 / 2.45 / 2.52). That reads as "no
 * coupling is load-bearing", but at least one arm is not a knockout at all:
 *   K1 gives growth GROW*CAP*0.5 = 4.4, and the coupled case gives GROW*nc with nc~8 = 4.4.
 *   IDENTICAL BY CONSTRUCTION. The "decoupled" control was calibrated to the coupled value.
 * A second problem sits underneath: the "intact" metric requires catalysts AND templates, but
 * reading CONSUMES templates (the same mistake genome.c made), so intact is measuring template
 * retention rather than system integrity while function stays high at ~2.5/4.
 *
 * So nothing here bears on whether the complete system is stable. The verdict text below is left
 * exactly as the program printed it, and it is WRONG.
 *
 * FOR THE NEXT ATTEMPT: make each knockout a real ablation (K1 must give growth genuinely unrelated
 * to catalytic content, e.g. a rate that does not track nc), separate template retention from system
 * integrity in the metrics, and confirm the arms differ before reading anything into them.
 *
 * ---- original design notes follow ----
 *
 * threecarrier.c — is the COMPLETE system stable? (not: can it bootstrap)
 *
 * The arc has only ever tested REACHABILITY of parts. It has never tested STABILITY of the whole.
 * Every failure had one shape — one object asked to do two jobs: catalyst = function AND information
 * (fidfloor.c: no compartment size both corrects and remembers); compartment = isolation AND
 * selection (byproduct.c); replicase = catalyst AND its own specification (replicase.c); role =
 * elective AND stable (rung45.c). The two things that WORKED — linkage.c, rung45irr.c — worked by
 * making a coupling STRUCTURAL rather than elective.
 *
 * So: three carriers, not one. A BOUNDARY nobody maintains, a METABOLISM that produces, and a
 * TEMPLATE that only remembers, with couplings that cannot be defected on. That is Ganti's chemoton,
 * and his claim was always that all three are needed SIMULTANEOUSLY. Every failure above was an
 * attempt to get two and derive the third.
 *
 * THE TEST IS STABILITY, WHICH IS A DIFFERENT QUESTION. Start the system COMPLETE and correct, then
 * ask whether it persists against parasite invasion, mutation and competition. Nothing here has to
 * bootstrap; the question is only whether the finished thing holds together.
 *
 * THE THREE COUPLINGS, each non-discretionary:
 *   metabolism -> boundary : catalysts produce the material the compartment grows from. You build
 *                            iff you catalyse; wall-building is not an investment anyone elects.
 *   template   -> function : a template is read to make a catalyst of its type.
 *   catalyst   -> template : catalysts copy templates at high fidelity (the replicase role).
 *
 * ARMS. FULL, then each coupling KNOCKED OUT in turn. If breaking any one collapses the system, all
 * three are load-bearing and the complete system is stable only when complete — which is the
 * simultaneous-origin claim, tested rather than asserted.
 *   K1 boundary decoupled : compartments grow at a fixed rate regardless of catalytic content
 *   K2 reading off        : templates cannot be read
 *   K3 replicase off      : templates copy at base fidelity regardless of catalysts present
 *
 * PARASITES ARE NOT SEEDED. They arise by mutation from catalysts and templates, replicate faster,
 * and contribute nothing — so the tragedy is live in every arm.
 *
 * PRE-REGISTERED:
 *  T1 FULL persists — most compartments still hold catalysts AND templates after GEN generations.
 *  T2 every knockout collapses. If a knockout persists, that coupling is not load-bearing.
 *  T3 TEST OF THE TEST: parasites must actually appear and be able to win, or nothing was resisted.
 *
 * If FULL also collapses, the complete system is not even stable, which is a stronger negative than
 * anything the arc has produced.
 *
 * build:  cc -O2 -Wall -std=c11 -o threecarrier threecarrier.c -lm
 */
#include <math.h>
#include <stdio.h>
#include <string.h>

#define NCOMP  600
#define CAP    16
#define GEN    400
#define NTYPE  16          /* larger type space: a lost type is EXPENSIVE to re-find */
#define NTARG  4           /* payoff needs these SPECIFIC types, not any four */
#define BETA   2.0          /* parasite replication advantage */
#define MUT    0.02         /* role/type mutation */
#define FLO    0.85         /* template copy fidelity without a catalyst present */
#define FHI    0.995        /* ... with one (the replicase coupling) */
#define GROW   0.55
#define DEATH  0.15         /* per-unit death per generation; production must exceed it */         /* material per catalyst per generation */

enum { CAT=0, TEM=1, PAR=2 };

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); }

typedef struct { unsigned char role, type; } Unit;
static Unit cell[NCOMP][CAP], nxt[NCOMP][CAP];
static int  sz[NCOMP], nsz[NCOMP];
static double mat[NCOMP], nmat[NCOMP];
static int ncomp;
static double TFRAC;
static int TARGET[NTYPE];

static void counts(int c,int *nc,int *nt,int *np){
	*nc=*nt=*np=0;
	for(int k=0;k<sz[c];k++){
		if(cell[c][k].role==CAT) (*nc)++;
		else if(cell[c][k].role==TEM) (*nt)++;
		else (*np)++;
	}
}
/* function = how many of the SPECIFIC target types are present among catalysts.
   THIS IS THE FIX. Previously any of 4 types counted, so a random mutation supplied a useful type
   one time in four and rediscovery was cheap — genome.c's diagnosis, rebuilt by accident. Now the
   payoff needs %d particular types out of %d, so a lost type costs many mutations to re-find and a
   template that holds one is worth keeping. Nothing rewards templates directly; they still fold
   nothing and still cost a slot. */
static int func_of(int c){
	int seen[NTYPE]; memset(seen,0,sizeof seen);
	for(int k=0;k<sz[c];k++) if(cell[c][k].role==CAT) seen[cell[c][k].type]=1;
	int f=0; for(int t=0;t<NTYPE;t++) if(seen[t]&&TARGET[t]) f++;
	return f;
}

/* ko: 0 = FULL, 1 = boundary decoupled, 2 = reading off, 3 = replicase off */
static void retarget(void){
	for(int t=0;t<NTYPE;t++) TARGET[t]=0;
	for(int got=0;got<NTARG;){ int t=rint_(NTYPE); if(!TARGET[t]){ TARGET[t]=1; got++; } }
}
/* switch_every = 0 means a STATIC environment (the previous condition). Otherwise the target set is
   redrawn every switch_every generations: a type not needed now becomes needed later, so holding one
   in a template pays REGULARLY rather than only if something is lost. That converts the deferred,
   conditional benefit into a recurring one WITHOUT requiring small compartments — which is the
   constraint fidfloor.c placed on any fix. */
static int SWITCH_EVERY=0;
static void run(int ko,double *alive,double *funcavg,double *parfrac)
{
	retarget();
	ncomp=NCOMP;
	for(int c=0;c<ncomp;c++){
		sz[c]=CAP; mat[c]=0;
		for(int k=0;k<CAP;k++){                       /* SEEDED COMPLETE: catalysts + templates */
			/* seeded COMPLETE and CORRECT: catalysts and templates covering the target set */
			int tg=-1,seen=0;
			for(int t=0;t<NTYPE&&tg<0;t++) if(TARGET[t]){ if(seen==(k/2)%NTARG) tg=t; seen++; }
			cell[c][k].role = (k%2)?TEM:CAT;
			cell[c][k].type = (unsigned char)(tg<0?0:tg);
		}
	}
	for(int g=0;g<GEN;g++){
		if(SWITCH_EVERY>0 && g>0 && g%SWITCH_EVERY==0) retarget();
		for(int c=0;c<ncomp;c++){
			int nc,nt,np; counts(c,&nc,&nt,&np);
			/* coupling 1: metabolism -> boundary. K1 decouples it. */
			/* K1 FIXED. The old ablation used GROW*CAP*0.5 = 4.4, identical by construction to the
			   coupled GROW*nc at nc~8 — not a knockout at all. Now growth tracks TOTAL SIZE, so a
			   parasite-filled compartment grows exactly as well as a catalyst-filled one. That is the
			   real decoupling, and it gives the knockout the BENEFIT OF THE DOUBT: sz >= nc always, so
			   K1 receives MORE material than FULL. If it still loses, the coupling is load-bearing. */
			mat[c] += (ko==1)? GROW*sz[c] : GROW*nc;
			/* coupling 3: catalyst -> template fidelity. K3 decouples it. */
			double f = (ko==3)? FLO : ((nc>0)? FHI : FLO);
			/* replication within the compartment; parasites are faster */
			double tot=0,cum[CAP];
			for(int k=0;k<sz[c];k++){ tot += (cell[c][k].role==PAR)? 1.0+BETA : 1.0; cum[k]=tot; }
			/* BUG FIXED: the first version set nsz[c]=newn, REPLACING the compartment with only what
			   production could afford — so nothing persisted and every arm starved to extinction
			   (FULL ended at 0 units, 0 parasites, which is death, not collapse-to-parasites).
			   Existing units now PERSIST; production ADDS to them; a per-unit death rate removes. */
			int newn=0;
			for(int k=0;k<sz[c];k++) if(urand()>=DEATH) nxt[c][newn++]=cell[c][k];
			while(mat[c]>=1.0 && newn<CAP && sz[c]>0){
				double r=urand()*tot; int lo=0,hi=sz[c]-1;
				while(lo<hi){ int mid=(lo+hi)/2; if(cum[mid]<r) lo=mid+1; else hi=mid; }
				Unit p=cell[c][lo], ch=p;
				if(p.role==TEM){
					/* coupling 2: template -> function. K2 turns reading off. */
					if(ko!=2 && nc>0 && urand()<0.5){ ch.role=CAT; ch.type=p.type; }
					else if(urand()>f) ch.type=(unsigned char)rint_(NTYPE);   /* copy error */
				}
				if(urand()<MUT){ ch.role=(unsigned char)rint_(3); ch.type=(unsigned char)rint_(NTYPE); }
				nxt[c][newn++]=ch; mat[c]-=1.0;
			}
			nsz[c]=newn; nmat[c]=mat[c];
		}
		for(int c=0;c<ncomp;c++){ sz[c]=nsz[c]; mat[c]=nmat[c]; for(int k=0;k<sz[c];k++) cell[c][k]=nxt[c][k]; }
		/* between-compartment: reproduce in proportion to function; empty ones are replaced */
		{
			double tot=0,cum[NCOMP];
			for(int c=0;c<ncomp;c++){ tot += 0.02 + func_of(c); 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; }
				int m = sz[lo]<CAP? sz[lo] : CAP;
				nsz[c]=m; nmat[c]=mat[lo]*0.5;
				for(int k=0;k<m;k++) nxt[c][k]=cell[lo][rint_(sz[lo]>0?sz[lo]:1)];
			}
			for(int c=0;c<ncomp;c++){ sz[c]=nsz[c]; mat[c]=nmat[c]; for(int k=0;k<sz[c];k++) cell[c][k]=nxt[c][k]; }
		}
	}
	/* METRICS SEPARATED. "intact" previously required catalysts AND templates, which conflated
	   template retention with system integrity. Now: integrity = the compartment still does work
	   (function >= 2 of NTYPE); template retention is reported on its own. */
	int ok=0,tp=0,tt=0,tu=0; double fs=0;
	for(int c=0;c<ncomp;c++){
		int nc,nt,np; counts(c,&nc,&nt,&np);
		if(func_of(c)>=2) ok++;
		fs += func_of(c); tp+=np; tt+=nt; tu+=sz[c];
	}
	*alive=(double)ok/ncomp; *funcavg=fs/ncomp;
	*parfrac= tu? (double)tp/tu : 0;
	TFRAC = tu? (double)tt/tu : 0;
}

int main(void)
{
	unsigned long long seeds[5]={0x9e3779b97f4a7c15ULL,0xd1b54a32d192ed03ULL,0x2545f4914f6cdd1dULL,
	                             0x123456789abcdefULL,0xfeedface12345678ULL};
	int NS=5;
	printf("=== FLUCTUATING ENVIRONMENT: can memory pay when what is needed CHANGES? ===\n");
	printf("  Three void attempts established the obstacle: a template costs a slot NOW while selection\n");
	printf("  scores current function EVERY generation, so a deferred, conditional benefit is lost\n");
	printf("  before it can be won. Making rediscovery expensive raised the later payoff without\n");
	printf("  touching the immediate cost, and the obvious remedy (frequent losses => small\n");
	printf("  compartments) collides with fidfloor.c.\n");
	printf("  THE FIX UNDER TEST: let the target set CHANGE. A type not needed now becomes needed\n");
	printf("  later, so holding one in a template pays REGULARLY rather than conditionally — and it\n");
	printf("  needs no small compartments. %d of %d types needed at a time.\n\n",NTARG,NTYPE);

	printf("   arm                          | templates | working | mean function (max %d) | parasites\n",NTARG);
	printf("   -----------------------------+-----------+---------+------------------------+----------\n");
	struct { const char*nm; int ko,sw; } arms[4]={
		{"STATIC, full couplings      ",0,0},
		{"FLUCTUATING every 40        ",0,40},
		{"FLUCTUATING every 15        ",0,15},
		{"FLUCTUATING 15, reading OFF ",2,15}};
	double T[4],A[4],F[4],P[4];
	for(int k=0;k<4;k++){
		double a=0,f=0,p=0,tf=0;
		for(int s=0;s<NS;s++){
			double x,y,z; RNG=seeds[s]; SWITCH_EVERY=arms[k].sw;
			run(arms[k].ko,&x,&y,&z); a+=x; f+=y; p+=z; tf+=TFRAC;
		}
		A[k]=a/NS;F[k]=f/NS;P[k]=p/NS;T[k]=tf/NS;
		printf("   %s |   %.3f   |  %.3f  |         %.2f           |   %.3f\n",arms[k].nm,T[k],A[k],F[k],P[k]);
	}
	printf("\n");
	int mem_pays  = (T[1]>T[0]+0.03) || (T[2]>T[0]+0.03);
	int needs_read= (T[2]>T[3]+0.02);
	int differ    = mem_pays || fabs(F[2]-F[0])>0.10;
	printf("   M1 templates persist better when the environment fluctuates : %s (%.3f/%.3f vs static %.3f)\n",
		mem_pays?"YES":"no",T[1],T[2],T[0]);
	printf("   M2 and that depends on templates being READABLE             : %s (%.3f vs %.3f)\n",
		needs_read?"YES":"no",T[2],T[3]);
	printf("   T0 arms differ at all                                       : %s\n\n", differ?"YES":"NO — result void");

	if(!differ)
		printf("   => VOID AGAIN. Fluctuation changes nothing measurable, so the fourth diagnosis is also\n"
		       "      wrong or the model still cannot express the benefit. Do not patch a fifth time\n"
		       "      without a different design.\n");
	else if(mem_pays && needs_read)
		printf("   => MEMORY PAYS WHEN THE WORLD CHANGES. Templates persist under fluctuation and not\n"
		       "      under a fixed target, and the effect disappears when they cannot be read — so it is\n"
		       "      the INFORMATION being used, not the slot being occupied. The third carrier finally\n"
		       "      has a reason to exist, and it needed no small compartments, so it does not collide\n"
		       "      with fidfloor.c. This is a condition on the ENVIRONMENT, not on the chemistry.\n");
	else if(mem_pays)
		printf("   => TEMPLATES PERSIST, BUT NOT BECAUSE THEY ARE READ (%.3f vs %.3f with reading off).\n"
		       "      Something other than the information is retaining them. Claim nothing yet.\n",T[2],T[3]);
	else
		printf("   => HONEST NULL. Fluctuation does not rescue memory: templates stay at %.3f vs %.3f\n"
		       "      static. A deferred benefit made recurring is still not enough, and the obstacle is\n"
		       "      deeper than the timing of the payoff.\n",T[2],T[0]);
	return 0;
}