/* spatial.c — can the ENVIRONMENT emergently supply templating? SPATIAL vs WELL-MIXED heredity.
*
* Every earlier model (gard..template) is WELL-MIXED: mature() fully stirs each pocket, so the only
* heredity is whatever survives a random sample of the pool -> copy-number-limited (digigard/template).
* The user's idea: make the environment part of the program. Give the pocket SPATIAL structure with
* LOCAL physics, and divide by a GEOMETRIC cut. Then a spatially-organized parent hands each daughter
* a COHERENT contiguous chunk that regrows truer than a random sample -- templating supplied by the
* environment's geometry, with NO copy primitive coded anywhere.
*
* THE RAZOR (or this is just the encoding trap relocated): I code ONLY (a) local autocatalysis -- an
* occupied site promotes its OWN clique in neighboring empty sites (the spatial version of the exact
* within-clique catalysis used all along), and (b) a geometric split (daughter = a contiguous half,
* regrown by the same local physics). I NEVER copy the parent's genome. Whether the module-set (an
* emergent readout) transmits, and whether stable domains (spatial specialization) form, is MEASURED.
*
* Two arms, identical local physics and identical ~50% material retention -- ONLY the cut differs:
* SPATIAL : daughter = the contiguous left half of the parent lattice (domains preserved).
* MIXED : daughter = a RANDOM half of the parent's tokens, scattered to random sites (domains broken).
* Measured: (1) heredity signal (module-set bit-match daughter~parent minus ~random), (2) coexistence
* (avg # modules present per pocket -- does space sustain specialists where stirring -> monoculture?),
* (3) selection on module-0.
*
* build: cc -O2 -Wall -std=c11 -o spatial spatial.c -lm
*/
#include <math.h>
#include <stdio.h>
#include <string.h>
#define L 64 /* lattice sites per pocket (a ring) */
#define M 8 /* modules */
#define POCK 120
#define GEN 30
#define REG (6*L) /* local-dynamics updates when regrowing a daughter */
#define NUC 0.003 /* RARE de-novo nucleation (only into a fully empty neighborhood) */
#define EPS 0.01 /* tiny weight for a NON-adjacent module (near-forbids invasion) */
#define CAT 1.0 /* per-same-clique-neighbor catalysis weight (growth from existing domains) */
#define PD 0.02 /* per-update decay prob of an occupied site */
#define PRES (L/(2*M)) /* module 'present' if its token count exceeds this (=4) */
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 double w[M]; /* intrinsic module growth weights (asymmetry, like beta's randn) */
static void build_chem(void){ for(int m=0;m<M;m++) w[m]=exp(0.3*randn()); }
/* LOCAL dynamics: pick a site; occupied -> rare decay; empty -> GROW from occupied neighbors
(local autocatalysis of adjacent cliques); de-novo nucleation only into a fully empty neighborhood,
and rare. Absent modules cannot invade an occupied neighborhood -> module-presence is heritable. */
static void step(int*g){
int i=(int)(xs()%L);
if(g[i]>=0){ if(urand()<PD) g[i]=-1; return; }
int lft=g[(i-1+L)%L], rgt=g[(i+1)%L];
if(lft<0 && rgt<0){ if(urand()<NUC) g[i]=(int)(xs()%M); return; } /* empty neighborhood: rare de novo */
double wt[M], tot=0;
for(int m=0;m<M;m++){ int nb=(lft==m)+(rgt==m); wt[m]=(nb? CAT*nb : EPS)*w[m]; tot+=wt[m]; }
double u=urand()*tot, a=0; int c=M-1;
for(int m=0;m<M;m++){ a+=wt[m]; if(u<=a){ c=m; break; } }
g[i]=c;
}
static void regrow(int*g,int steps){ for(int t=0;t<steps;t++) step(g); }
static void genome(const int*g,int*gm){ int cnt[M]; memset(cnt,0,sizeof cnt); for(int i=0;i<L;i++) if(g[i]>=0) cnt[g[i]]++; for(int m=0;m<M;m++) gm[m]=(cnt[m]>PRES); }
static double bitmatch(const int*a,const int*b){ int e=0; for(int m=0;m<M;m++) if(a[m]==b[m]) e++; return (double)e/M; }
static int nmods(const int*gm){ int s=0; for(int m=0;m<M;m++) s+=gm[m]; return s; }
static int pk[POCK][L], nxt[POCK][L];
static double fit[POCK], cum[POCK];
/* one run. mode 0=spatial cut, 1=mixed sample. returns heredity signal; writes coexistence + selection. */
static double run(int mode, double *coex, double *f_start, double *f_end){
for(int p=0;p<POCK;p++){ for(int i=0;i<L;i++) pk[p][i]=-1;
int present[M],np=0; for(int m=0;m<M;m++){present[m]=(urand()<0.5);if(present[m])np++;} if(!np){present[0]=1;}
for(int i=0;i<L;i++) if(urand()<0.5){ int m; do{m=(int)(xs()%M);}while(!present[m]); pk[p][i]=m; }
regrow(pk[p],REG); }
double dig=0,cx=0; int hc=0,cc=0; double t0=0,t1=0;
for(int g=0;g<GEN;g++){
double tot=0; for(int p=0;p<POCK;p++){ int gm[M]; genome(pk[p],gm); fit[p]=0.3+(gm[0]?1.0:0.0); tot+=fit[p]; cum[p]=tot; }
if(g==1){ double f=0; for(int p=0;p<POCK;p++){int gm[M];genome(pk[p],gm);f+=gm[0];} t0=f/POCK; }
for(int p=0;p<POCK;p++){
double r=urand()*tot; int lo=0,hi=POCK-1; while(lo<hi){int mid=(lo+hi)/2; if(cum[mid]<r)lo=mid+1; else hi=mid;} int par=lo;
int D[L]; for(int i=0;i<L;i++) D[i]=-1;
if(mode==0){
/* SPATIAL: inherit the contiguous left half; right half empty. Domains preserved. */
for(int i=0;i<L/2;i++) D[i]=pk[par][i];
} else {
/* MIXED: keep ~50% of the SAME tokens but scatter to random empty sites. Domains broken. */
for(int i=0;i<L;i++) if(pk[par][i]>=0 && urand()<0.5){ int q; do{ q=(int)(xs()%L); }while(D[q]>=0); D[q]=pk[par][i]; }
}
regrow(D,REG);
memcpy(nxt[p],D,sizeof D);
if(g>GEN/2){ int rp=(int)(xs()%POCK); int gd[M],gpar[M],grn[M]; genome(D,gd); genome(pk[par],gpar); genome(pk[rp],grn);
dig += bitmatch(gd,gpar)-bitmatch(gd,grn); cx += nmods(gd); hc++; cc++; }
}
memcpy(pk,nxt,sizeof pk);
}
{ double f=0; for(int p=0;p<POCK;p++){int gm[M];genome(pk[p],gm);f+=gm[0];} t1=f/POCK; }
*coex = cc?cx/cc:0; *f_start=t0; *f_end=t1;
return hc?dig/hc:0;
}
int main(void){
printf("=== SPATIAL vs WELL-MIXED: can the environment's geometry supply templating? ===\n");
printf(" Lattice L=%d, M=%d modules, local autocatalysis, geometric split. NO copy primitive.\n",L,M);
printf(" Only difference between arms: SPATIAL keeps a contiguous half; MIXED keeps a random half.\n\n");
unsigned long long seeds[]={0x9e3779b97f4a7c15ULL,0xd1b54a32d192ed03ULL,0x2545f4914f6cdd1dULL,0x123456789abcdefULL,0xfeedface12345678ULL};
int NS=sizeof(seeds)/sizeof(seeds[0]);
double hS=0,cS=0,dS=0, hM=0,cM=0,dM=0;
for(int s=0;s<NS;s++){
double c,a,b;
RNG=seeds[s]; build_chem(); double h=run(0,&c,&a,&b); hS+=h; cS+=c; dS+=b-a;
RNG=seeds[s]; build_chem(); h=run(1,&c,&a,&b); hM+=h; cM+=c; dM+=b-a;
}
hS/=NS;cS/=NS;dS/=NS; hM/=NS;cM/=NS;dM/=NS;
printf(" (averaged over %d chemistry seeds)\n",NS);
printf(" arm | heredity | coexistence(#mods) | selection Delta\n");
printf(" ----------+----------+--------------------+----------------\n");
printf(" SPATIAL | %+.3f | %.2f | %+.3f\n", hS,cS, dS);
printf(" MIXED | %+.3f | %.2f | %+.3f\n", hM,cM, dM);
printf("\n");
double s1S=dS,s0S=0,s1M=dM,s0M=0; (void)s1S;(void)s0S;(void)s1M;(void)s0M;
int her_win = (hS > hM + 0.02);
int coex_win = (cS > cM + 0.2);
int sel_win = (dS > dM + 0.05);
printf(" heredity : spatial %s mixed (%+.3f vs %+.3f) [whole module-set fidelity]\n", her_win?">":"~=/<", hS,hM);
printf(" coexistence: spatial %s mixed (%.2f vs %.2f) [modular-cage diversity]\n", coex_win?">":"~=/<", cS,cM);
printf(" selection : spatial %s mixed (%+.3f vs %+.3f) [holding a favored trait]\n", sel_win?">":"~=/<", dS,dM);
printf("\n");
printf(" => TWO-SIDED result, honest:\n");
if(!her_win && !coex_win)
printf(" * TEMPLATING: NULL. Geometry does NOT supply faithful copying. A contiguous cut is only a\n CORRELATED sampler of which modules are present -- no better (a hair worse) than a random draw,\n because regrowth re-equilibrates to the inherited module-SET regardless of its arrangement. The\n fidelity bottleneck is untouched; only templated COPYING (template.c) crosses it. Environment as\n container (gardenv) and as geometry (here) both give container+selection, NOT the copy.\n");
if(sel_win)
printf(" * SELECTION: REAL. Spatial structure robustly amplifies selection on a trait (spatial\n reciprocity): a favored module's domain colonizes and dominates a pocket, so between-pocket\n selection fixes it far better than in the well-mixed arm. The environment-as-program helps the\n SELECTION half of Darwinian evolution -- but heredity/fidelity, the actual bottleneck, was NOT\n the half it helped. The 'modular cage' sharpens selection; it does not manufacture a template.\n");
printf("\n HONEST BOUND: local physics + a geometric cut were supplied; the null is not tuned away.\n");
return 0;
}