Quick summary: Cultured chicken is produced by growing avian muscle cells in bioreactors. The main cost drivers are media (amino acids, nutrients), growth factors (signaling proteins), bioreactors (capital equipment), and operating costs.
CELL BANK → SEED TRAIN → PRODUCTION → HARVEST → PRODUCT
[O] [OOO] [OOOOOOO] [===] [≡≡≡]
Learn: Understanding Uncertainty & Distributions
This model uses Monte Carlo simulation: we sample thousands of possible futures and see what distribution of costs emerges.
Distribution Types Used:
Parameter Type
Distribution
Why
Costs, intensities
Lognormal
Always positive, right-skewed (some very high values possible)
Probabilities, fractions
Beta
Bounded between 0 and 1, flexible shape
Bounded ranges
Uniform
Equal probability within bounds
Reading the Results:
p5 (5th percentile): “Optimistic” - only 5% of simulations are cheaper
p95 (95th percentile): “Pessimistic” - 95% of simulations cheaper
The “Maturity” Correlation:
A key feature is the latent maturity factor that links:
Technology adoption rates
Reactor costs
Financing costs (WACC)
In “good worlds” for cultured chicken (high maturity), multiple things improve together. This prevents unrealistic scenarios where technology succeeds but financing remains difficult.
Interactive Model
Code
functionmulberry32(seed) {returnfunction() {let t = seed +=0x6D2B79F5; t =Math.imul(t ^ t >>>15, t |1); t ^= t +Math.imul(t ^ t >>>7, t |61);return ((t ^ t >>>14) >>>0) /4294967296; }}// ============================================================// STATISTICAL SAMPLING FUNCTIONS// ============================================================// Box-Muller transform for standard normalfunctionboxMuller(rng) {const u1 =rng();const u2 =rng();returnMath.sqrt(-2*Math.log(u1)) *Math.cos(2*Math.PI* u2);}// Sample from lognormal given p5 and p95functionsampleLognormalP5P95(rng, p5, p95, n) {const Z_95 =1.6448536269514722;const mu = (Math.log(p5) +Math.log(p95)) /2;const sigma = (Math.log(p95) -Math.log(p5)) / (2* Z_95);const samples =newArray(n);for (let i =0; i < n; i++) { samples[i] =Math.exp(mu + sigma *boxMuller(rng)); }return samples;}// Sample uniformfunctionsampleUniform(rng, lo, hi, n) {const samples =newArray(n);for (let i =0; i < n; i++) { samples[i] = lo + (hi - lo) *rng(); }return samples;}// Beta distribution parameters from mean/stdevfunctionbetaFromMeanStdev(mean, stdev) {let variance = stdev * stdev;const maxVar = mean * (1- mean);if (variance <=0|| variance >= maxVar) { stdev =Math.sqrt(maxVar *0.99); variance = stdev * stdev; }const t = maxVar / variance -1;const a =Math.max(mean * t,0.01);const b =Math.max((1- mean) * t,0.01);return [a, b];}// Sample from beta using Joehnk's algorithm (simple, works for all a,b > 0)functionsampleBeta(rng, a, b) {// Use gamma ratio methodconst gammaA =sampleGamma(rng, a);const gammaB =sampleGamma(rng, b);return gammaA / (gammaA + gammaB);}// Sample from gamma distribution (Marsaglia and Tsang's method)functionsampleGamma(rng, shape) {if (shape <1) {returnsampleGamma(rng, shape +1) *Math.pow(rng(),1/ shape); }const d = shape -1/3;const c =1/Math.sqrt(9* d);while (true) {let x, v;do { x =boxMuller(rng); v =1+ c * x; } while (v <=0); v = v * v * v;const u =rng();if (u <1-0.0331* (x * x) * (x * x)) return d * v;if (Math.log(u) <0.5* x * x + d * (1- v +Math.log(v))) return d * v; }}// Sample from beta given mean/stdevfunctionsampleBetaMeanStdev(rng, mean, stdev, n) {const [a, b] =betaFromMeanStdev(mean, stdev);const samples =newArray(n);for (let i =0; i < n; i++) { samples[i] =sampleBeta(rng, a, b); }return samples;}// Capital Recovery Factorfunctioncrf(wacc, nYears) {return wacc *Math.pow(1+ wacc, nYears) / (Math.pow(1+ wacc, nYears) -1);}// Clip values between min and maxfunctionclip(arr, min, max) {return arr.map(v =>Math.min(Math.max(v, min), max));}// Element-wise operationsfunctionadd(a, b) { return a.map((v, i) => v + b[i]); }functionmul(a, b) { return a.map((v, i) => v * b[i]); }functiondiv(a, b) { return a.map((v, i) => v / b[i]); }functionscale(arr, s) { return arr.map(v => v * s); }// ============================================================// MAIN SIMULATION FUNCTION// ============================================================functionsimulate(n, seed, params) {const rng =mulberry32(seed);// Latent maturity factorconst maturity =sampleBetaMeanStdev(rng, params.maturity_mean,0.20, n);// Scale and uptimeconst plant_kta =sampleLognormalP5P95(rng, params.plant_kta_p5, params.plant_kta_p95, n);const uptime =sampleBetaMeanStdev(rng, params.uptime_mean,0.05, n);const output_kgpy =mul(scale(plant_kta,1e6), uptime);// Adoption probabilities (maturity-adjusted)let p_hydro =sampleBetaMeanStdev(rng, params.p_hydro_mean,0.10, n); p_hydro =clip(add(p_hydro,scale(maturity.map(m => m -0.5),0.25)),0,1);let p_foodgrade =sampleBetaMeanStdev(rng, params.p_foodgrade_mean,0.10, n); p_foodgrade =clip(add(p_foodgrade,scale(maturity.map(m => m -0.5),0.20)),0,1);let p_recf =sampleBetaMeanStdev(rng, params.p_recfactors_mean,0.15, n); p_recf =clip(add(p_recf,scale(maturity.map(m => m -0.5),0.25)),0,1);// Bernoulli draws for adoptionconst is_hydro = p_hydro.map(p =>rng() < p);const is_foodgrade = p_foodgrade.map(p =>rng() < p);const is_recf_cheap = p_recf.map(p =>rng() < p);// Process intensitiesconst density_gL =sampleLognormalP5P95(rng, params.density_gL_p5, params.density_gL_p95, n);const cycle_days =sampleLognormalP5P95(rng,0.5,5.0, n);const media_turnover =sampleLognormalP5P95(rng, params.media_turnover_p5, params.media_turnover_p95, n);const L_per_kg =mul(div(density_gL.map(_ =>1000), density_gL), media_turnover);// Media costconst media_cost_hydro =sampleLognormalP5P95(rng,0.2,1.2, n);const media_cost_pharma =sampleLognormalP5P95(rng,1.0,4.0, n);const media_cost_L = is_hydro.map((h, i) => h ? media_cost_hydro[i] : media_cost_pharma[i]);const cost_media =mul(L_per_kg, media_cost_L);// Commodity micronutrientsconst g_comm_food =sampleLognormalP5P95(rng,0.1,2.0, n);const g_comm_pharma =sampleLognormalP5P95(rng,1.0,10.0, n);const g_comm = is_foodgrade.map((f, i) => f ? g_comm_food[i] : g_comm_pharma[i]);const price_comm_food =sampleLognormalP5P95(rng,0.02,2.0, n);const price_comm_pharma =sampleLognormalP5P95(rng,0.5,20.0, n);const price_comm = is_foodgrade.map((f, i) => f ? price_comm_food[i] : price_comm_pharma[i]);const cost_comm_micros =mul(g_comm, price_comm);// Recombinant growth factors (FGF-2, IGF-1, TGF-β, etc.)// CRITICAL: Literature shows GFs can be 55-95% of media cost at current prices// Current prices: FGF-2 ~$50,000/g, TGF-β up to $1M/g// Target prices: $1-10/g with scaled recombinant production// Quantity (g/kg meat) - based on typical concentrations in media// ~10-100 ng/mL in media × 20-60 L/kg = 0.0002-0.006 g/kgconst g_recf_cheap =sampleLognormalP5P95(rng,1e-4,5e-3, n);// Reduced usage with optimizationconst g_recf_exp =sampleLognormalP5P95(rng,5e-4,2e-2, n);// Standard usageconst g_recf = is_recf_cheap.map((c, i) => c ? g_recf_cheap[i] : g_recf_exp[i]);// Price ($/g) - Scaled by gf_progress parameter (0-100%)// At 0% progress: current prices ($5k-500k expensive, $100-10k cheap)// At 100% progress: target prices ($50-5k expensive, $1-100 cheap)const progress = params.gf_progress/100;// 0 to 1// Interpolate price ranges based on progress// Cheap scenario: $100-10,000 at 0% → $1-100 at 100%const cheap_p5 =100*Math.pow(0.01, progress);// 100 → 1const cheap_p95 =10000*Math.pow(0.01, progress);// 10000 → 100const price_recf_cheap =sampleLognormalP5P95(rng, cheap_p5, cheap_p95, n);// Expensive scenario: $5,000-500,000 at 0% → $50-5,000 at 100%const exp_p5 =5000*Math.pow(0.01, progress);// 5000 → 50const exp_p95 =500000*Math.pow(0.01, progress);// 500000 → 5000const price_recf_exp =sampleLognormalP5P95(rng, exp_p5, exp_p95, n);const price_recf = is_recf_cheap.map((c, i) => c ? price_recf_cheap[i] : price_recf_exp[i]);const cost_recf =mul(g_recf, price_recf);// Other variable costsconst other_var =sampleLognormalP5P95(rng,0.5,5.0, n);// VOC totalconst voc =add(add(add(cost_media, cost_comm_micros), cost_recf), other_var);// CAPEX calculationlet capex_perkg =newArray(n).fill(0);if (params.include_capex) {const prod_kg_L_day =div(scale(density_gL,1/1000), cycle_days);const total_working_volume_L =div(output_kgpy,scale(prod_kg_L_day,365));const reactor_cost_L_pharma =sampleLognormalP5P95(rng,50,500, n);const custom_ratio =sampleUniform(rng,0.35,0.85, n);let custom_share =sampleBetaMeanStdev(rng,0.55,0.15, n); custom_share =clip(add(custom_share,scale(maturity.map(m => m -0.5),0.30)),0,1);const reactor_cost_L_avg = reactor_cost_L_pharma.map((p, i) => p * (custom_share[i] * custom_ratio[i] + (1- custom_share[i])) );const capex_s =sampleUniform(rng,0.6,0.9, n);const plant_factor =sampleLognormalP5P95(rng,1.5,3.5, n);const V_ref =1e6;const capex_total = reactor_cost_L_avg.map((r, i) => r * total_working_volume_L[i] *Math.pow(total_working_volume_L[i] / V_ref, capex_s[i] -1) * plant_factor[i] );let wacc =sampleLognormalP5P95(rng, params.wacc_p5, params.wacc_p95, n); wacc =clip(wacc.map((w, i) => w -0.03* (maturity[i] -0.5)),0.03,1);const asset_life =sampleUniform(rng, params.asset_life_lo, params.asset_life_hi, n);const crf_val = wacc.map((w, i) =>crf(w, asset_life[i])); capex_perkg = capex_total.map((c, i) => (c * crf_val[i]) / output_kgpy[i]); }// Fixed OPEX calculationlet fixed_perkg =newArray(n).fill(0);if (params.include_fixed_opex) {const ref_output =20e6*0.9;const fixed_perkg_ref =sampleUniform(rng,1.0,6.0, n);const fixed_annual_ref =scale(fixed_perkg_ref, ref_output);const fixed_scale =sampleUniform(rng,0.6,1.0, n);const fixed_annual = fixed_annual_ref.map((f, i) => f *Math.pow(output_kgpy[i] / ref_output, fixed_scale[i]) ); fixed_perkg =div(fixed_annual, output_kgpy); }// Downstream processing (scaffolding, texturization) for structured productslet downstream_perkg =newArray(n).fill(0);if (params.include_downstream) {// Downstream adds $2-15/kg for structured products downstream_perkg =sampleLognormalP5P95(rng,2.0,15.0, n); }// Total unit costconst unit_cost =add(add(add(voc, capex_perkg), fixed_perkg), downstream_perkg);return { unit_cost, cost_media, cost_comm_micros, cost_recf,cost_other_var: other_var,cost_capex: capex_perkg,cost_fixed: fixed_perkg,cost_downstream: downstream_perkg,pct_hydro: is_hydro.filter(x => x).length/ n,pct_foodgrade: is_foodgrade.filter(x => x).length/ n,pct_recf_cheap: is_recf_cheap.filter(x => x).length/ n,// Input parameters for sensitivity analysismaturity_samples: maturity,density_samples: density_gL,media_turnover_samples: media_turnover,L_per_kg_samples: L_per_kg,media_cost_L_samples: media_cost_L,is_hydro_samples: is_hydro.map(x => x ?1:0),is_foodgrade_samples: is_foodgrade.map(x => x ?1:0),is_recf_cheap_samples: is_recf_cheap.map(x => x ?1:0),g_recf_samples: g_recf,price_recf_samples: price_recf,plant_kta_samples: plant_kta,uptime_samples: uptime };}// Statistical helpersfunctionquantile(arr, q) {const sorted = [...arr].sort((a, b) => a - b);const idx = (sorted.length-1) * q;const lo =Math.floor(idx);const hi =Math.ceil(idx);const frac = idx - lo;return sorted[lo] * (1- frac) + sorted[hi] * frac;}functionmean(arr) {return arr.reduce((a, b) => a + b,0) / arr.length;}// Spearman rank correlation coefficientfunctionspearmanCorr(x, y) {const n = x.length;// Rank function (handles ties with average rank)functionrank(arr) {const sorted = arr.map((v, i) => ({v, i})).sort((a, b) => a.v- b.v);const ranks =newArray(n);let i =0;while (i < n) {let j = i;while (j < n -1&& sorted[j +1].v=== sorted[j].v) j++;const avgRank = (i + j) /2+1;for (let k = i; k <= j; k++) ranks[sorted[k].i] = avgRank; i = j +1; }return ranks; }const rx =rank(x);const ry =rank(y);// Pearson correlation of ranksconst meanRx =mean(rx);const meanRy =mean(ry);let num =0, denX =0, denY =0;for (let i =0; i < n; i++) {const dx = rx[i] - meanRx;const dy = ry[i] - meanRy; num += dx * dy; denX += dx * dx; denY += dy * dy; }return num /Math.sqrt(denX * denY);}
Annual production capacity in thousand metric tons (kTA). Current pilots: <1 kTA. Commercial target: 10-50 kTA.
What plant size affects:
Cost Component
Effect of Larger Plant
CAPEX per kg
↓ Scales sub-linearly (power law ~0.6-0.9)
Fixed OPEX per kg
↓ Overhead spread over more output
Variable costs per kg
— No direct effect (same $/L, $/g)
Larger plants have lower per-kg costs due to economies of scale in capital and fixed costs. Variable costs (media, growth factors) don’t change with scale — they depend on technology adoption.
What is this?
Fraction of capacity actually used. Accounts for downtime, cleaning, maintenance. 90% is optimistic for new industry.
Code
viewof maturity = Inputs.range([0.1,0.9], {value:0.5,step:0.05,label:"Industry Maturity by 2036"})
What is this?
A latent factor (0=nascent, 1=mature) that affects all technology adoption, reactor costs, and financing. High maturity = correlated improvements.
What does the year affect?
Earlier years → lower maturity, less technology adoption. Later years → more time for cost reductions and scale-up. The model adjusts maturity expectations based on years from 2024.
Technology Adoption by
Probability that each cost-reducing technology is widely adopted by the target year.
Code
viewof p_hydro = Inputs.range([0.3,0.95], {value:0.75,step:0.05,label:"P(Hydrolysates for basal media)"})
What is this? (click to expand)
Hydrolysates are enzymatically digested plant/yeast proteins that provide amino acids for basal media — the bulk nutrient broth that cells grow in.
Important clarification: Hydrolysates replace amino acids, NOT growth factors. These are completely separate cost categories:
Why 75% baseline?Recent research (2024-2025) shows hydrolysates are already validated across plant, yeast, insect, and fish sources. Companies like IntegriCulture have reduced media components by replacing amino acids with yeast extract. The main uncertainty is regulatory approval for food products, not technical feasibility.
P(Scalable GF technology) — Probability that at least one breakthrough production method reaches commercial scale. This switches between “expensive” and “cheap” price distributions.
GF cost reduction progress — How far along the price reduction curve we are within each regime:
0% = Current prices ($5,000-500,000/g expensive; $100-10,000/g cheap)
50% = Midway
100% = Industry target achieved ($1-100/g cheap; $50-5,000/g expensive)
Growth factors are signaling proteins (FGF-2, IGF-1, TGF-β, etc.) that tell cells to proliferate. Currently the most expensive media component — often 55-95% of media cost at research scale.
Why this is modeled as a binary switch + continuous progress:
The switch represents whether any scalable technology reaches commercial viability
The progress slider represents how far costs have dropped within that regime
Even in the “expensive” scenario (no breakthrough), incremental improvements occur
This structure captures the bimodal nature of the uncertainty: either scalable production is achieved, or it isn’t
Model cost impact:
Scenario
Usage (g/kg)
Price ($/g)
Cost/kg chicken
No breakthrough
0.0005 - 0.02
$500 - $50,000
$0.25 - $1,000
Breakthrough achieved
0.0001 - 0.005
$1 - $100
$0.0001 - $0.50
This is a pivotal uncertainty. The GFI 2024 State of the Industry report identifies growth factor cost reduction as one of the top technical challenges. If solved, GFs become negligible; if not, they dominate the cost structure.
viewof wacc_lo = Inputs.range([5,20], {value:8,step:1,label:"WACC Low (%)"})viewof wacc_hi = Inputs.range([10,35], {value:20,step:1,label:"WACC High (%)"})viewof asset_life_lo = Inputs.range([5,15], {value:8,step:1,label:"Asset Life Low (years)"})viewof asset_life_hi = Inputs.range([10,30], {value:20,step:1,label:"Asset Life High (years)"})
Advanced: Process Intensities
Show advanced parameters
Code
viewof density_lo = Inputs.range([10,100], {value:30,step:10,label:"Cell Density Low (g/L)"})viewof density_hi = Inputs.range([50,300], {value:200,step:10,label:"Cell Density High (g/L)"})viewof media_turnover_lo = Inputs.range([1,5], {value:1,step:1,label:"Media Turnover Low"})viewof media_turnover_hi = Inputs.range([2,20], {value:10,step:1,label:"Media Turnover High"})
Code
results = {// Adjust maturity based on target year (2024 baseline)// Later years allow more time for industry developmentconst yearFactor =Math.max(0,Math.min(1, (target_year -2024) /20));const adjustedMaturity = maturity * (0.5+0.5* yearFactor);const params = {plant_kta_p5: plant_capacity *0.5,plant_kta_p95: plant_capacity *2.0,uptime_mean: uptime,maturity_mean: adjustedMaturity,p_hydro_mean: p_hydro,p_foodgrade_mean: p_foodgrade,p_recfactors_mean: p_recfactors,wacc_p5: wacc_lo /100,wacc_p95: wacc_hi /100,asset_life_lo: asset_life_lo,asset_life_hi: asset_life_hi,density_gL_p5: density_lo,density_gL_p95: density_hi,media_turnover_p5: media_turnover_lo,media_turnover_p95: media_turnover_hi,include_capex: include_capex,include_fixed_opex: include_fixed_opex,include_downstream: include_downstream,gf_progress: gf_progress };returnsimulate(30000,42, params);}// Calculate statisticsstats = {const uc = results.unit_cost;return {p5:quantile(uc,0.05),p50:quantile(uc,0.50),p95:quantile(uc,0.95),prob_10: uc.filter(x => x <10).length/ uc.length*100,prob_25: uc.filter(x => x <25).length/ uc.length*100,prob_50: uc.filter(x => x <50).length/ uc.length*100,prob_100: uc.filter(x => x <100).length/ uc.length*100 };}
Results Summary
Code
html`<div style="background: #f8f9fa; padding: 1rem 1.25rem; border-left: 4px solid #3498db; margin-bottom: 1.5rem; font-size: 0.95em; line-height: 1.6;"><strong>What these numbers represent:</strong> Simulated <strong>production cost per kilogram of cultured chicken</strong> (wet weight, unprocessed) in <strong>${targetYear}</strong>, based on ${n.toLocaleString()} Monte Carlo simulations. This is the cost to produce meat in a bioreactor — not retail price, which would include processing, distribution, and margins.<br><br><strong>Why it matters:</strong> If production costs reach <strong>~$10/kg</strong> (comparable to conventional chicken), cultured meat could compete at scale. If costs remain <strong>>$50/kg</strong>, the technology may remain niche. These thresholds inform whether animal welfare interventions should prioritize supporting this industry.</div>`
The width of the distribution shows uncertainty. Narrow = confident. Wide = uncertain.
Probability Thresholds
These percentages answer: “What’s the chance that production costs fall below key price points?” — based on the parameter distributions you’ve set above. These are the decision-relevant outputs for evaluating whether cultured meat can compete with conventional chicken.
Where does the cost come from? This chart shows the average contribution of each cost component across all simulations. The largest bars are the cost drivers to focus on — these are where technological progress or parameter uncertainty has the most impact.
Code
{const allComponents = [ {name:"Media",value:mean(results.cost_media),color:"#27ae60"}, {name:"Micronutrients",value:mean(results.cost_comm_micros),color:"#3498db"}, {name:"Growth Factors",value:mean(results.cost_recf),color:"#9b59b6"}, {name:"Other VOC",value:mean(results.cost_other_var),color:"#7f8c8d"}, {name:"CAPEX (annualized)",value:mean(results.cost_capex),color:"#e74c3c"}, {name:"Fixed OPEX",value:mean(results.cost_fixed),color:"#f39c12"}, {name:"Downstream",value:mean(results.cost_downstream),color:"#1abc9c"} ];// Filter out zero-value components (e.g., downstream when not included)const components = allComponents.filter(c => c.value>0.001).sort((a, b) => b.value- a.value);const total = components.reduce((s, c) => s + c.value,0);return Plot.plot({width:800,height:350,marginLeft:150,marginRight:100,x: {label:"Average Cost ($/kg)",grid:true },y: {label:null },marks: [ Plot.barX(components, {y:"name",x:"value",fill:"color",sort: {y:"-x"} }), Plot.text(components, {y:"name",x: d => d.value+0.5,text: d =>`$${d.value.toFixed(2)} (${(d.value/total*100).toFixed(0)}%)`,textAnchor:"start",fontSize:12 }) ],title:`Cost Breakdown by Component (Total: $${total.toFixed(2)}/kg)` })}
Understanding the cost components
Variable Operating Costs (VOC):
Media: Nutrient broth for cell growth (amino acids, glucose, vitamins)
Using hydrolysates → lower cost (cheaper amino acids)
Bar length: How much that parameter affects the output
|correlation| > 0.5 = strong effect
|correlation| 0.3-0.5 = moderate effect
|correlation| < 0.3 = weak effect
This is Spearman rank correlation, which captures monotonic relationships (not just linear). A correlation of -0.7 means “when this parameter is high, costs tend to be low” across the 30,000 simulations.
Key insight: The parameters with the longest bars are the ones to focus on when: - Forecasting (these drive your uncertainty) - Prioritizing R&D (these are the levers to pull) - Gathering expert input (these are worth eliciting carefully)
Industry Maturity Deep Dive
The maturity factor is a latent variable (0 = nascent, 1 = mature) that correlates multiple model inputs. Higher maturity means:
The model runs 30,000 Monte Carlo simulations, sampling from parameter distributions and technology adoption switches to produce cost distributions and probability thresholds.
Key Insights
The model reveals several important patterns:
Scenario
Typical Median Cost
Key Drivers
High maturity (0.7+)
$15-30/kg
All technologies adopted, low WACC
Baseline (0.5)
$25-50/kg
Mixed adoption, moderate uncertainty
Low maturity (0.3-)
$50-100+/kg
Pharma-grade inputs, high financing costs
Most sensitive parameters:
Technology adoption (especially hydrolysates)
Cell density at harvest
Industry maturity (affects multiple factors)
Limitations
Static snapshot model — Projects costs for a single target year (adjustable 2026-2050). Does not model year-over-year learning curves; instead, the “maturity” parameter serves as a proxy for cumulative industry development.
Downstream is optional — Scaffolding, texturization, and forming costs can be included via toggle (+$2-15/kg for structured products).
No geography — Assumes generic global costs, not region-specific labor, energy, or regulatory factors.
Limited contamination modeling — No batch failure or contamination event distributions.