Engineered living materials (ELMs) — materials in which living cells are a load-bearing, functional component rather than a contaminant — have been proposed as a covering layer for robots. Three platforms have produced physical demonstrations: tissue-engineered mammalian dermis and epidermis cultured directly onto robotic substructures; microbial polysaccharide matrices, principally bacterial cellulose grown by Komagataeibacter species in engineered co-culture; and fungal mycelial networks, both as printed structural skins and as electrophysiological sensing elements. Each has been shown to deliver at least one capability that no synthetic elastomer provides without an external trigger: autonomous wound repair, self-cleaning, growth-based conformal coverage, or chemically specific environmental transduction.
We argue that the field's framing problem is a mismatch of timescales rather than a shortage of biological function. Living tissue operates on a repair-and-adaptation clock measured in hours to days and a chemical-sensing clock measured in seconds to minutes; robot control loops operate at 10²–10³ Hz. Attempts to make a living layer serve as the primary tactile transducer therefore compete directly with mature synthetic electronic skin on the one axis where biology is structurally disadvantaged. We propose instead a division of labour by bandwidth: synthetic layers own the fast channel (contact, force, slip, vibration), while the living layer owns the slow channel (damage detection and autonomous repair, chemically specific sensing, antifouling, appearance, end-of-life biodegradation).
We formalize this as an eight-part specification (S1–S8) with target values drawn from native human skin and from published robot-skin requirements, score each ELM platform against it, and identify where the platforms fail. We present an order-of-magnitude life-support budget showing that the binding constraint on mammalian living skin is water flux, not metabolic energy: the cellular power draw of a square metre of dermis equivalent is on the order of 10 mW, while evaporative water loss from a barrier-deficient skin equivalent is on the order of 1 kg m⁻² day⁻¹. We close with a proposed reporting standard, an evidence-tier table separating demonstrated results from conjecture, and a staged readiness roadmap.
Keywords: engineered living materials; bio-hybrid robotics; electronic skin; bacterial cellulose; fungal mycelium; tissue engineering; self-healing materials; synthetic biology
An essay. A thesis put forward by its author — an argument, specification, or design — and not a verified result.
1. Introduction
The covering layer of a robot has followed a consistent trajectory: from rigid shells, to compliant elastomers, to sensorised elastomers with distributed transduction. Each step traded stiffness for compliance and added channels of information. The step now being proposed — replacing or supplementing the elastomer with living cells — is different in kind. It does not trade one passive property for another. It introduces a component with metabolism, a growth programme, an autonomous repair response, and a failure mode no synthetic material has: death.
The motivation is specific. Synthetic self-healing polymers exist, but almost all of them require an external trigger — heat, pressure, light, a solvent, a catalyst — and none of them restore the original microstructure. Living tissue repairs damage without being told to, using material it manufactures on site from ambient nutrients, and the repaired region is compositionally the same as the original. Fungal mycelium extends this further: it regenerates across a severed gap and can colonise and clean its own surface. Whether these capabilities are worth their operational cost is the question the field has not yet answered quantitatively.
This review takes a specification-led approach. Rather than surveying the literature by material class and reporting what each material can do, we first state what a robot skin must do, then ask what each ELM platform delivers and where it fails. This inverts the usual framing and produces a sharper result: two of the three platforms fail the same requirement (mechanical strength at skin-relevant strains), all three fail a requirement rarely stated explicitly (unattended operation in non-sterile, non-humidified ambient conditions for months), and none is competitive on the requirement most heavily emphasized in the electronic-skin literature (high-bandwidth spatially resolved tactile transduction).
1.1 Scope and definitions
We use engineered living material to mean a material in which viable organisms are integral to the material's function, and in which either the organism or the matrix it produces has been deliberately designed. This excludes decellularised scaffolds, cell-free biopolymers such as purified collagen or chitosan films, and materials in which cells are transient (e.g. cell-seeded implants intended to be replaced by host tissue). It includes:
- Class A — Mammalian tissue constructs. Fibroblast-populated collagen hydrogels (dermis equivalents), optionally with a stratified keratinocyte layer (full skin equivalents), cultured onto or around a robotic substructure.
- Class B — Microbial polysaccharide matrices. Bacterial cellulose (BC) and related biofilm matrices produced by Komagataeibacter rhaeticus, K. xylinus, or engineered E. coli biofilm systems, optionally in stable co-culture with engineered Saccharomyces cerevisiae.
- Class C — Fungal mycelial networks. Hyphal networks of Ganoderma lucidum, Pleurotus eryngii, Fomes fomentarius and similar basidiomycetes, grown into or printed as a structural layer, and used either mechanically or electrophysiologically.
A fourth class — photosynthetic ELMs incorporating cyanobacteria or micro-algae — is discussed only briefly (§4.4), because no robot-skin demonstration has been published, though the platform is the most interesting long-term candidate for the water and energy problems identified in §6.
2. A Functional Specification for Robot Skin
Requirements below are stated as a specification with target values, following the convention that a covering layer for a general-purpose mobile or manipulation robot should be at least as capable as human skin on the axes where human skin is the design reference, and no worse than a good synthetic elastomer on the axes where it is not.
S1 — Mechanical compliance and strength. Native human skin exhibits a J-shaped stress–strain curve: a low-modulus toe region (≈0.1 MPa effective modulus) governed by collagen crimp, transitioning to a stiff regime (≈10–100 MPa) as fibres align. Ultimate tensile strength is on the order of 5–30 MPa with failure strains of 30–80%, varying strongly with anatomical site, orientation, age and hydration. A robotic integument covering actuated joints must survive repeated strain cycles at the joint-relevant amplitude — for a finger joint, on the order of 20–40% local strain — without tearing or delaminating. Target: UTS ≥ 1 MPa; failure strain ≥ 30%; ≥ 10⁵ cycles at 20% strain without loss of continuity.
S2 — Conformal coverage of non-developable geometry. Robot surfaces are not developable: they contain saddle points, re-entrant features, and joint gaps. A skin applied as a sheet must either be cut and seamed (introducing failure lines) or must be formed in place. Target: seamless coverage of a multi-axis articulated member with no seam crossing a flexion axis.
S3 — Adhesion to substructure. The skin must transmit shear to the substructure without peeling, and must accommodate relative sliding where native skin slides over fascia. Target: interfacial shear strength ≥ 10 kPa; no delamination over the S1 cycle count.
S4 — Autonomous repair. Restoration of mechanical continuity and (where applicable) barrier function following a full-thickness cut, without external trigger, intervention, or part replacement. Target: ≥ 50% recovery of UTS within 7 days at operating conditions.
S5 — Transduction. Two channels, with distinct requirements. S5a (fast): normal and shear force, slip, vibration. Spatial resolution ≤ 5 mm; bandwidth ≥ 100 Hz; latency ≤ 10 ms. S5b (slow): chemical species, humidity, temperature, damage state, biological contamination. Latency ≤ 60 s acceptable; chemical specificity is the differentiator.
S6 — Environmental tolerance and life support. Continuous operation in ambient air (20–30 °C, 30–70% RH), non-sterile, without immersion, for a defined service interval. Target: ≥ 30 days unattended; ≥ 180 days with scheduled servicing.
S7 — Interface compatibility. Compatible with embedded electronics, actuators, wiring and power. In practice this means waterproof encapsulation of the electronic domain against a wet biological domain, with feedthroughs. Target: IPX7-equivalent isolation at all feedthroughs; no cytotoxic leachates.
S8 — Lifecycle. Manufacturability at area scale, cost, and end-of-life disposition. Target: ≥ 0.1 m² per production batch; biodegradable or compostable at end of service.
3. The Bandwidth Argument
Before scoring platforms, it is worth stating the paper's central claim, because it determines how the scores should be read.
Living systems and engineered electronic systems have complementary, and largely non-overlapping, competence bands.
Where biology wins. Chemical specificity is the clearest case. A fungal mycelium or an engineered biofilm can be made to respond to a named molecule at low concentration through a genetically encoded receptor, with a selectivity that a broadband synthetic chemiresistor does not approach. Autonomous repair is the second: no external trigger, no operator, no consumable cartridge. Self-cleaning and antifouling through active surface metabolism is the third. Growth-based fabrication — the ability to form a covering in situ around an arbitrary geometry rather than cutting and seaming a sheet — is the fourth, and it addresses S2 directly. Biodegradability at end of life is the fifth.
Where biology loses. Bandwidth. Mycelial electrophysiological activity presents as action-potential-like spiking events on timescales of seconds; a robot grasp controller correcting for incipient slip needs an answer in under 20 ms. Gene-expression-based reporting is slower still, with transcription-translation-maturation delays measured in tens of minutes. Amplitude is a second problem: extracellular potentials from a hyphal network are in the millivolt-to-microvolt range and require shielded instrumentation, whereas a piezoresistive or capacitive taxel delivers a robust signal from a simple divider. Spatial resolution is a third: no demonstrated living system provides addressable, spatially resolved contact maps at the taxel densities routinely achieved in synthetic e-skin.
The implication is that the research programme "replace electronic skin with living skin" is competing on the wrong axis. The defensible programme is stratified:
- Layer Function Technology Timescale
- Outer living layer Appearance, barrier, self-cleaning, autonomous repair, chemical sensing ELM (Class A/B/C) 10⁰–10⁵ s
- Intermediate synthetic sensor layer Contact, force, slip, vibration, temperature Electronic skin 10⁻³–10⁻¹ s
- Structural / actuation layer Load path, motion Conventional robotics 10⁻³–10⁰ s
Recent work on neuromorphic electronic skins with active injury perception and modular quick-release repair is instructive here: it achieves damage detection and rapid replaceability without biology, but the authors are explicit that non-living materials lack regenerative capability, which is precisely why the design compensates with modular replacement. The living layer's proposition is that replacement becomes unnecessary. That proposition is worth testing on its own terms, not by asking the living layer to also do taxel-density force sensing.
4. Platforms and State of the Art
4.1 Class A — Tissue-engineered mammalian skin
The most direct approach cultures human dermal fibroblasts in a collagen hydrogel around a robotic member, optionally seeding keratinocytes on the surface and lifting the construct to an air–liquid interface to drive stratification.
Demonstrated. A three-joint robotic finger has been covered by culturing a single continuous piece of skin tissue around the finger, avoiding seams (addresses S2). The construct bends with the underlying mechanism. Wound repair has been demonstrated on a dermis equivalent covering a robotic finger, using a grafted collagen sheet in a procedure adapted from the clinical treatment of deep burns, with recovery of tensile continuity across the repaired region (addresses S4). Subsequent work solved the adhesion problem (S3) by abandoning surface anchors in favour of perforation-type anchors inspired by skin ligaments: V-shaped perforations through the solid substructure, into which a viscous collagen gel is drawn after plasma treatment of the substrate lowers the contact angle enough for the gel to enter the fine features. The anchors let the skin move with the mechanism without tearing or peeling, and — critically for S2 — allow essentially arbitrary surface geometries to be skinned, which surface-adhesive approaches do not.
Failure modes. Mechanical strength is the dominant one. Collagen hydrogel dermis equivalents have moduli in the kilopascal range, three or more orders of magnitude below native dermis, and correspondingly low ultimate strength; they fail S1 by a wide margin. Sterility is the second: manipulating soft wet tissue without introducing bacteria is a substantial practical constraint, and a contaminated construct dies. The third is life support — see §6.
Score against specification: S1 ✗ (kPa moduli vs. MPa target) · S2 ✓ · S3 ✓ (perforation anchors) · S4 ✓ (assisted repair demonstrated; unassisted repair of full-thickness defects not) · S5a ✗ · S5b ✗ (no engineered sensing demonstrated in this platform) · S6 ✗ (requires culture conditions) · S7 partial · S8 ✗
4.2 Class B — Bacterial cellulose and engineered biofilms
Bacterial cellulose is produced by Komagataeibacter species as a dense hydrated pellicle of nanofibrillar cellulose. It is substantially purer, stronger and more crystalline than plant cellulose, is biocompatible, and can be grown under static, agitated or bioreactor conditions.
Demonstrated. Programmable function has been established through a stable co-culture of K. rhaeticus with engineered S. cerevisiae ("Syn-SCOBY"). Two distinct strategies are available: engineered yeast can secrete enzymes into the growing cellulose, producing autonomously grown catalytic materials and enabling DNA-encoded modification of the bulk material properties; or the engineered yeast can be retained within the growing matrix, producing a material that senses and responds to chemical and optical stimuli. This is the strongest available route to S5b — genetically encoded, chemically specific sensing built into the covering material itself. Separately, engineered bacterial biofilms have been developed as a broader ELM platform, with the field noting that high-yield producers such as K. rhaeticus and mycelium are the realistic candidates for mass production, since laboratory biofilm matrices do not scale economically.
Failure modes. No published robot-skin demonstration. Appearance is unlike skin. The material's mechanical properties are strongly hydration-dependent — stiff and paper-like when dry, compliant when wet — and the useful mechanical regime may not coincide with the metabolically viable one. Response latency for gene-expression-based reporting is tens of minutes.
Score: S1 partial (strong, but compliance/strain-to-failure profile is unlike skin) · S2 ✗ (grown as a pellicle; conformal growth on non-developable robot geometry not demonstrated) · S3 ✗ · S4 partial · S5a ✗ · S5b ✓ (best in class) · S6 partial (desiccation tolerance and dormancy are advantages) · S7 unknown · S8 ✓ (best in class)
4.3 Class C — Fungal mycelium
Mycelium provides two distinct and largely independent capabilities, and the literature conflates them at some cost to clarity.
As a structural living skin. Hydrogels loaded with Ganoderma lucidum have been three-dimensionally printed into lattice architectures that promote a balanced exploration/exploitation growth pattern — the hyphae simultaneously colonise the gel and bridge air gaps between printed filaments. On this basis the authors printed a robotic skin that is mechanically robust, self-cleaning, and autonomously regenerating after damage. Multi-axis printing onto non-planar mandrels, with the nozzle held perpendicular to the substrate throughout, produced grid templates that became living skins after seven days at 23 °C and 95% relative humidity; these were applied to a robotic gripper and to untethered rolling robots. This is, to date, the most complete single demonstration against the specification, because it hits S1, S2 and S4 together, which no other platform does.
As a sensor and controller. Separately, the electrophysiological activity of living mycelia has been used to control robotic actuators directly. Two biohybrid robots — a soft walking machine and a wheeled machine — were built in which mycelia sense the environment and emit action-potential-like spiking voltages that serve as control signals to motors and valves. The two enabling contributions are a vibration- and EMI-shielded mycelial electrical interface permitting stable long-term recordings during untethered mobile operation, and a control architecture inspired by neural central pattern generators. Three regimes were demonstrated: locomotion driven by the mycelia's spontaneous spiking, response to an external stimulus (light), and complete override of the native signal by the operator. The stated forward path is chemical rather than optical input, with soil chemistry sensing given as the motivating application.
Failure modes. Requires high humidity (95% RH in the printing work) — a direct conflict with S6. Contamination during fabrication is the recurring practical failure in mycelium printing generally. Appearance is not human-like. Signal bandwidth and amplitude are as discussed in §3. Growth-based fabrication takes days, which constrains manufacturing throughput but is not disqualifying.
Score: S1 ✓ · S2 ✓ · S3 partial · S4 ✓ · S5a ✗ · S5b ✓ (electrophysiological; specificity demonstrated for light, asserted for chemistry) · S6 ✗ (humidity requirement) · S7 partial (shielded interface demonstrated) · S8 ✓
4.4 Class D — Photosynthetic ELMs (prospective)
No robot-skin demonstration exists. The interest is structural: a photosynthetic layer converts ambient light into fixed carbon in situ, which addresses the nutrient-supply problem identified in §6 without a perfusion loop. Cyanobacteria-mediated biomineralisation has been used to make regenerative living building materials from sand–hydrogel matrices, establishing that photosynthetic ELMs can perform a structural function. Whether the areal productivity of a thin photosynthetic layer under indoor illumination can meet the carbon demand of a co-resident heterotrophic layer is an open, and calculable, question. We flag it as the highest-value unexplored direction.
4.5 Cross-platform summary A: Mammalian B: Bacterial cellulose C: Mycelium S1 Mechanics ✗ ◐ ✓ S2 Conformality ✓ ✗ ✓ S3 Adhesion ✓ ✗ ◐ S4 Autonomous repair ◐ ◐ ✓ S5a Fast tactile ✗ ✗ ✗ S5b Slow/chemical ✗ ✓ ✓ S6 Ambient operation ✗ ◐ ✗ S7 Electronics interface ◐ ? ◐ S8 Lifecycle ✗ ✓ ✓ Human-like appearance ✓ ✗ ✗
✓ demonstrated · ◐ partial · ✗ not met · ? not reported
The table makes the trade explicit. Class A is the only platform that delivers human-like appearance and is the only one designed around integration with a jointed mechanism; it is also the weakest and the most fragile. Class C is the only platform that satisfies mechanics, conformality and autonomous repair simultaneously. Class B is the only platform with genetically programmable chemical specificity and the only one with a credible manufacturing economy. No platform satisfies S6. This is the field's shared blocker and, we argue, the correct target for the next generation of work.
5. Integration Physics
5.1 Mechanical anchoring
The anchoring problem is the transition from a compliant hydrated layer to a stiff dry substructure across an interface that must carry shear through millions of cycles. Surface adhesives fail because they concentrate stress at a plane. The perforation-anchor solution succeeds because it converts a planar bond into a distributed volumetric interlock, mirroring the function of skin ligaments (retinacula cutis), which tether dermis to underlying fascia at discrete points while permitting sliding between them. Two design parameters follow directly and are, to our knowledge, unoptimised in the published work: anchor pitch relative to the local radius of curvature and joint travel, and anchor geometry (the reported V-profile is one point in a space that includes undercut, barbed and helical forms). The wetting problem — driving a viscous collagen gel into sub-millimetre features — was solved by plasma treatment; the same problem in Class B and C platforms has not been addressed and is a concrete near-term target.
5.2 Bioelectronic transduction
Recording from a living layer on a moving robot is dominated by two artefacts: mechanical vibration coupling into the electrode, and electromagnetic interference from the robot's own motors and drivers. The mycelial work addresses both with a shielded interface, which is the reason its recordings survive untethered operation. We recommend that any future living-skin sensing claim report, as a minimum, the signal-to-noise ratio measured during actuation, not at rest — a stationary-baseline SNR is not evidence that the channel is usable in a control loop.
The second transduction question is arbitration. If a living layer emits spontaneous spiking activity in the absence of any stimulus, a controller must distinguish signal from spontaneous activity. The demonstrated three-regime approach — spontaneous drive, stimulus-evoked response, operator override — is effectively a manual arbitration policy. An automatic policy (e.g. a learned discriminator over the spike train, or a redundant synthetic channel used as a reference) is required before such a system can be trusted with safety-relevant decisions.
5.3 The wet/dry boundary
Class A and, to a lesser degree, Class C require a hydrated environment; robot electronics require a dry one. The field's own stated priority is a robotic architecture with a coexisting perfusable artificial vascular network and biocompatible waterproof electronics, which would simultaneously maintain skin viability and permit integration of processors, motors, sensors, wiring, batteries and communications. This is an engineering problem with a known solution class (conformal parylene or silicone encapsulation with hermetic feedthroughs) and no published demonstration at skin scale. It should be treated as a system-integration milestone in its own right rather than as a detail.
6. Life-Support Budget: Water, Not Energy, Is the Binding Constraint
A recurring implicit assumption in discussion of living robot skin is that keeping the cells alive is energetically expensive and will compete with the robot's power budget. A first-order calculation shows this is wrong, and identifies the real constraint. Full assumptions and sources are in Appendix A; these are order-of-magnitude estimates from literature parameter values, not measurements.
Metabolic power. For a 2 mm dermis equivalent at a fibroblast density of ~10⁶ cells cm⁻³ and a per-cell oxygen consumption rate of ~10⁻¹⁷ mol s⁻¹, one square metre contains ~2 × 10⁹ cells consuming ~2 × 10⁻⁸ mol O₂ s⁻¹. Fully oxidised, this corresponds to a metabolic heat output of ~10 mW m⁻² and a substrate demand of ~50 mg glucose-equivalent per m² per day — rising to perhaps 0.5 g m⁻² day⁻¹ if the cells are as glycolytic as cultured fibroblasts typically are. Against a mobile robot's baseline draw of tens to hundreds of watts, the living layer's own energy consumption is negligible. It is not a power-budget problem.
Oxygen delivery. Oxygen diffusion in tissue supports viability only ~100–200 µm from a source. Native skin partially circumvents this by taking oxygen transcutaneously from the atmosphere. This yields a useful design rule: a living layer thinner than ~200 µm over a gas-permeable substrate may not require vasculature at all, whereas a millimetre-scale layer does. Much of the perceived need for an artificial vascular network is a consequence of a thickness choice made for appearance and mechanics, not an inescapable requirement.
Nutrient delivery. Glucose and amino acids cannot be obtained from air. A living layer therefore requires either a perfusion network, periodic immersion or gel replenishment, or an autotrophic co-resident (§4.4). This is the strongest argument for the photosynthetic direction.
Water. Here the numbers turn hostile. Human skin with an intact stratum corneum loses roughly 5–10 g m⁻² h⁻¹ transepidermally. In vitro skin equivalents are well documented not to develop full barrier function, with water loss typically an order of magnitude higher. At a representative 50 g m⁻² h⁻¹, a square metre of living skin loses ~1.2 kg of water per day. A humanoid with ~1.8 m² of coverage would need to carry or replenish roughly 2 L per day purely to stay hydrated — comparable to the robot's entire battery mass, and recurring.
Conclusion. The life-support problem for mammalian living skin is a water-management problem, and secondarily a nutrient-logistics problem. It is not an energy problem. This reframes the research priority: the highest-value target is not a smaller pump or a more efficient culture medium but a functional barrier layer — either a properly stratified and cornified epidermis, or a gas-permeable, water-retaining synthetic outer film that lets the living layer sit beneath it. Class B and Class C platforms sidestep the issue through desiccation tolerance and dormancy, which is a major and under-credited advantage.
7. Failure Modes and Reliability
A living skin introduces failure modes with no synthetic analogue. Any deployment case must address the following, and we recommend they be reported explicitly.
Contamination. A non-sterile environment contains organisms that will colonise a nutrient-rich hydrated matrix. If sterility is not maintained during fabrication, bacteria enter and the tissue dies. In use, the construct is exposed continuously. Class C mycelium has an advantage: an actively growing, antagonistic fungal network with self-cleaning behaviour resists colonisation in a way a passive collagen gel does not.
Desiccation. Fastest failure mode for Class A; recoverable in Class B and C through dormancy and rehydration.
Necrosis from diffusion limitation. Any region beyond the oxygen diffusion length from a source dies, and the dead tissue then becomes a substrate for contamination — a coupled failure, where one mode induces another.
Mechanical fatigue at anchors. Stress concentrates at the anchor sites by design. Long-cycle data at these interfaces is, to our knowledge, unpublished.
Genetic instability. Engineered strains under long culture lose engineered function to mutation and to selection against metabolic burden. For a material intended to serve for months, loss-of-function drift is a reliability parameter and should be reported as a half-life of engineered function under operating conditions.
Biocontainment escape. Engineered organisms on the exterior of a mobile robot operating in an uncontrolled environment is a release pathway. This is a regulatory question before it is a technical one (§9).
Immunological absence. Tissue constructs lack an immune system, vasculature and innervation. Repair is fibroblast-mediated matrix deposition, not the full wound-healing cascade. Claims of "self-healing like human skin" should be read narrowly.
8. Proposed Reporting Standard
Cross-study comparison in this field is currently very difficult: mechanical, biological and robotic results are reported in the conventions of three different disciplines. We propose a minimum reporting set.
Category Metric Method Mechanical Modulus at 10% and 30% strain; UTS; strain at failure Uniaxial tension, hydrated, at operating temperature Mechanical Cycles to failure at joint-relevant strain Cyclic loading to defined amplitude Interface Interfacial shear strength; peel strength Lap shear; 90° peel Repair % UTS and % barrier function recovered vs. time Standardised full-thickness incision Repair Whether repair was assisted (graft, medium change) or unassisted Stated explicitly Viability Viable cell fraction vs. depth and time Live/dead assay, depth-resolved Life support Water loss (g m⁻² h⁻¹); nutrient consumption (g m⁻² day⁻¹) Gravimetric; medium assay Sensing SNR during actuation; latency; limit of detection; specificity vs. named interferents Defined stimulus protocol Environment Days of unattended operation at stated T, RH, and non-sterility Continuous run to failure Genetic Half-life of engineered function under operating conditions Functional assay over serial time points Lifecycle Areal production rate; cost per m²; end-of-life pathway Reported directly
The two entries we consider most important are "unattended days at stated ambient conditions" and "assisted or unassisted repair", because these are where the literature's language most often outruns its evidence.
9. Evidence Tiering
To keep demonstrated results separable from extrapolation, we classify the claims in this review by epistemic status.
Claim Tier
- 1.A seamless tissue-engineered skin can be cultured around a multi-joint robotic finger that then articulates Demonstrated
- 2.Perforation-type anchors bind skin equivalents to complex substructures without tearing or peeling Demonstrated
- 3.A wounded dermis equivalent on a robotic finger can be repaired via collagen-sheet grafting Demonstrated (assisted)
- 4.3D-printed mycelium can form a robotic skin that is mechanically robust, self-cleaning, and autonomously regenerating Demonstrated
- 5.Mycelial electrophysiology can drive robot actuators, with stimulus response and operator override Demonstrated
- 6.Engineered co-cultures can produce bacterial cellulose that senses and responds to chemical and optical stimuli Demonstrated
- 7.The binding life-support constraint for mammalian living skin is water flux, not energy Calculated estimate (Appendix A); untested experimentally
- 8.Living layers thinner than ~200 µm on gas-permeable substrates may not require vasculature Inference from diffusion physics; not demonstrated in a robot skin
- 9.Stratified hybrid architecture (fast synthetic + slow living) outperforms either alone Conjecture; no comparative study exists
- 10.Chemical sensing via mycelia will follow from demonstrated optical sensing Author-stated forward path; not yet demonstrated
- 11.Photosynthetic co-residency can close the nutrient loop for a living skin Speculative; calculable but uncalculated
- 12.Living skins will reach 30-day unattended ambient operation Speculative
Claims 1–6 are load-bearing for the review's argument and are all demonstrated. Claim 9 — the paper's central thesis — is explicitly a conjecture, and §11 states the experiment that would test it.
10. Biosafety, Ethics, and Regulation
Three issues require resolution before field deployment, and none is currently addressed in the robotics literature.
Environmental release. A mobile robot carrying engineered organisms on its exterior is a dispersal vector. Existing biocontainment strategies — auxotrophy, kill switches, synthetic amino acid dependence — are transferable but impose a metabolic burden that shortens the functional half-life of the engineered strain (§7). The engineering and the containment requirements are in direct tension, and this tension should be quantified rather than assumed away.
Human contact. Skin-to-skin contact between a person and a robot carrying live microorganisms is a plausible transmission route, benign or otherwise. Immunocompromised populations are a specific concern for care robotics, which is otherwise one of the most-cited applications for human-like living skin.
Human-derived tissue. Class A constructs use human cells, raising donor consent, provenance and commercialisation questions already familiar from tissue engineering but new to robotics procurement chains.
Regulatory ambiguity. A robot covered in living human-derived tissue is not obviously a medical device, a cosmetic, a GMO release, or a machine. It plausibly falls under several regimes at once, with no clear lead authority in most jurisdictions.
Appearance. A human-appearing living skin makes uncanny-valley and deception questions concrete rather than hypothetical, and interacts with disclosure norms for human–robot interaction.
11. Open Problems and the Critical Experiment
Ranked by our estimate of value per unit effort:
- Ambient-condition operation (S6). The shared blocker. Target: a living skin that survives 30 days at 25 °C, 50% RH, non-sterile, unattended. Most promising route: a gas-permeable, water-retentive synthetic outer film over a thin living layer, converting the barrier problem from a biological one into a membrane-engineering one.
- A barrier layer for Class A. Either genuine cornification in the skin equivalent, or the synthetic film above. Directly attacks the dominant term in the water budget.
- Mechanical strengthening of Class A. Composite reinforcement of collagen constructs — aligned fibre, or a Class B cellulose interpenetrating network — to close the three-order-of-magnitude gap in S1. A mammalian/bacterial-cellulose hybrid is an obvious and, as far as we can determine, untried construct.
- Conformal growth for Class B. Growing bacterial cellulose directly onto non-developable robot geometry, transferring the S2 solution from Class A and C.
- Chemical sensing on a robot. Executing the stated transition from optical to chemical input in mycelial control, with reported specificity against named interferents.
- Photosynthetic nutrient closure. Calculate first: areal photosynthetic productivity under indoor illumination versus heterotrophic carbon demand. If the ratio is unfavourable by orders of magnitude, the direction should be abandoned early.
The critical experiment for Claim 9. The bandwidth thesis is testable and has not been tested. Build three otherwise-identical robotic manipulators: one with a synthetic e-skin only, one with a living skin only, one stratified. Subject all three to an identical protocol combining (a) a fast manipulation task requiring slip detection, (b) a chemical detection task, and (c) a mechanical damage event followed by continued operation. Report task success, time-to-recovered-function, and total intervention hours. The stratified architecture predicts: parity with synthetic on (a), parity with living on (b), and a decisive advantage on (c). A null result on (c) would substantially weaken the case for living robot skin generally, which is what makes the experiment worth running.
12. Conclusion
Engineered living materials deliver capabilities that synthetic robot skins do not: untriggered autonomous repair, growth-based conformal coverage of arbitrary geometry, active self-cleaning, genetically programmable chemical specificity, and benign end-of-life. These are demonstrated results across three platforms, not projections. What is equally clear from the evidence is that living materials are poorly matched to the high-bandwidth, spatially resolved tactile sensing that dominates the electronic-skin literature, and that no platform yet operates unattended in ordinary ambient conditions.
The productive reading is that living skin is not a replacement for electronic skin but a different layer of the same stack, occupying the slow channel that electronics cannot serve. On that reading, the field's central engineering problem is not sensing at all — it is water. The metabolic energy demand of a living integument is negligible against a robot's power budget; its water demand is not. Solving the barrier problem, rather than the sensing problem, is what would move living robot skin from laboratory demonstration to a covering that can simply be left on.
Author
- Oleksii Vergunenko
References
- 1.Kawai, M., Nie, M., Oda, H., Morimoto, Y., Takeuchi, S. Living skin on a robot. Matter 5(7), 2022.
- 2.Rafsanjani, A., Coulter, F. B., Studart, A. R. Giving life to robotic skins. Matter 5(7), 1990–1992, 2022.
- 3.Kawai, M., Nie, M., Oda, H., Takeuchi, S. Perforation-type anchors inspired by skin ligament for robotic face covered with living skin. Cell Reports Physical Science, 102066, 2024. doi:10.1016/j.xcrp.2024.102066
- 4.Gantenbein, S., Colucci, E., Käch, J., Trachsel, E., Coulter, F. B., Rühs, P. A., Masania, K., Studart, A. R. Three-dimensional printing of mycelium hydrogels into living complex materials. Nature Materials 22(1), 2023. doi:10.1038/s41563-022-01429-5
- 5.Mishra, A. K., Kim, J., Baghdadi, H., Johnson, B. R., Hodge, K. T., Shepherd, R. F. Sensorimotor control of robots mediated by electrophysiological measurements of fungal mycelia. Science Robotics 9(93), 2024. doi:10.1126/scirobotics.adk8019
- 6.Gilbert, C., Tang, T.-C., Ott, W., et al. (Ellis, T., senior author). Living materials with programmable functionalities grown from engineered microbial co-cultures. Nature Materials 20, 2021. doi:10.1038/s41563-020-00857-5
- 7.Wang, Y., Zhang, Q., Ge, C., An, B., Zhong, C. Programmable bacterial biofilms as engineered living materials. Accounts of Materials Research 5(7), 797–808, 2024. doi:10.1021/accountsmr.3c00271
- 8.Malcı, K., Li, I. S., Kisseroudis, N., Ellis, T. Modulating microbial materials — engineering bacterial cellulose with synthetic biology. ACS Synthetic Biology 13(12), 3857–3875, 2024. doi:10.1021/acssynbio.4c00615
- 9.Toward practical applications of engineered living materials with advanced fabrication techniques. ACS Synthetic Biology, 2024. doi:10.1021/acssynbio.4c00259
- 10.Materials come alive (Editorial). Nature Materials, 2022. doi:10.1038/s41563-022-01234-0
- 11.A neuromorphic robotic electronic skin with active pain and injury perception. PNAS, 2025. doi:10.1073/pnas.2520922122
- 12.Quijano, L., Fischer, D., Ferrero-Regis, T., Navone, L. Exploring bacterial cellulose as an engineered living and programmable biomaterial across disciplines through qualitative thematic analysis. Scientific Reports, 2025. doi:10.1038/s41598-025-01931-1
- 13.Mohseni, A., Vieira, F. R., Pecchia, J. A., Gürsoy, B. Three-dimensional printing of living mycelium-based composites: material compositions, workflows, and ways to mitigate contamination. Biomimetics 8(2), 257, 2023. doi:10.3390/biomimetics8020257
- 14.Extrusion-based additive manufacturing of complex three-dimensional ultra-lightweight materials using the basidiomycete Fomes fomentarius. Materials Advances, Issue 15, 2025. doi:10.1039/D5MA00320B
Experimentation loop
0. What this simulation is for
The model is not a predictor of absolute service life. Every parameter is a literature order-of-magnitude value, and several are known only to within a decade. Its purpose is structural: to determine which acceptance tests are coupled, so that the physical verification programme is not designed on the false assumption that the subsystems can be debugged independently.
The design follows one rule: a single shared state vector, read by every subsystem. Nothing is fitted. If turning one physical knob breaks four tests at once, that is a property of the coupling graph, not something inserted by hand.
Files
File Contents elmskin.py Coupled water / nutrient / oxygen / viability / matrix / mechanics / sensing model; acceptance tests; photosynthetic supply bandwidth.py Monte-Carlo architecture comparison testing Claim 9 make_figures.py Generates all seven figures fig1 – fig7 (PNG) Results 0.1 State and coupling
Per m² of skin, lumped through-thickness except oxygen (1-D reaction–diffusion):
Symbol State Units W water inventory (→ hydration φ = W/W_max) kg m⁻² G nutrient inventory (glucose-equivalent) g m⁻² C_v viable cell fraction – M matrix integrity – B contaminant load – R_p repair progress at a damage site – F engineered-function retention (genetic drift) –
The coupling graph — the reason the model is worth running:
k_b (barrier) ──► W ──► φ ─┬─► desiccation stress ──► C_v ──┬─► M ──┬─► UTS (T3a)
│ │ └─► ε_f (T3b)
├─────────────────────────────────┼─► R_p (T4)
└─────────────────────────────────┴─► SNR (T5)
│
necrosis (1−C_v) ──► B ────────┘ (contamination
feeds on death)
ρ_cell × L ──► O2 penetration ──► f_O2 ──► C_v (T2)
G ──► starvation ──► C_vφ is the hub. Every failure mode except oxygen routes through it. That is the model's central structural prediction, and §4 turns it into a falsifiable experiment.
0.2 Acceptance tests (from the paper's specification) Test Spec Criterion at day 30 T1 S6 hydration φ ≥ 0.50 T2 S6 oxygen viable depth fraction ≥ 0.90 T3 S1 UTS ≥ 1 MPa and ε_f ≥ 0.30 T4 S4 ≥ 50 % repair within 7 d of a full-thickness cut T5 S5b living-channel SNR ≥ 6 dB during actuation 1. Results R1 — Water dominates the mass budget; energy is irrelevant
Figure 1a.
Flux Value Water (evaporative, no barrier) 1,128 g m⁻² d⁻¹ Glucose-equivalent (metabolic) 0.50 g m⁻² d⁻¹ Oxygen (metabolic) 0.55 g m⁻² d⁻¹ Metabolic heat 96 mW m⁻²
Water:nutrient = 2,257 : 1. This confirms the paper's §6 conclusion. It corrects the heat figure: the paper's Appendix A used an OCR of 10⁻¹⁷ mol cell⁻¹ s⁻¹ and reported ~10 mW m⁻²; the simulation uses 10⁻¹⁶ (upper literature range) and gives ~96 mW m⁻². Either way the conclusion is unchanged — the living layer's power draw is negligible against any mobile robot.
R2 — There are three regimes, not one. This contradicts the paper's conclusion.
Figure 1b.
Barrier quality k_b [g m⁻² h⁻¹ kPa⁻¹] Regime Time to first spec violation 31.5 (skin equivalent, no barrier) water-limited 0.9 d 4.7 (native human skin) water-limited 4.2 d ≤ 2 (better than skin), stored nutrient only nutrient-limited plateau 4.35 d — does not improve further 0.7, closed nutrient loop function-limited 28.3 d 0.30, closed nutrient loop function-limited 53 d — all five tests pass
The paper's §12 states that "solving the barrier problem, rather than the sensing problem, is what would move living robot skin from laboratory demonstration to a covering that can simply be left on." The simulation shows this is wrong as stated. Barrier improvement saturates at ~4.3 days because the stored nutrient inventory (~2 g m⁻²) runs out. Below k_b ≈ 2, further barrier engineering buys nothing at all without a closed nutrient loop.
The corrected statement: 30-day autonomy requires a barrier roughly an order of magnitude better than human skin and a closed nutrient supply. Neither alone is sufficient, and the two are not independently optimisable.
R3 — Failure propagation: four tests break within a 1.4× window of one knob
Figure 2. Sweeping only k_b, with everything else at the passing design:
Test Breaks at k_b = T5 (sensing SNR) 0.66 T3 (mechanics) 0.78 T4 (repair) 0.78 T1 (hydration) 0.92 T2 (oxygen) never — uncoupled from this knob
Four of five acceptance criteria cross their thresholds within a factor of 1.4 in a single parameter. This is the epistemically load-bearing result, and it is more informative than the passing configuration: it means the subsystems cannot be developed or debugged separately. A team that fixes the mechanics in isolation and then observes the sensing channel fail has not found two problems; it has found one problem twice.
T2's flatness is equally informative — it establishes that oxygen genuinely is an independent axis, so the coupling is not an artefact of everything being wired to everything.
Figure 4 shows the cascade in time: hydration falls → viability collapses (day 8–14) → contamination blooms on the necrotic substrate (day 15–22) → matrix integrity is destroyed → UTS rises transiently as drying stiffens the construct, then collapses, while ε_f falls monotonically → SNR falls off a cliff. The transient strength rise is worth noting: a mid-experiment tensile measurement can look better than baseline while the construct is dying.
R4 — The oxygen constraint is set by cell density, not thickness. Claim 8 is retracted as stated.
Figure 3b.
Cell density O₂ penetration depth L_c 10⁶ cells cm⁻³ (published dermis equivalents) 2.83 mm 10⁸ cells cm⁻³ 283 µm 2 × 10⁸ cells cm⁻³ 200 µm ← the literature figure
The paper's Claim 8 ("living layers thinner than ~200 µm on gas-permeable substrates may not require vasculature") is true but badly framed. The 200 µm figure applies at ~2 × 10⁸ cells cm⁻³ — near-native tissue density. At the seeding densities actually used in published robot-skin work, a 2 mm construct is not oxygen-limited at all, with two orders of magnitude of margin.
Corrected: the vascularisation requirement is a consequence of the density × thickness product, and at present it is the density choice — driven by the desire for tissue-like appearance and mechanics — that would activate it, not the thickness. Anyone claiming a vascular network is needed should first report their cell density.
R5 — A derived requirement the paper missed: the encapsulant must be selective
Figure 5.
The barrier must hold water in while letting oxygen through. Quantitatively, for the reference construct:
required O₂ permeance ≥ 1 × 10⁻¹¹ mol m⁻² s⁻¹ Pa⁻¹ required water-vapour permeance ≤ 0.30 g m⁻² h⁻¹ kPa⁻¹ required modulus ≤ ~10 MPa (compliance, spec S1)
Against commodity films (25 µm basis):
Material k_b O₂ permeance E [MPa] Verdict PDMS (silicone) 35 8.0 × 10⁻⁹ 2 ✗ water by 100× Breathable PU 14 4.0 × 10⁻¹¹ 20 ✗ water by 47× Natural rubber 1.4 3.2 × 10⁻¹⁰ 2 ✗ water by 4.7× Butyl rubber 0.021 1.74 × 10⁻¹¹ 2 ✓ all three LDPE 0.11 3.9 × 10⁻¹¹ 200 ✗ compliance PP 0.05 2.3 × 10⁻¹¹ 1500 ✗ compliance PET 0.14 4.7 × 10⁻¹² 3000 ✗ O₂, compliance PVDC 0.011 7.1 × 10⁻¹³ 500 ✗ O₂ by 14× EVOH 0.42 8.0 × 10⁻¹⁴ 2500 ✗ O₂ by 125×
Silicone — the default choice for encapsulating anything biological — is the single worst material on this plot for water retention. The one candidate that clears all three constraints is butyl rubber, with an O₂ margin of only 1.74×. That margin is consumed by a 2× increase in cell density, which links R5 directly back to R4: the encapsulant choice and the cell-density choice are the same decision.
Recommend adding S9 — encapsulant selectivity to the paper's specification.
R6 — Photosynthetic closure is plausible, not speculative
Figure 1c. Illuminance at which a photosynthetic co-resident layer meets the 0.5 g m⁻² d⁻¹ heterotrophic demand:
Realised efficiency η Crossover 1.0 (theoretical, 10 photons/CO₂) 143 lux 0.30 (optimistic layer) 476 lux 0.10 (realistic layer) 1,429 lux
1,400 lux is ordinary bright indoor light near a window; a corridor at 200 lux is not sufficient. The paper listed Claim 11 as "speculative; calculable but uncalculated." It is now calculated, and it passes at realistic efficiency in well-lit indoor environments. This should be promoted to a funded workstream, because it is the only route that closes R2's nutrient constraint without a perfusion loop.
R7 — The bandwidth thesis (Claim 9): stratified wins, but not everywhere
Figure 6. 180-day mission, Poisson damage at 14-day mean, 400 runs.
Synthetic Living Stratified Fast task (slip, 20 ms deadline) 0.968 0.000 0.969 Chemical task (F1, interferent load) 0.561 0.878 0.883 Mean availability 0.859 0.916 0.963 Human intervention [h / 180 d] 6.6 0.0 0.0
The stratified architecture dominates, but the structure of the win matters and constrains how the physical trial must be pre-registered:
Against synthetic-only, the advantage is chemical specificity (+0.32 F1) and intervention hours (−6.6 h). Large. Against living-only, the advantage is entirely the fast task (0.97 vs 0.00) and availability (+0.047). On the chemical task the difference is 0.883 vs 0.878 — indistinguishable.
Effect sizes on availability: stratified vs living, d = 2.60; stratified vs synthetic, d = 3.59. At 80 % power and α = 0.05, the model implies n = 3 per arm. Real-world variance will exceed model variance, so n = 5 is recommended.
The chemical comparison against living-only must be pre-registered as a non-inferiority test, not a superiority test. Powering it for superiority would require an impractical sample and would be a fishing expedition. The stratified architecture's claim on that axis is "no worse," and that is what should be tested.
R8 — At the passing design point, the binding subsystem migrates
Figure 7. Elasticity η = ∂ln(TTF)/∂ln(X) at k_b = 0.30 with a closed nutrient loop:
Parameter η living-channel SNR at t = 0 +2.32 ambient relative humidity +0.66 water inventory group (k_b, L, ε_w) ±0.61 engineered-function half-life τ_gen +0.38 critical hydration φ_crit −0.36 everything else (ρ_cell, q_O₂, k_rep, UTS_ref, g_use…) ≈ 0
Once the barrier and nutrient problems are solved, service life is set by the sensing channel and by genetic drift, not by water. This is a third regime the paper does not anticipate, and it changes what has to be measured precisely: the initial SNR and the half-life of engineered function move to the top of the instrumentation priority list, while cell density, repair rate and reference UTS can be estimated coarsely at this operating point.
2. Revised claims ledger
Updating §9 of the paper. Changes in bold.
# Claim Previous tier New tier Basis 7 Water flux, not energy, is the binding life-support constraint calculated estimate Confirmed by simulation; heat figure revised 10 → ~100 mW m⁻² R1 8 Layers < 200 µm may not need vasculature inference Retracted as stated; replaced by a density × thickness criterion R4 9 Stratified architecture dominates conjecture Conjecture, now with effect sizes and a powered trial design R7 11 Photosynthetic closure speculative Plausible: passes at η = 0.10 above ~1,400 lux R6 12 30-day unattended operation speculative Reachable in model, but requires barrier AND nutrient loop jointly R2 — (new) Four of five acceptance tests are coupled through hydration — Model prediction; falsifiable — see §4, Stage 2 R3 — (new) S9: encapsulant must satisfy water × O₂ × compliance jointly; butyl rubber is the only commodity candidate — Derived requirement; untested R5 — (new) Once the barrier is fixed, service life is set by sensing and genetic drift — Model prediction; untested R8
Two of the paper's claims moved down in confidence (8, and the §12 conclusion) and two moved up (11, 12). The simulation earned its keep mainly through the retraction.
3. Model limitations
Stated plainly, because the verification loop in §4 is designed to attack exactly these.
Lumped hydration. Real drying is a moving front with a gradient; the model treats φ as uniform. This will overestimate time-to-failure at high k_b and is the most likely source of quantitative error. Mechanics are phenomenological. UTS(φ, M) and ε_f(φ, M) are plausible functional forms, not constitutive laws. The shapes (drying strengthens then embrittles) matter for the argument; the coefficients do not survive contact with data. Sensing model is a scalar amplitude. No spectral content, no spike statistics, no discrimination model. SNR is treated as a proxy for channel usability, which is generous. Contamination is a single lumped species with logistic growth. Real colonisation is species-specific, spatially heterogeneous, and stochastic. No thermal coupling and no mechanical fatigue. Both are in the specification (S1) and neither is modelled. Bandwidth module uses assumed channel parameters. Latency, specificity and MTTR values are literature order-of-magnitude assumptions, not measurements. This module is a hypothesis generator, and its only legitimate output is the trial design in Stage 4. 4. The closed verification loop
The loop is closed in the control-theoretic sense: every stage produces a prediction before the experiment runs, the experiment produces a residual, and the residual either updates parameters (design iteration) or invalidates model structure (model revision). Each stage has an explicit kill criterion — a result that stops the programme rather than triggering another iteration.
┌─────────────────────────────────────────────────────────────────┐ │ │ │ MODEL ──► predicted metrics + uncertainty ──► EXPERIMENT │ │ ▲ │ │ │ │ ▼ │ │ parameter ┌───────────── residual analysis ─────┐ │ │ update ◄──────────┤ |r| within CI → update params │ │ │ (Stage 0 rig) │ |r| large, structure holds │ │ │ │ → re-select design point │ │ │ MODEL REVISION ◄───┤ coupling signature absent │ │ │ │ → model structure is wrong │ │ │ PROGRAMME STOP ◄───┤ kill criterion met │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ Stage 0 — Parameter identification (coupons, no robot)
Duration 6 weeks. Article 50 × 50 mm flat coupons of the chosen platform.
Priority is set by Figure 7, not by convenience.
Parameter Measurement Rig Target precision Why this precision SNR₀ electrode recording, construct at rest and on a shaker shielded Faraday enclosure + vibration table ±1 dB η = 2.32, dominates everything k_b gravimetric water loss, controlled T/RH climate chamber, balance ±1 mg ±10 % η = 0.61; also the Stage 2 knob ε_w · L wet/dry mass, calliper oven, balance ±5 % sets the water inventory τ_gen serial functional assay of the engineered reporter, 90 d plate reader / flow ±20 % η = 0.38 φ_crit viability vs equilibrated RH desiccator series over saturated salts ±0.05 η = −0.36 ρ_cell, q_O₂ DNA quant / haemocytometer; Clark electrode or optical O₂ standard ±50 % is fine η ≈ 0, but sets R4/R5 coupling UTS(φ), ε_f(φ) uniaxial tension at 5 equilibrated hydration levels load frame in a humidity box ±20 % fixes the constitutive shape
Prediction to record before running: UTS rises then falls as φ decreases, while ε_f falls monotonically (Figure 4, lower row). Kill criterion: none — this stage cannot fail, only inform. Update: re-run make_figures.py with measured parameters; regenerate Figure 3a; re-select the design point before Stage 1.
Stage 1 — Encapsulant screen (the S9 test)
Duration 4 weeks. Article coupons under five candidate encapsulants: butyl rubber, natural rubber, LDPE, PDMS, and bare (control).
Prediction: ranking by time-to-φ<0.5 follows butyl > LDPE > natural rubber > PDMS > bare, and PDMS performs worse than a stiff commodity film despite being the standard biological encapsulant. Measure: water loss, O₂ partial pressure at the substrate interface (microelectrode), viability at 14 d. Kill criterion: if butyl-encapsulated coupons show O₂ starvation at the design cell density, the selectivity window is empty and the passive-barrier route is dead — go to active water recovery or to a dormancy-tolerant platform (Class B/C). Update: measured k_b and O₂ permeance replace the literature values in Figure 5.
Stage 2 — The coupling falsification test ★ critical
This is the experiment the whole simulation exists to justify. It tests model structure, not parameters.
Duration 8 weeks. Design one knob, five levels, n = 4 replicates. Vary only encapsulant grade (barrier permeance) across a range spanning k_b ≈ 0.1 → 3. Hold cell density, thickness, geometry, medium, temperature and RH fixed.
Measure all five acceptance tests on every replicate, at day 30, including the damaged-and-repaired subset.
Prediction (pre-registered):
T5, T3 and T4 will cross their thresholds within a factor of ≤ 2 in k_b of each other and of T1. T2 will not cross at all.
Falsification: if the four breakpoints span more than 5× in k_b, the hydration-hub structure is wrong and the model must be rebuilt with independent subsystem drivers. This is a real risk and the pre-registration must say so. Kill criterion: none — a falsification here is a successful experiment and redirects the programme. Why it matters operationally: if coupling is confirmed, the development plan collapses from five parallel workstreams to one, and any future result showing a single subsystem "fixed" in isolation should be disbelieved.
Stage 3 — 30-day robot-integrated autonomy (S6)
Duration 12 weeks. Article a two- or three-joint actuated member skinned with perforation-type anchors, encapsulated per Stage 1, with the nutrient loop from Stage 0's measured demand.
Protocol: 25 °C, 50 % RH, non-sterile room, continuous duty cycle (defined actuation schedule), unattended. Full-thickness cut at day 30. Terminate at day 37.
Instrumentation: continuous mass (load cell under the whole assembly, for water balance), continuous electrode recording during actuation, periodic non-destructive stiffness probe (indentation), 16S/ITS sequencing of surface swabs at days 0/10/20/30, terminal histology for depth-resolved viability.
Prediction: with the Stage-0/1 design point, T1–T5 all pass at day 30 and repair reaches ≥ 50 % by day 37. Kill criterion: if no configuration reaches 14 days after two design iterations, stop pursuing mammalian Class A for ambient operation and redirect to Class C (mycelium), which the model shows tolerates the hydration excursion through dormancy. Update: the water-balance trace directly identifies whether the lumped-φ assumption (Limitation 1) held; a systematic early failure with correct mass loss indicates a moving-front effect and triggers a 1-D spatial rewrite of the water module.
Stage 4 — Three-arm bandwidth trial (Claim 9)
Duration 6 months. Arms n = 5 identical manipulators each: synthetic e-skin only; living skin only; stratified.
Task battery, repeated on a fixed schedule:
(a) Fast: grasp-and-hold with induced slip; success = arrest within 20 ms. (b) Chemical: detect a target analyte at defined concentration in the presence of ≥ 3 structurally similar interferents; score F1 with a 300 s deadline. (c) Damage: scheduled full-thickness insult; measure capability fraction and time-to-recovered-function; log every human intervention.
Primary endpoint: mean capability fraction over 180 days. Pre-registere
Simulation
Codepython
"""
elmskin.py -- Coupled life-support / mechanics / sensing model for an
engineered-living-material (ELM) robotic integument.
One shared state vector. Every subsystem reads from it. The point of the
model is NOT to predict absolute numbers -- the parameters are literature
order-of-magnitude values -- but to expose COUPLING: which acceptance
tests break together when a single physical knob is turned.
State (per m^2 of skin, lumped through-thickness except for O2):
W water inventory [kg m^-2]
G nutrient (glucose-equiv) inventory [g m^-2]
Cv viable cell fraction [-]
M matrix integrity [-]
B contaminant load [-]
Rp repair progress of a damage site [-]
F engineered-function retention [-]
All rates in per-day. Vectorised over an ensemble axis so phase maps are cheap.
"""
import numpy as np
# ----------------------------------------------------------------------
# Physical constants / environment
# ----------------------------------------------------------------------
P_SAT_25C = 3.17 # kPa, saturation vapour pressure of water at 25 C
RHO_W = 1000.0 # kg m^-3
D_O2 = 2.0e-9 # m^2 s^-1, O2 diffusivity in hydrated tissue
C_O2_SURF = 0.2 # mol m^-3, air-equilibrated O2 in tissue water
MW_GLUCOSE = 180.0 # g mol^-1
DH_GLUCOSE = 2870e3 # J mol^-1, full oxidation
# ----------------------------------------------------------------------
# Platform presets
# ----------------------------------------------------------------------
PLATFORMS = {
# UTS_ref [MPa], eps_f0 [-], k_b_native [g m^-2 h^-1 kPa^-1],
# desiccation tolerance phi_crit, dormancy (recoverable below phi_crit?)
"A_mammalian": dict(UTS_ref=0.005, eps_f0=0.60, k_b=31.5, phi_crit=0.55,
dormant=False, label="A: mammalian dermis equiv."),
"B_cellulose": dict(UTS_ref=20.0, eps_f0=0.10, k_b=45.0, phi_crit=0.10,
dormant=True, label="B: bacterial cellulose"),
"C_mycelium": dict(UTS_ref=2.0, eps_f0=0.35, k_b=25.0, phi_crit=0.20,
dormant=True, label="C: fungal mycelium"),
"H_hybrid": dict(UTS_ref=3.0, eps_f0=0.45, k_b=31.5, phi_crit=0.45,
dormant=False, label="H: BC-reinforced dermis (proposed)"),
}
def default_params(platform="H_hybrid", **over):
p = dict(
# --- geometry ---
L=2.0e-3, # m, living layer thickness
eps_w=0.95, # water volume fraction when fully hydrated
# --- environment ---
T_C=25.0,
RH=0.50,
p_sat=P_SAT_25C,
# --- barrier (THE knob) ---
k_b=31.5, # g m^-2 h^-1 kPa^-1 water-vapour permeance
# --- cells ---
rho_cell=1.0e12, # cells m^-3 (= 1e6 cells cm^-3)
q_O2=1.0e-16, # mol cell^-1 s^-1
# --- nutrients ---
G0=2.0, # g m^-2 stored glucose-equivalent
g_use=0.50, # g m^-2 d^-1 at Cv=1 (glycolytic fibroblasts)
J_nut=0.0, # g m^-2 d^-1 external / autotrophic supply
J_water=0.0, # kg m^-2 d^-1 external supply (perfusion)
# --- kinetics (per day) ---
k_des=3.0, # desiccation death rate constant
w_des=0.05, # sharpness of the hydration cliff
phi_crit=0.45,
dormant=False, # can the organism survive drying and recover?
k_anox=1.5, # anoxic death rate
k_starve=2.0, # starvation death rate
k_prolif=0.35, # recovery / proliferation
k_contam=0.9, # contaminant growth on necrotic substrate
k_antag=0.6, # active antagonism by healthy living layer
k_contam_kill=1.2, # cell death caused by contamination
k_syn=0.5, # matrix synthesis by viable cells
k_deg=0.7, # matrix degradation (necrosis + contamination)
k_rep=0.45, # wound repair rate at ideal conditions
tau_gen=90.0, # d, half-life of engineered genetic function
# --- mechanics ---
UTS_ref=3.0, # MPa at phi=1, M=1
eps_f0=0.45,
# --- sensing ---
SNR0_dB=22.0, # living-channel SNR at rest, ideal state
actuation_penalty_dB=9.0, # loss when the robot is moving
)
if platform is not None:
pre = PLATFORMS[platform]
p.update({k: v for k, v in pre.items() if k != "label"})
p.update(over)
return p
# ----------------------------------------------------------------------
# Constitutive relations
# ----------------------------------------------------------------------
def water_activity(phi):
"""Sorption isotherm: a_w ~ 1 for a wet gel, collapses as it dries."""
return phi / (phi + 0.03)
def evap_flux(phi, p):
"""kg m^-2 d^-1. Fick through the barrier, driven by vapour-pressure diff."""
dp = (water_activity(phi) - p["RH"]) * p["p_sat"] # kPa
dp = np.maximum(dp, 0.0)
return p["k_b"] * dp * 24.0 / 1000.0 # g/m2/h -> kg/m2/d
def o2_viable_fraction(p, L=None, rho=None):
"""One-sided O2 supply into a slab. Returns viable depth fraction.
Zeroth-order consumption, no-flux at the substrate:
C(x) = Cs + (R/2D)(x^2 - 2Lx), anoxia when L > Lc = sqrt(2 D Cs / R)
"""
L = p["L"] if L is None else L
rho = p["rho_cell"] if rho is None else rho
R = rho * p["q_O2"] # mol m^-3 s^-1
Lc = np.sqrt(2.0 * D_O2 * C_O2_SURF / np.maximum(R, 1e-30))
return np.minimum(1.0, Lc / L), Lc
def mechanics(phi, M, p):
"""Drying stiffens and strengthens but embrittles. Both must pass."""
UTS = p["UTS_ref"] * M * (1.0 + 2.0 * (1.0 - phi)) # MPa
eps_f = p["eps_f0"] * M * np.sqrt(np.clip(phi, 0, 1)) # -
return UTS, eps_f
def sensing_snr(phi, Cv, F, p, actuating=True):
amp = np.clip(Cv, 1e-6, 1) * np.clip(phi, 1e-6, 1) * np.clip(F, 1e-6, 1)
snr = p["SNR0_dB"] + 20.0 * np.log10(amp)
if actuating:
snr = snr - p["actuation_penalty_dB"]
return snr
# ----------------------------------------------------------------------
# Integrator
# ----------------------------------------------------------------------
def simulate(p, days=60.0, dt=0.02, n=1, damage_day=None, record=False):
"""Vectorised forward Euler. Arrays in p may be shape (n,) to sweep."""
nsteps = int(days / dt)
W_max = p["L"] * RHO_W * p["eps_w"] # kg m^-2
def arr(x):
return np.broadcast_to(np.asarray(x, dtype=float), (n,)).copy()
W = arr(W_max)
G = arr(p["G0"])
Cv = arr(1.0)
M = arr(1.0)
B = arr(0.001)
F = arr(1.0)
Rp = arr(1.0) # 1 = intact; drops to 0 at the damage event
damaged = arr(0.0)
fO2, Lc = o2_viable_fraction(p)
fO2 = np.broadcast_to(np.asarray(fO2, dtype=float), (n,)).copy()
hist = {k: [] for k in
("t", "phi", "Cv", "M", "B", "UTS", "eps_f", "SNR", "G", "Rp")}
for i in range(nsteps):
t = i * dt
phi = np.clip(W / W_max, 0.0, 1.0)
# --- water ---
dW = -evap_flux(phi, p) + p["J_water"]
# --- nutrients ---
dG = -p["g_use"] * Cv + p["J_nut"]
# --- stressors ---
s_des = p["k_des"] / (1.0 + np.exp((phi - p["phi_crit"]) / p["w_des"]))
if p["dormant"]:
s_des = s_des * 0.15 # dormancy: drying suspends, not kills
s_anox = p["k_anox"] * (1.0 - fO2)
s_starve = p["k_starve"] * (G <= 0.0)
s_contam = p["k_contam_kill"] * B
healthy = (phi > p["phi_crit"]) & (G > 0.0)
dCv = (-(s_des + s_anox + s_starve + s_contam) * Cv
+ p["k_prolif"] * Cv * (1.0 - Cv) * healthy)
# --- contamination feeds on necrosis, is suppressed by healthy tissue ---
necrotic = np.clip(1.0 - Cv, 0.0, 1.0)
dB = (p["k_contam"] * B * (0.15 + necrotic) * (1.0 - B)
- p["k_antag"] * B * Cv)
# --- matrix ---
dM = p["k_syn"] * Cv * (1.0 - M) - p["k_deg"] * (0.6 * necrotic + B) * M
# --- engineered function decay ---
dF = -np.log(2.0) / p["tau_gen"] * F
# --- damage / repair ---
if damage_day is not None and abs(t - damage_day) < dt / 2:
Rp = arr(0.0)
damaged = arr(1.0)
dRp = p["k_rep"] * Cv * np.clip(phi, 0, 1) * (G > 0.0) * (1.0 - Rp)
W = np.maximum(W + dW * dt, 0.0)
G = np.maximum(G + dG * dt, 0.0)
Cv = np.clip(Cv + dCv * dt, 0.0, 1.0)
B = np.clip(B + dB * dt, 0.0, 1.0)
M = np.clip(M + dM * dt, 0.0, 1.0)
F = np.clip(F + dF * dt, 0.0, 1.0)
Rp = np.clip(Rp + dRp * dt, 0.0, 1.0)
if record:
phin = np.clip(W / W_max, 0, 1)
UTS, eps_f = mechanics(phin, M * (0.25 + 0.75 * Rp), p)
hist["t"].append(t)
hist["phi"].append(phin.copy())
hist["Cv"].append(Cv.copy())
hist["M"].append(M.copy())
hist["B"].append(B.copy())
hist["G"].append(G.copy())
hist["Rp"].append(Rp.copy())
hist["UTS"].append(UTS.copy())
hist["eps_f"].append(eps_f.copy())
hist["SNR"].append(sensing_snr(phin, Cv, F, p).copy())
phi = np.clip(W / W_max, 0.0, 1.0)
Meff = M * (0.25 + 0.75 * Rp)
UTS, eps_f = mechanics(phi, Meff, p)
out = dict(phi=phi, Cv=Cv, M=M, B=B, G=G, Rp=Rp, F=F,
fO2=fO2, Lc=Lc, UTS=UTS, eps_f=eps_f,
SNR=sensing_snr(phi, Cv, F, p))
if record:
out["hist"] = {k: (np.array(v) if k == "t" else np.array(v))
for k, v in hist.items()}
return out
# ----------------------------------------------------------------------
# Acceptance tests (the specification, S1/S4/S5b/S6 from the review)
# ----------------------------------------------------------------------
THRESH = dict(phi=0.50, fO2=0.90, UTS=1.0, eps_f=0.30, repair=0.50, SNR=6.0)
def acceptance(p, service_days=30.0, repair_window=7.0):
"""Run to the service interval, damage, then run the repair window.
Returns the five test metrics and their booleans."""
base = simulate(p, days=service_days, dt=0.02, n=1)
# repair test: damage at the end of service, watch for repair_window
p2 = dict(p)
rep = simulate(p2, days=service_days + repair_window, dt=0.02, n=1,
damage_day=service_days)
metrics = dict(
T1_hydration=float(base["phi"][0]),
T2_oxygen=float(np.atleast_1d(base["fO2"])[0]),
T3a_UTS=float(base["UTS"][0]),
T3b_eps_f=float(base["eps_f"][0]),
T4_repair=float(rep["Rp"][0]),
T5_SNR=float(base["SNR"][0]),
)
passed = dict(
T1=metrics["T1_hydration"] >= THRESH["phi"],
T2=metrics["T2_oxygen"] >= THRESH["fO2"],
T3=(metrics["T3a_UTS"] >= THRESH["UTS"]) and
(metrics["T3b_eps_f"] >= THRESH["eps_f"]),
T4=metrics["T4_repair"] >= THRESH["repair"],
T5=metrics["T5_SNR"] >= THRESH["SNR"],
)
return metrics, passed
def time_to_failure(p, days=90.0, dt=0.05, n=1):
"""First day on which ANY acceptance criterion is violated."""
nsteps = int(days / dt)
W_max = p["L"] * RHO_W * p["eps_w"]
W = np.full(n, W_max)
G = np.full(n, float(np.asarray(p["G0"]).ravel()[0])
if np.isscalar(p["G0"]) else 0.0)
G = np.broadcast_to(np.asarray(p["G0"], dtype=float), (n,)).copy()
Cv = np.ones(n); M = np.ones(n); B = np.full(n, 0.001); F = np.ones(n)
fO2, _ = o2_viable_fraction(p)
fO2 = np.broadcast_to(np.asarray(fO2, dtype=float), (n,)).copy()
ttf = np.full(n, np.nan)
for i in range(nsteps):
t = i * dt
phi = np.clip(W / W_max, 0, 1)
UTS, eps_f = mechanics(phi, M, p)
snr = sensing_snr(phi, Cv, F, p)
bad = ((phi < THRESH["phi"]) | (fO2 < THRESH["fO2"]) |
(UTS < THRESH["UTS"]) | (eps_f < THRESH["eps_f"]) |
(snr < THRESH["SNR"]))
newly = bad & np.isnan(ttf)
ttf[newly] = t
s_des = p["k_des"] / (1.0 + np.exp((phi - p["phi_crit"]) / p["w_des"]))
if p["dormant"]:
s_des = s_des * 0.15
healthy = (phi > p["phi_crit"]) & (G > 0)
necrotic = np.clip(1 - Cv, 0, 1)
W = np.maximum(W + (-evap_flux(phi, p) + p["J_water"]) * dt, 0)
G = np.maximum(G + (-p["g_use"] * Cv + p["J_nut"]) * dt, 0)
Cv = np.clip(Cv + (-(s_des + p["k_anox"] * (1 - fO2)
+ p["k_starve"] * (G <= 0)
+ p["k_contam_kill"] * B) * Cv
+ p["k_prolif"] * Cv * (1 - Cv) * healthy) * dt, 0, 1)
B = np.clip(B + (p["k_contam"] * B * (0.15 + necrotic) * (1 - B)
- p["k_antag"] * B * Cv) * dt, 0, 1)
M = np.clip(M + (p["k_syn"] * Cv * (1 - M)
- p["k_deg"] * (0.6 * necrotic + B) * M) * dt, 0, 1)
F = np.clip(F - np.log(2) / p["tau_gen"] * F * dt, 0, 1)
ttf[np.isnan(ttf)] = days
return ttf
# ----------------------------------------------------------------------
# Photosynthetic nutrient closure
# ----------------------------------------------------------------------
def photosynthetic_supply(lux, eta=1.0, quantum_req=10.0, ppfd_per_lux=0.0135):
"""g m^-2 d^-1 glucose-equivalent (as CH2O, MW 30) fixed by a
photosynthetic co-resident layer.
ppfd_per_lux ~ 0.0135 umol m^-2 s^-1 per lux for white LED / daylight mix.
quantum_req ~ 8-12 photons per CO2 fixed (theoretical ~8).
eta = realised fraction of the theoretical yield (light capture, respiration,
non-photochemical quenching, layer optical density).
"""
ppfd = lux * ppfd_per_lux # umol photons m^-2 s^-1
co2 = ppfd / quantum_req * eta # umol CO2 m^-2 s^-1
mol_per_day = co2 * 1e-6 * 86400.0 # mol m^-2 d^-1
return mol_per_day * 30.0 # g CH2O m^-2 d^-1
"""
bandwidth.py -- Monte-Carlo test of the review's central conjecture (Claim 9):
a stratified skin (fast synthetic sensing layer + slow living outer layer)
dominates either layer alone on a mixed duty cycle.
Three architectures, three task families, 180-day mission with stochastic
damage and chemical-hazard events. Outputs the three numbers the physical
three-arm experiment in the verification loop is designed to measure:
(i) fast-task success rate
(ii) chemical-detection F1 under interferent load
(iii) operational availability and cumulative intervention hours
"""
import numpy as np
RNG = np.random.default_rng(20260829)
# ----------------------------------------------------------------------
# Channel characteristics (literature order-of-magnitude)
# ----------------------------------------------------------------------
CHANNELS = {
"synthetic": dict(
latency_ms=3.0, latency_cv=0.35, # taxel -> controller
p_detect_slip=0.97,
chem_latency_s=5.0,
chem_sensitivity=0.92, # broadband chemiresistor
chem_specificity=0.55, # cross-sensitive
autonomous_repair=False,
mttr_h=26.0, # wait for operator + swap
intervention_h=0.5,
degraded_capability=0.0, # dead until swapped
),
"living": dict(
latency_ms=3000.0, latency_cv=0.60, # hyphal spiking
p_detect_slip=0.35,
chem_latency_s=120.0,
chem_sensitivity=0.88,
chem_specificity=0.96, # genetically encoded receptor
autonomous_repair=True,
mttr_h=5 * 24.0, # autonomous, but slow
intervention_h=0.0,
degraded_capability=0.55, # keeps working while healing
),
}
CHANNELS["stratified"] = dict(
latency_ms=CHANNELS["synthetic"]["latency_ms"],
latency_cv=CHANNELS["synthetic"]["latency_cv"],
p_detect_slip=CHANNELS["synthetic"]["p_detect_slip"],
chem_latency_s=CHANNELS["living"]["chem_latency_s"],
chem_sensitivity=CHANNELS["living"]["chem_sensitivity"],
chem_specificity=CHANNELS["living"]["chem_specificity"],
autonomous_repair=True,
mttr_h=5 * 24.0,
intervention_h=0.0,
degraded_capability=0.80, # synthetic layer intact under a healing outer layer
)
SLIP_DEADLINE_MS = 20.0
CHEM_DEADLINE_S = 300.0
# ----------------------------------------------------------------------
def fast_task(arch, n=20000, rng=RNG):
c = CHANNELS[arch]
mu = np.log(c["latency_ms"]) - 0.5 * np.log(1 + c["latency_cv"] ** 2)
sig = np.sqrt(np.log(1 + c["latency_cv"] ** 2))
lat = rng.lognormal(mu, sig, n)
detected = rng.random(n) < c["p_detect_slip"]
return float(np.mean(detected & (lat < SLIP_DEADLINE_MS))), lat
def chem_task(arch, n=20000, prevalence=0.25, rng=RNG):
"""F1 for a target analyte under a load of chemically similar interferents,
with a hard deadline on time-to-alarm."""
c = CHANNELS[arch]
truth = rng.random(n) < prevalence
in_time = c["chem_latency_s"] < CHEM_DEADLINE_S
tp = truth & (rng.random(n) < c["chem_sensitivity"]) & in_time
fp = (~truth) & (rng.random(n) > c["chem_specificity"]) & in_time
fn = truth & ~tp
prec = tp.sum() / max(tp.sum() + fp.sum(), 1)
rec = tp.sum() / max(tp.sum() + fn.sum(), 1)
f1 = 0.0 if (prec + rec) == 0 else 2 * prec * rec / (prec + rec)
return float(f1), float(prec), float(rec)
def mission(arch, days=180, dmg_rate_per_day=1 / 14, n_runs=400, rng=RNG):
"""Availability-weighted capability and intervention hours."""
c = CHANNELS[arch]
avail_curves = np.zeros((n_runs, days))
interventions = np.zeros(n_runs)
for r in range(n_runs):
cap = np.ones(days)
t = 0.0
while t < days:
t += rng.exponential(1 / dmg_rate_per_day)
if t >= days:
break
d0 = int(t)
ttr_d = rng.gamma(4.0, (c["mttr_h"] / 24.0) / 4.0)
d1 = min(days, int(t + ttr_d) + 1)
if c["autonomous_repair"]:
ramp = np.linspace(c["degraded_capability"], 1.0, max(d1 - d0, 1))
cap[d0:d1] = np.minimum(cap[d0:d1], ramp)
else:
cap[d0:d1] = 0.0
interventions[r] += c["intervention_h"]
avail_curves[r] = cap
return avail_curves, interventions
"""Generate the six result figures for the ELM robot-skin simulation."""
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
from matplotlib.patches import Rectangle
import elmskin as E
import bandwidth as BW
OUT = "/mnt/user-data/outputs"
plt.rcParams.update({
"figure.dpi": 150, "savefig.dpi": 160, "font.size": 9,
"axes.grid": True, "grid.alpha": 0.25, "grid.linewidth": 0.6,
"axes.spines.top": False, "axes.spines.right": False,
"axes.titlesize": 10, "axes.titleweight": "bold",
"legend.frameon": False, "figure.facecolor": "white",
})
C = dict(water="#2a6f9e", nut="#b8860b", o2="#4a8a54", mech="#8b3a62",
sens="#5b4b8a", fail="#c0392b", ok="#2e7d4f", grey="#6b6b6b")
BARRER = 3.35e-16 # mol m / (m2 s Pa)
DP_TEST = 5.9 # kPa driving force in the 38C/90%RH WVTR test
DP_OP = (E.water_activity(1.0) - 0.5) * E.P_SAT_25C # kPa at 25 C / 50 % RH
# ======================================================================
# FIG 1 -- The life-support budget and its two-regime structure
# ======================================================================
def fig1():
fig, ax = plt.subplots(1, 3, figsize=(13.2, 3.9))
p = E.default_params("H_hybrid")
# (a) daily fluxes per m2
water = float(E.evap_flux(np.array([1.0]), p)[0]) * 1000 # g/m2/d
glucose = p["g_use"] # g/m2/d
o2_mol = p["rho_cell"] * p["q_O2"] * p["L"] * 86400
o2_g = o2_mol * 32.0
heat_mW = o2_mol / 6.0 * E.DH_GLUCOSE / 86400 * 1000
names = ["Water\n(evaporative)", "Glucose-equiv\n(metabolic)",
"Oxygen\n(metabolic)"]
vals = [water, glucose, o2_g]
cols = [C["water"], C["nut"], C["o2"]]
b = ax[0].bar(names, vals, color=cols, width=0.6)
ax[0].set_yscale("log")
ax[0].set_ylabel("consumption [g m$^{-2}$ d$^{-1}$]")
ax[0].set_title("(a) Daily budget per m$^2$ of living skin")
for r, v in zip(b, vals):
ax[0].text(r.get_x() + r.get_width() / 2, v * 1.35,
f"{v:,.3g}", ha="center", fontsize=8.5)
ax[0].set_ylim(1e-2, 1e5)
ax[0].text(0.03, 0.93,
f"water : nutrient = {water/glucose:,.0f} : 1\n"
f"metabolic heat = {heat_mW:.0f} mW m$^{{-2}}$",
transform=ax[0].transAxes, fontsize=8.5, va="top",
bbox=dict(fc="#f3f3f3", ec="none", pad=4))
# (b) time to first failure vs barrier permeance
kb = np.logspace(-2, 2, 90)
for jn, ls, lab in [(0.0, "--", "stored nutrient only"),
(0.6, "-", "closed nutrient supply\n(0.6 g m$^{-2}$ d$^{-1}$)")]:
ttf = np.array([float(E.time_to_failure(
dict(p, k_b=k, J_nut=jn), days=120)[0]) for k in kb])
ax[1].loglog(kb, np.maximum(ttf, 1e-2), ls, color=C["water"], lw=2,
label=lab)
ax[1].axhline(30, color=C["fail"], lw=1.2, ls=":")
ax[1].text(0.012, 33, "30-day unattended target (S6)", color=C["fail"],
fontsize=8)
for k, lab in [(31.5, "skin equivalent\n(no barrier)"), (4.7, "native\nhuman skin")]:
ax[1].axvline(k, color=C["grey"], lw=0.9, ls="-.")
ax[1].text(k * 1.08, 0.08, lab, rotation=90, fontsize=7.5,
color=C["grey"], va="bottom")
ax[1].axvspan(5, 100, color=C["water"], alpha=0.07)
ax[1].axvspan(0.01, 5, color=C["nut"], alpha=0.07)
ax[1].text(20, 0.02, "water-limited", fontsize=8, color=C["water"], ha="center")
ax[1].text(0.1, 0.02, "nutrient-limited", fontsize=8, color=C["nut"], ha="center")
ax[1].set_xlabel("barrier water-vapour permeance $k_b$ [g m$^{-2}$ h$^{-1}$ kPa$^{-1}$]")
ax[1].set_ylabel("time to first spec violation [d]")
ax[1].set_title("(b) Two limiting regimes, and their crossover")
ax[1].legend(loc="upper right", fontsize=8)
ax[1].set_ylim(1e-2, 2e2)
# (c) photosynthetic closure
lux = np.logspace(2, 5.1, 200)
ax[2].loglog(lux, E.photosynthetic_supply(lux, eta=1.0), color=C["o2"],
lw=1.2, ls=":", label="theoretical max ($\\eta$ = 1)")
ax[2].loglog(lux, E.photosynthetic_supply(lux, eta=0.30), color=C["o2"],
lw=2, label="optimistic layer ($\\eta$ = 0.30)")
ax[2].loglog(lux, E.photosynthetic_supply(lux, eta=0.10), color=C["o2"],
lw=2, ls="--", label="realistic layer ($\\eta$ = 0.10)")
ax[2].axhline(p["g_use"], color=C["nut"], lw=1.5)
ax[2].text(120, p["g_use"] * 1.2, "heterotrophic demand 0.5 g m$^{-2}$ d$^{-1}$",
color=C["nut"], fontsize=8)
for lo, hi, lab in [(100, 500, "corridor"), (500, 1000, "office"),
(1000, 5000, "near window"), (10000, 100000, "outdoor")]:
ax[2].axvspan(lo, hi, color=C["grey"], alpha=0.06)
ax[2].text(np.sqrt(lo * hi), 3e2, lab, fontsize=7.5, ha="center",
color=C["grey"], rotation=90, va="top")
ax[2].set_xlabel("illuminance [lux]")
ax[2].set_ylabel("fixed carbon [g CH$_2$O m$^{-2}$ d$^{-1}$]")
ax[2].set_title("(c) Can a photosynthetic layer close the loop?")
ax[2].legend(loc="lower right", fontsize=8)
ax[2].set_ylim(1e-3, 1e3)
fig.tight_layout()
fig.savefig(f"{OUT}/fig1_life_support_budget.png", bbox_inches="tight")
plt.close(fig)
return dict(water=water, glucose=glucose, o2=o2_g, heat_mW=heat_mW)
# ======================================================================
# FIG 2 -- Failure propagation from a single knob
# ======================================================================
def fig2():
p = E.default_params("H_hybrid", J_nut=0.6)
kb = np.logspace(-1.4, 1.4, 40)
rows = []
for k in kb:
m, ok = E.acceptance(dict(p, k_b=k))
rows.append((m, ok))
M = {key: np.array([r[0][key] for r in rows]) for key in rows[0][0]}
P = {key: np.array([r[1][key] for r in rows]) for key in rows[0][1]}
fig = plt.figure(figsize=(13.2, 6.4))
gs = fig.add_gridspec(3, 3, height_ratios=[1, 1, 0.42], hspace=0.55,
wspace=0.3)
panels = [
("T1 hydration $\\varphi$ at day 30", M["T1_hydration"],
E.THRESH["phi"], C["water"], (0, 1.05), None),
("T3a ultimate tensile strength", M["T3a_UTS"],
E.THRESH["UTS"], C["mech"], None, "log"),
("T3b strain at failure $\\epsilon_f$", M["T3b_eps_f"],
E.THRESH["eps_f"], C["mech"], (0, 0.6), None),
("T4 repair after damage (7 d)", M["T4_repair"],
E.THRESH["repair"], C["ok"], (0, 1.05), None),
("T5 living-channel SNR under actuation", M["T5_SNR"],
E.THRESH["SNR"], C["sens"], (-40, 25), None),
]
axes = [fig.add_subplot(gs[i // 3, i % 3]) for i in range(5)]
for ax, (title, y, thr, col, ylim, scale) in zip(axes, panels):
ax.semilogx(kb, y, color=col, lw=2)
ax.axhline(thr, color=C["fail"], ls=":", lw=1.2)
ax.axvline(0.3, color=C["grey"], lw=0.9, ls="-.")
if scale == "log":
ax.set_yscale("log")
if ylim:
ax.set_ylim(*ylim)
ax.set_title(title, fontsize=9)
ax.set_xlabel("$k_b$ [g m$^{-2}$ h$^{-1}$ kPa$^{-1}$]", fontsize=8)
# T2 is knob-independent -- say so explicitly
ax6 = fig.add_subplot(gs[1, 2])
ax6.semilogx(kb, M["T2_oxygen"], color=C["o2"], lw=2)
ax6.axhline(E.THRESH["fO2"], color=C["fail"], ls=":", lw=1.2)
ax6.set_ylim(0, 1.1)
ax6.set_title("T2 oxygen viable fraction (uncoupled)", fontsize=9)
ax6.set_xlabel("$k_b$ [g m$^{-2}$ h$^{-1}$ kPa$^{-1}$]", fontsize=8)
ax6.text(0.5, 0.35, "independent of the barrier knob:\nO$_2$ is set by "
"thickness $\\times$ cell density\n(see Fig. 3b)",
transform=ax6.transAxes, ha="center", fontsize=8, color=C["grey"])
# pass/fail strip
axs = fig.add_subplot(gs[2, :])
keys = ["T1", "T2", "T3", "T4", "T5"]
grid = np.array([[1 if P[k][i] else 0 for i in range(len(kb))] for k in keys])
axs.imshow(grid, aspect="auto", cmap=matplotlib.colors.ListedColormap(
[C["fail"], C["ok"]]), extent=[0, len(kb), 0, 5], vmin=0, vmax=1)
axs.set_yticks(np.arange(5) + 0.5)
axs.set_yticklabels(keys[::-1])
tick_idx = np.linspace(0, len(kb) - 1, 7).astype(int)
axs.set_xticks(tick_idx + 0.5)
axs.set_xticklabels([f"{kb[i]:.2g}" for i in tick_idx])
axs.set_xlabel("barrier water-vapour permeance $k_b$ "
"[g m$^{-2}$ h$^{-1}$ kPa$^{-1}$] $\\longrightarrow$ worse barrier")
axs.set_title("Acceptance verdict — four of five tests break together "
"at the same knob setting", fontsize=9.5)
axs.grid(False)
fig.suptitle("Failure propagation: turning ONE physical knob (barrier permeance)",
fontsize=11.5, fontweight="bold", y=0.99)
fig.savefig(f"{OUT}/fig2_failure_propagation.png", bbox_inches="tight")
plt.close(fig)
# locate the simultaneous breakpoints
bps = {}
for k in keys:
arr = P[k]
idx = np.where(~arr)[0]
bps[k] = float(kb[idx[0]]) if len(idx) and arr[0] else None
return bps
# ======================================================================
# FIG 3 -- Feasibility phase maps
# ======================================================================
def fig3():
p = E.default_params("H_hybrid")
fig, ax = plt.subplots(1, 2, figsize=(12.4, 4.6))
# (a) barrier x nutrient supply -> time to failure
kbv = np.logspace(-2.0, 1.7, 50)
jnv = np.linspace(0.0, 0.9, 40)
Z = np.zeros((len(jnv), len(kbv)))
for i, jn in enumerate(jnv):
for j, k in enumerate(kbv):
Z[i, j] = E.time_to_failure(dict(p, k_b=k, J_nut=jn), days=120,
dt=0.1)[0]
im = ax[0].pcolormesh(kbv, jnv, np.maximum(Z, 0.2), norm=LogNorm(0.2, 120),
cmap="YlGnBu", shading="auto")
cs = ax[0].contour(kbv, jnv, Z, levels=[7, 30, 90], colors="k",
linewidths=[0.8, 2.0, 0.8])
ax[0].clabel(cs, fmt={7: "7 d", 30: "30 d", 90: "90 d"}, fontsize=8)
ax[0].set_xscale("log")
ax[0].axhline(p["g_use"], color="w", ls="--", lw=1)
ax[0].text(0.04, p["g_use"] + 0.02, "supply = demand", color="w", fontsize=8)
for k, lab in [(31.5, "skin equiv."), (4.7, "human skin"),
(0.30, "LDPE 25 µm"), (0.021, "butyl 25 µm")]:
ax[0].axvline(k, color="k", lw=0.7, ls=":", alpha=0.6)
ax[0].text(k, 0.92, lab, rotation=90, fontsize=7, ha="right", va="top")
ax[0].set_xlabel("barrier permeance $k_b$ [g m$^{-2}$ h$^{-1}$ kPa$^{-1}$]")
ax[0].set_ylabel("nutrient supply [g m$^{-2}$ d$^{-1}$]")
ax[0].set_title("(a) Feasible operating region for 30-day autonomy")
fig.colorbar(im, ax=ax[0], label="time to first spec violation [d]")
# (b) thickness x cell density -> O2 viable fraction
Lv = np.logspace(np.log10(20e-6), np.log10(5e-3), 160)
rv = np.logspace(11, 15.5, 160)
LL, RR = np.meshgrid(Lv, rv)
fO2, Lc = E.o2_viable_fraction(p, L=LL, rho=RR)
im2 = ax[1].pcolormesh(LL * 1e6, RR / 1e6, fO2, cmap="RdYlGn",
vmin=0, vmax=1, shading="auto")
cs2 = ax[1].contour(LL * 1e6, RR / 1e6, fO2, levels=[0.9], colors="k",
linewidths=2)
ax[1].clabel(cs2, fmt={0.9: "T2 threshold (90 %)"}, fontsize=8,
manual=[(700, 2.5e7)])
ax[1].set_xscale("log"); ax[1].set_yscale("log")
ax[1].plot(2000, 1e12 / 1e6, "o", ms=9, mfc="w", mec="k", mew=1.6)
ax[1].annotate("published dermis equivalents\n(2 mm, 10$^6$ cells cm$^{-3}$)\n"
"— NOT O$_2$-limited",
xy=(2000, 1e6), xytext=(30, 1.6e6), fontsize=8,
arrowprops=dict(arrowstyle="->", lw=1),
bbox=dict(fc="white", ec="none", alpha=0.75, pad=2))
ax[1].plot(2000, 1e8, "s", ms=8, mfc="w", mec="k", mew=1.6)
ax[1].annotate("native dermal density\n— requires vasculature",
xy=(2000, 1e8), xytext=(40, 1.1e9), fontsize=8,
arrowprops=dict(arrowstyle="->", lw=1),
bbox=dict(fc="white", ec="none", alpha=0.75, pad=2))
ax[1].axvline(200, color="k", ls=":", lw=1)
ax[1].text(210, 1.3e5, "the '200 µm rule'", rotation=90, fontsize=7.5)
ax[1].set_xlabel("living-layer thickness [µm]")
ax[1].set_ylabel("cell density [cells cm$^{-3}$]")
ax[1].set_title("(b) Oxygen constraint is set by density, not thickness alone")
fig.colorbar(im2, ax=ax[1], label="viable depth fraction")
fig.tight_layout()
fig.savefig(f"{OUT}/fig3_phase_maps.png", bbox_inches="tight")
plt.close(fig)
# ======================================================================
# FIG 4 -- Coupled trajectories, pass vs fail, with a damage event
# ======================================================================
def fig4():
p = E.default_params("H_hybrid", J_nut=0.6)
cases = [("passing design ($k_b$ = 0.3)", dict(p, k_b=0.30), C["ok"]),
("failing design ($k_b$ = 3.0)", dict(p, k_b=3.0), C["fail"])]
fig, ax = plt.subplots(2, 3, figsize=(13.2, 6.0), sharex=True)
ax = ax.ravel()
for lab, pp, col in cases:
r = E.simulate(pp, days=37, dt=0.01, n=1, damage_day=30, record=True)
h = r["hist"]; t = h["t"]
series = [("hydration $\\varphi$", h["phi"][:, 0], E.THRESH["phi"], None),
("viable cell fraction $C_v$", h["Cv"][:, 0], None, None),
("contaminant load $B$", h["B"][:, 0], None, None),
("UTS [MPa]", h["UTS"][:, 0], E.THRESH["UTS"], "log"),
("strain at failure $\\epsilon_f$", h["eps_f"][:, 0],
E.THRESH["eps_f"], None),
("living-channel SNR [dB]", h["SNR"][:, 0], E.THRESH["SNR"], None)]
for a, (title, y, thr, sc) in zip(ax, series):
a.plot(t, np.maximum(y, 1e-8) if sc == "log" else y, color=col,
lw=1.9, label=lab)
a.set_title(title, fontsize=9)
if thr is not None:
a.axhline(thr, color="k", ls=":", lw=1)
if sc == "log":
a.set_yscale("log"); a.set_ylim(1e-4, 20)
for a in ax:
a.axvline(30, color=C["grey"], lw=1.2, ls="--")
a.axvspan(30, 37, color=C["grey"], alpha=0.08)
ax[0].text(30.2, 0.05, "full-thickness cut", fontsize=7.5, rotation=90,
color=C["grey"])
for a in ax[3:]:
a.set_xlabel("time [d]")
ax[0].legend(loc="lower left", fontsize=8)
fig.suptitle("Coupled trajectories: hydration loss propagates into viability, "
"matrix, mechanics, repair and sensing",
fontsize=11.5, fontweight="bold")
fig.tight_layout()
fig.savefig(f"{OUT}/fig4_trajectories.png", bbox_inches="tight")
plt.close(fig)
# ======================================================================
# FIG 5 -- The encapsulant selectivity problem
# ======================================================================
MATS = [
# name, O2 permeability [Barrer], WVTR [g/m2/d @25um,38C/90%], E [MPa]
("PDMS (silicone)", 600, 5000, 2),
("natural rubber", 24, 200, 2),
("butyl rubber", 1.3, 3, 2),
("breathable PU", 3.0, 2000, 20),
("LDPE", 2.9, 15, 200),
("PP", 1.7, 7, 1500),
("PET", 0.35, 20, 3000),
("PVDC", 0.053, 1.5, 500),
("EVOH", 0.006, 60, 2500),
]
def fig5():
p = E.default_params("H_hybrid")
req_perm = p["rho_cell"] * p["q_O2"] * p["L"] / 20e3 # mol m-2 s-1 Pa-1
kb_req = 0.30
fig, ax = plt.subplots(figsize=(7.6, 5.6))
for name, P_b, wvtr, Emod in MATS:
perm = P_b * BARRER / 25e-6
kb = wvtr / (24 * DP_TEST)
col = plt.cm.coolwarm(np.clip(np.log10(Emod / 1.0) / 3.5, 0, 1))
ax.scatter(kb, perm, s=190, c=[col], edgecolor="k", zorder=3, lw=0.8)
ax.annotate(name, (kb, perm), textcoords="offset points",
xytext=(0, 13), ha="center", fontsize=8)
ax.add_patch(Rectangle((1e-4, req_perm), kb_req - 1e-4, 1e-7,
fc=C["ok"], alpha=0.13, ec=C["ok"], lw=1.5, zorder=1))
ax.text(3e-4, 8e-10, "FEASIBLE\n(barrier + O$_2$)", color=C["ok"],
fontsize=10.5, fontweight="bold")
ax.axhline(req_perm, color=C["o2"], ls=":", lw=1.4)
ax.axvline(kb_req, color=C["water"], ls=":", lw=1.4)
ax.text(1.2e-4, req_perm * 0.55, "min O$_2$ permeance for the living layer",
fontsize=7.8, color=C["o2"])
ax.text(kb_req * 1.12, 2e-14, "max $k_b$ for 30-day autonomy",
rotation=90, fontsize=7.8, color=C["water"], va="bottom")
ax.set_xscale("log"); ax.set_yscale("log")
ax.set_xlabel("water-vapour permeance $k_b$ [g m$^{-2}$ h$^{-1}$ kPa$^{-1}$]")
ax.set_ylabel("O$_2$ permeance [mol m$^{-2}$ s$^{-1}$ Pa$^{-1}$] (25 µm film)")
ax.set_title("The encapsulant must be selective: hold water in, let oxygen through")
sm = plt.cm.ScalarMappable(cmap="coolwarm",
norm=matplotlib.colors.LogNorm(1, 3000))
fig.colorbar(sm, ax=ax, label="Young's modulus [MPa] (compliance requirement)")
ax.set_xlim(1e-4, 1e2); ax.set_ylim(1e-14, 1e-7)
ax.text(0.98, 0.03,
"Only butyl rubber satisfies barrier + O$_2$ + compliance —\n"
"and its O$_2$ margin is ~1.7$\\times$, so it fails if cell density doubles.",
transform=ax.transAxes, ha="right", fontsize=8,
bbox=dict(fc="#fff6e5", ec="#d9b45b", pad=5))
fig.tight_layout()
fig.savefig(f"{OUT}/fig5_encapsulant_selectivity.png", bbox_inches="tight")
plt.close(fig)
return req_perm
# ======================================================================
# FIG 6 -- The bandwidth thesis
# ======================================================================
def fig6():
archs = ["synthetic", "living", "stratified"]
cols = {"synthetic": "#2a6f9e", "living": "#4a8a54", "stratified": "#8b3a62"}
fig, ax = plt.subplots(2, 2, figsize=(12.4, 7.4))
# (a) latency-specificity plane
a = ax[0, 0]
for k in archs:
c = BW.CHANNELS[k]
a.scatter(c["latency_ms"], c["chem_specificity"], s=230,
color=cols[k], edgecolor="k", zorder=3, lw=0.8)
dy = -24 if k == "stratified" else 16
a.annotate(k, (c["latency_ms"], c["chem_specificity"]),
textcoords="offset points", xytext=(0, dy), ha="center",
fontsize=9)
a.axvline(BW.SLIP_DEADLINE_MS, color=C["fail"], ls=":", lw=1.4)
a.text(BW.SLIP_DEADLINE_MS * 1.15, 0.4, "20 ms slip deadline", rotation=90,
fontsize=8, color=C["fail"])
a.axhline(0.9, color=C["nut"], ls=":", lw=1.4)
a.text(1.3, 0.915, "chemical specificity floor", fontsize=8, color=C["nut"])
a.add_patch(Rectangle((1, 0.9), BW.SLIP_DEADLINE_MS - 1, 0.12,
fc=C["ok"], alpha=0.13, ec=C["ok"], lw=1.4))
a.text(11.5, 0.925, "both\nrequirements", fontsize=8, color=C["ok"],
fontweight="bold", ha="right")
a.set_xscale("log"); a.set_xlim(1, 1e4); a.set_ylim(0.4, 1.03)
a.set_xlabel("sensing latency [ms]")
a.set_ylabel("chemical specificity [1 − FPR]")
a.set_title("(a) No single layer occupies both corners")
# (b) task success
b = ax[0, 1]
fast = {k: BW.fast_task(k)[0] for k in archs}
chem = {k: BW.chem_task(k)[0] for k in archs}
dmg = {}
curves, interv = {}, {}
for k in archs:
cv, iv = BW.mission(k)
curves[k], interv[k] = cv, iv
dmg[k] = float(cv.mean())
x = np.arange(3); w = 0.26
for i, k in enumerate(archs):
b.bar(x + (i - 1) * w, [fast[k], chem[k], dmg[k]], w, color=cols[k],
label=k, edgecolor="k", lw=0.5)
b.set_xticks(x)
b.set_xticklabels(["fast task\n(slip, 20 ms)", "chemical task\n(F1, interferents)",
"damage task\n(mean availability)"])
b.set_ylabel("score [0–1]")
b.set_ylim(0, 1.12)
b.legend(ncol=3, fontsize=8, loc="upper center")
b.set_title("(b) Task performance by architecture")
for i, k in enumerate(archs):
for j, v in enumerate([fast[k], chem[k], dmg[k]]):
b.text(j + (i - 1) * w, v + 0.02, f"{v:.2f}", ha="center", fontsize=7)
# (c) availability over 180 days
c = ax[1, 0]
for k in archs:
cv = curves[k]
m = cv.mean(0)
lo, hi = np.percentile(cv, [10, 90], axis=0)
c.plot(m, color=cols[k], lw=2, label=k)
c.fill_between(np.arange(len(m)), lo, hi, color=cols[k], alpha=0.13)
c.set_xlabel("mission day"); c.set_ylabel("capability fraction")
c.set_ylim(0, 1.05)
c.set_title("(c) Operational capability over a 180-day mission\n"
"(damage events, Poisson, mean 14 d)", fontsize=9.5)
c.legend(fontsize=8, loc="lower left")
# (d) intervention hours
d = ax[1, 1]
means = [interv[k].mean() for k in archs]
errs = [interv[k].std() for k in archs]
d.bar(archs, means, yerr=errs, capsize=5,
color=[cols[k] for k in archs], edgecolor="k", lw=0.5, width=0.55)
d.set_ylabel("cumulative human intervention [h / 180 d]")
d.set_title("(d) The cost the living layer is supposed to remove")
for i, v in enumerate(means):
d.text(i, v + 0.3, f"{v:.1f} h", ha="center", fontsize=9)
fig.suptitle("Testing Claim 9: does a stratified skin dominate either layer alone?",
fontsize=11.5, fontweight="bold")
fig.tight_layout()
fig.savefig(f"{OUT}/fig6_bandwidth_thesis.png", bbox_inches="tight")
plt.close(fig)
return fast, chem, dmg, {k: interv[k].mean() for k in archs}
if __name__ == "__main__":
import sys
which = sys.argv[1:] or ["1", "2", "3", "4", "5", "6"]
if "1" in which: print("FIG1 budget:", fig1())
if "2" in which: print("FIG2 breakpoints:", fig2())
if "3" in which: fig3(); print("FIG3 done")
if "4" in which: fig4(); print("FIG4 done")
if "5" in which: print("FIG5 req O2 permeance:", fig5())
if "6" in which: print("FIG6:", fig6())
# ======================================================================
# FIG 7 -- Sensitivity: which measurement actually needs precision
# ======================================================================
def fig7():
base = E.default_params("H_hybrid", k_b=0.30, J_nut=0.6)
f0 = float(E.time_to_failure(base, days=300, dt=0.05)[0])
keys = [("SNR0_dB", "living-channel SNR at t=0", C["sens"]),
("RH", "ambient relative humidity", C["water"]),
("k_b", "barrier permeance", C["water"]),
("L", "layer thickness", C["water"]),
("eps_w", "water volume fraction", C["water"]),
("tau_gen", "engineered-function half-life", C["sens"]),
("phi_crit", "critical hydration", C["o2"]),
("k_des", "desiccation death rate", C["o2"]),
("k_prolif", "proliferation rate", C["o2"]),
("g_use", "nutrient consumption", C["nut"]),
("rho_cell", "cell density", C["o2"]),
("k_rep", "repair rate", C["mech"]),
("UTS_ref", "reference UTS", C["mech"])]
res = []
for k, lab, col in keys:
lo = float(E.time_to_failure(dict(base, **{k: base[k] * 0.9}),
days=300, dt=0.05)[0])
hi = float(E.time_to_failure(dict(base, **{k: base[k] * 1.1}),
days=300, dt=0.05)[0])
res.append((lab, col, (hi - lo) / (0.2 * f0), lo, hi))
res.sort(key=lambda r: abs(r[2]))
fig, ax = plt.subplots(figsize=(8.4, 5.4))
y = np.arange(len(res))
for i, (lab, col, el, lo, hi) in enumerate(res):
ax.barh(i, hi - f0, left=f0, color=col, alpha=0.85, height=0.62,
edgecolor="k", lw=0.4)
ax.barh(i, lo - f0, left=f0, color=col, alpha=0.45, height=0.62,
edgecolor="k", lw=0.4)
ax.text(max(hi, lo) + 0.6, i, f"$\\eta$ = {el:+.2f}", va="center",
fontsize=7.8)
ax.axvline(f0, color="k", lw=1.2)
ax.set_yticks(y); ax.set_yticklabels([r[0] for r in res], fontsize=8.5)
ax.set_xlabel("time to first spec violation [d] "
"($\\pm$10 % on each parameter, others held at the passing design)")
ax.set_title("Which parameter must be measured precisely?\n"
"Sensitivity at the passing design point ($k_b$ = 0.30, "
"closed nutrient loop)", fontsize=10)
ax.text(0.99, 0.04,
"At the passing design point the binding subsystem has moved:\n"
"sensing (SNR, genetic drift) now limits service life, not water.",
transform=ax.transAxes, ha="right", fontsize=8.5,
bbox=dict(fc="#fff6e5", ec="#d9b45b", pad=5))
fig.tight_layout()
fig.savefig(f"{OUT}/fig7_sensitivity.png", bbox_inches="tight")
plt.close(fig)
return res