Jaw Lab
A carnivore feeding lab — steer your nose at live red prey and the jaw LUNGES, gapes, and crunches everything in its short front bite arc; see how none/poor/decent/great change reach, bite width, big-prey capacity, recovery, and energy per kill, and how nature skews the roll.
---
.. toc::
Hunt the red
The jaw is carnivore predation — it eats live prey only (the red cells), never plankton. Unlike the filter's passive reach-ring, the jaw is a directional melee strike you aim by steering: point your nose (the cell faces its heading) at a red prey cell, close to melee range, and the jaw auto-fires — the cell lunges forward, the mandibles gape, then crunch shut, popping every prey caught in the short front bite arc (a yellow burst marks each kill). Then a brief recovery locks the jaw before you can strike again, so a clean head-on line-up is rewarded and a sloppy angle whiffs into open water. Tap Space to force a strike for manual timing on a juking target.
The red wedge is your bite arc — bright when the jaw is ready, dimmed grey during recovery. Tiers escalate the predator: none can't bite at all; poor is short, narrow, slow to recover, its bites often slip off (wasted energy) and it can only take the smaller prey; decent reliably takes equal-or-smaller cells; great is fast, wide, handles larger prey, can crunch a small cluster at once, and pays out big. Green plankton is greyed out — useless to a carnivore. The right panel shows how this specimen's nature skews which feed part it would tend to mutate (cross-diet is rare but possible).
.. exec::docs.adaptation_jaw.adaptation_jaw :code: false
Source
```python
File: lib/adaptation_lab.py
"""Builder for the movement Adaptation Labs (one isolated, drivable petri dish per movement part). movement_lab(part_id) returns the page component and registers its (part-namespaced) clientside callbacks. Used by the thin docs/adaptation_<part> pages so flagellum/cilia/membrane share one implementation.
Each lab is a *limited* example — no food chain, just: pick a specimen + a tier (none/poor/decent/great), drive it (joystick / WASD), and FEEL the difference, with a path trail + live stats and a per-spawn luck roll. The same tier feels different per specimen because it modulates that specimen's movement archetype. """
import json
from dash import dcc, html, clientside_callback, Input, Output, State import dash_mantine_components as dmc from dash_iconify import DashIconify import dash_gauge as dg
from lib.adaptations import ADAPTATIONS, TIER_LABEL, TIER_COLOR from lib.movement import to_store as _movement_store, SPECIMENS, FEEL from lib.diet import (PART_DIET, SPECIMEN_DIET, DIET_LABEL, DIET_COLOR, DIET_EDIBLE, to_store as _diet_store)
ACCENT = "#3399ff" _ROLE_COLOR = {"speed": "blue", "sense": "grape", "defense": "orange", "attack": "red", "feed": "green", "grip": "teal"} _TIERS = ["none", "poor", "decent", "great"] _DEFAULT_SPEC = {"flagellum": "euglena", "cilia": "paramecium", "membrane": "celegans"}
def _tier_ref_card(part_id, v): """A static reference card: organelle schematic + feel line + stats.""" t = v["tier"] return dmc.Paper( [ dmc.Group([dmc.Badge(TIER_LABEL[t], color=TIER_COLOR[t], variant="light", size="sm"), dmc.Text(v["name"], fw=600, size="sm")], justify="space-between", wrap="nowrap"), html.Div(id={"type": "ml-card", "part": part_id, "tier": t}, className="petri-dish", style={"width": "100%", "aspectRatio": "1 / 1", "borderRadius": "10px", "overflow": "hidden", "margin": "6px 0"}), dmc.Text(FEEL.get(part_id, {}).get(t, v["blurb"]), size="xs", c="dimmed"), dmc.Group([dmc.Text(f"stat {v['mult']:+.1f}", size="xs", fw=600, c="red" if v["mult"] < 0 else ("dimmed" if v["mult"] == 0 else "teal"), style={"fontFamily": "monospace"}), dmc.Text(f"cost {v['cost']:.2f}", size="xs", c="dimmed", style={"fontFamily": "monospace"})], justify="space-between"), ], withBorder=True, radius="md", p="sm", )
def movement_lab(part_id): part = ADAPTATIONS[part_id] P = part_id defspec = _DEFAULT_SPEC.get(P, SPECIMENS[0]["value"])
badges = [dmc.Badge(part["role"], color=_ROLE_COLOR.get(part["role"], "gray"), variant="light", size="sm")] if part.get("diet"): badges.append(dmc.Badge(part["diet"], color="lime", variant="dot", size="sm"))
dish = dmc.Paper( [ dmc.Group( [dmc.Text(part["label"].upper(), fw=700, c=ACCENT, style={"letterSpacing": "2px", "fontFamily": "monospace"}), dmc.Group(badges, gap=6)], justify="space-between", mb="xs", ), html.Div( [ html.Div(html.Div(id=f"ml-{P}-arena", style={"position": "absolute", "inset": 0, "zIndex": 1}), className="petri-dish", style={"position": "absolute", "inset": 0, "borderRadius": "50%", "overflow": "hidden"}), html.Div( dg.DashRCJoystick(id=f"ml-{P}-joy", baseRadius=48, controllerRadius=22, directionCountMode="Nine", insideMode=True, throttle=40), style={"position": "absolute", "right": "2px", "bottom": "2px", "zIndex": 6, "padding": "6px", "borderRadius": "50%", "background": "rgba(255,255,255,0.55)", "backdropFilter": "blur(2px)", "boxShadow": "0 2px 10px rgba(15,80,72,0.25)"}, ), ], style={"position": "relative", "aspectRatio": "1 / 1", "width": "100%", "maxWidth": 460, "margin": "0 auto"}, ), dcc.Interval(id=f"ml-{P}-tick", interval=60, n_intervals=0), dcc.Interval(id=f"ml-{P}-init", interval=300, n_intervals=0, max_intervals=1), ], p="md", radius="md", withBorder=True, style={"flex": "1 1 460px", "maxWidth": 540, "background": "linear-gradient(rgb(255 255 255 / 0%) 0%, rgb(243 247 239 / 30%) 100%)", "borderColor": "rgb(51 142 234 / 24%)"}, )
controls = dmc.Stack( [ dmc.Text("DRIVE IT", fw=700, c=ACCENT, style={"letterSpacing": "2px", "fontFamily": "monospace"}), dmc.Select(id=f"ml-{P}-spec", label="Specimen", value=defspec, allowDeselect=False, data=[{"value": s["value"], "label": s["label"]} for s in SPECIMENS], leftSection=DashIconify(icon="mdi:microscope")), dmc.Stack( [dmc.Text("ADAPTATION TIER", fw=700, size="xs", c="dimmed", style={"letterSpacing": "1px", "fontFamily": "monospace"}), dmc.SegmentedControl(id=f"ml-{P}-tier", value="decent", fullWidth=True, color="brand", radius="md", size="xs", data=[{"value": t, "label": TIER_LABEL[t]} for t in _TIERS])], gap=4), dmc.Text("", id=f"ml-{P}-feel", size="sm", fw=600, c=ACCENT), dmc.Paper( html.Pre("", id=f"ml-{P}-stats", style={"fontFamily": "monospace", "fontSize": "11px", "margin": 0, "whiteSpace": "pre-wrap", "color": "light-dark(#236bb3, #9ec9ff)"}), withBorder=True, radius="md", p="sm", style={"background": "light-dark(#eef5ff, #16202e)"}, ), dmc.Button("Reroll luck (respawn)", id=f"ml-{P}-respawn", variant="light", color="brand", leftSection=DashIconify(icon="tabler:refresh")), dmc.Text("Drive with the joystick or WASD / arrow keys. The trail shows the movement's " "character; ‘Reroll luck’ re-rolls this spawn's small random variance.", size="xs", c="dimmed"), ], gap="sm", style={"flex": "1 1 240px", "maxWidth": 300}, )
component = dmc.Stack( [ dmc.Paper( [dmc.Text(part["label"] + " — MOVEMENT LAB", fw=700, c=ACCENT, style={"letterSpacing": "2px", "fontFamily": "monospace"}), dmc.Text(part["function"] + " Each tier modulates the specimen's natural movement; " "a small per-spawn luck roll makes every run a little different.", size="sm", c="dimmed")], withBorder=True, radius="md", p="md", style={"borderColor": "rgb(51 142 234 / 24%)", "background": "linear-gradient(rgb(255 255 255 / 0%) 0%, rgb(243 247 239 / 30%) 100%)"}, ), dmc.Group([dish, controls], align="flex-start", gap="xl", wrap="wrap"), dmc.Paper( [dmc.Text("TIER REFERENCE", fw=700, size="xs", c="dimmed", style={"letterSpacing": "1px", "fontFamily": "monospace"}, mb="xs"), dmc.SimpleGrid(cols={"base": 2, "sm": 4}, spacing="sm", children=[_tier_ref_card(P, v) for v in part["variants"]])], withBorder=True, radius="md", p="md", style={"borderColor": "rgb(51 142 234 / 24%)"}, ), dcc.Store(id=f"ml-{P}-mv", data=_movement_store()), dcc.Store(id=f"ml-{P}-sink"), ], gap="md", )
# ---- driving loop (part-namespaced via __P__ substitution) -------------- loop_js = """ function(n, nResp, spec, tier, mv, jAngle, jDist) { const nu = window.dash_clientside.no_update; if (!window.CM || !mv) { return [nu, nu]; } const P = '__P__'; if (!window.__mlKeys) { window.__mlKeys = {}; window.addEventListener('keydown', e => { window.__mlKeys[e.key.toLowerCase()] = true; if (e.key === ' ' && document.querySelector('.petri-dish:hover, .petri-dish:focus-within')) { e.preventDefault(); } }); window.addEventListener('keyup', e => { window.__mlKeys[e.key.toLowerCase()] = false; }); } window.__ml = window.__ml || {}; let M = window.__ml[P]; if (!M || M.spec !== spec || M.tier !== tier || M.resp !== (nResp || 0)) { M = window.__ml[P] = {spec: spec, tier: tier, resp: (nResp || 0), variance: window.CM.variance(), state: {x: 0, y: 0, vx: 0, vy: 0, deg: -90, trail: [], cd: 0, _ph: 0}}; } const eff = window.CM.computeStats(mv, spec, P, tier, M.variance); let dx = 0, dy = 0; if (jAngle != null && jDist) { const m = Math.min(1, jDist / 26), a = jAngle * Math.PI / 180; dx += Math.cos(a) * m; dy += -Math.sin(a) * m; } const k = window.__mlKeys || {}; if (k['w'] || k['arrowup']) { dy -= 1; } if (k['s'] || k['arrowdown']) { dy += 1; } if (k['a'] || k['arrowleft']) { dx -= 1; } if (k['d'] || k['arrowright']) { dx += 1; } if (dx || dy) { const mm = Math.hypot(dx, dy); if (mm > 1) { dx /= mm; dy /= mm; } } window.CM.step(M.state, {dx: dx, dy: dy}, eff); const COL = {none: '#9aa7b3', poor: '#fa5252', decent: '#f0a020', great: '#2bb673'}[tier] || '#3399ff'; const sp = (mv.specimens.find(s => s.value === spec) || {}); const host = document.getElementById('ml-__P__-arena'); if (host) { host.innerHTML = window.CM.render(M.state, sp.img || '', COL); } const v = M.variance; const stats = 'ARCHETYPE ' + eff.archLabel + '\\n' + 'TOP SPEED ' + eff.speed.toFixed(1) + '\\n' + 'TURN ' + (eff.turn * 100 | 0) + '%\\n' + 'CONTROL ' + (eff.control * 100 | 0) + '%\\n' + (eff.dart > 0 ? 'DART ' + eff.dart.toFixed(0) + '\\n' : '') + 'LUCK spd ' + v.speed.toFixed(2) + ' \\u00b7 ctl ' + v.ctrl.toFixed(2) + (eff.dart > 0 ? ' \\u00b7 jmp ' + v.jump.toFixed(2) : ''); return [stats, eff.feel]; } """.replace("__P__", P) clientside_callback( loop_js, Output(f"ml-{P}-stats", "children"), Output(f"ml-{P}-feel", "children"), Input(f"ml-{P}-tick", "n_intervals"), Input(f"ml-{P}-respawn", "n_clicks"), Input(f"ml-{P}-spec", "value"), Input(f"ml-{P}-tier", "value"), State(f"ml-{P}-mv", "data"), State(f"ml-{P}-joy", "angle"), State(f"ml-{P}-joy", "distance"), )
# ---- fill the tier reference schematics once on mount ------------------- clientside_callback( """ function(_n) { if (!window.CV) { return window.dash_clientside.no_update; } document.querySelectorAll('[id*="ml-card"]').forEach(host => { let id = null; try { id = JSON.parse(host.id); } catch (e) { return; } if (id.part !== '__P__') { return; } host.innerHTML = (window.CV.renderAdaptPreview || window.CV.renderPart)(id.part, id.tier); }); return ''; } """.replace("__P__", P), Output(f"ml-{P}-sink", "data"), Input(f"ml-{P}-init", "n_intervals"), )
return component
============================ combat labs ====================================
Spike (defense) + Stinger (attack): drive a specimen while red predators home in.
Spike REPELS attackers on contact (tier scales push + damage); Stinger ZAPS the
nearest predator within a tier-scaled venom reach (paralyse → flee; great kills).
_COMBAT_DEFAULT_SPEC = {"spike": "actinosphaerium", "stinger": "cyclops"} _COMBAT_FEEL = { "spike": {"none": "No spines — predators engulf you freely.", "poor": "Brittle bumps — barely slow an attacker.", "decent": "Rigid spines deter most predators on contact.", "great": "Reinforced spines — attackers bounce off and take damage."}, "stinger": {"none": "No barb — you can't fight back.", "poor": "Misfires — short, weak, slow venom.", "decent": "Reliable barb — paralyses an adjacent attacker.", "great": "Ranged high-potency barb — zap and kill before contact."}, }
def combat_lab(part_id): part = ADAPTATIONS[part_id] P = part_id is_def = part["role"] == "defense" defspec = _COMBAT_DEFAULT_SPEC.get(P, SPECIMENS[0]["value"]) badges = [dmc.Badge(part["role"], color="orange" if is_def else "red", variant="light", size="sm")]
dish = dmc.Paper( [dmc.Group([dmc.Text(part["label"].upper(), fw=700, c=ACCENT, style={"letterSpacing": "2px", "fontFamily": "monospace"}), dmc.Group(badges, gap=6)], justify="space-between", mb="xs"), html.Div( [html.Div(html.Div(id=f"cl-{P}-arena", style={"position": "absolute", "inset": 0, "zIndex": 1}), className="petri-dish", style={"position": "absolute", "inset": 0, "borderRadius": "50%", "overflow": "hidden"}), html.Div(dg.DashRCJoystick(id=f"cl-{P}-joy", baseRadius=48, controllerRadius=22, directionCountMode="Nine", insideMode=True, throttle=40), style={"position": "absolute", "right": "2px", "bottom": "2px", "zIndex": 6, "padding": "6px", "borderRadius": "50%", "background": "rgba(255,255,255,0.55)", "backdropFilter": "blur(2px)", "boxShadow": "0 2px 10px rgba(15,80,72,0.25)"})], style={"position": "relative", "aspectRatio": "1 / 1", "width": "100%", "maxWidth": 460, "margin": "0 auto"}), dcc.Interval(id=f"cl-{P}-tick", interval=60, n_intervals=0), dcc.Interval(id=f"cl-{P}-init", interval=300, n_intervals=0, max_intervals=1)], p="md", radius="md", withBorder=True, style={"flex": "1 1 460px", "maxWidth": 540, "background": "linear-gradient(rgb(255 255 255 / 0%) 0%, rgb(243 247 239 / 30%) 100%)", "borderColor": "rgb(51 142 234 / 24%)"}, )
controls = dmc.Stack( [dmc.Text("SURVIVE IT", fw=700, c=ACCENT, style={"letterSpacing": "2px", "fontFamily": "monospace"}), dmc.Select(id=f"cl-{P}-spec", label="Specimen", value=defspec, allowDeselect=False, data=[{"value": s["value"], "label": s["label"]} for s in SPECIMENS], leftSection=DashIconify(icon="mdi:microscope")), dmc.Stack([dmc.Text("ADAPTATION TIER", fw=700, size="xs", c="dimmed", style={"letterSpacing": "1px", "fontFamily": "monospace"}), dmc.SegmentedControl(id=f"cl-{P}-tier", value="decent", fullWidth=True, color="brand", radius="md", size="xs", data=[{"value": t, "label": TIER_LABEL[t]} for t in _TIERS])], gap=4), dmc.Text("", id=f"cl-{P}-feel", size="sm", fw=600, c=ACCENT), dmc.Paper(html.Pre("", id=f"cl-{P}-stats", style={"fontFamily": "monospace", "fontSize": "11px", "margin": 0, "whiteSpace": "pre-wrap", "color": "light-dark(#236bb3, #9ec9ff)"}), withBorder=True, radius="md", p="sm", style={"background": "light-dark(#eef5ff, #16202e)"}), dmc.Button("Reset predators", id=f"cl-{P}-respawn", variant="light", color="brand", leftSection=DashIconify(icon="tabler:refresh")), dmc.Text(("Red predators hunt you. Spines repel attackers on contact — higher tiers push harder, " "damage them, and keep them fleeing longer." if is_def else "Red predators hunt you. Your barb auto-zaps the nearest attacker in venom range — higher " "tiers reach further, hit harder, and paralyse longer."), size="xs", c="dimmed")], gap="sm", style={"flex": "1 1 250px", "maxWidth": 320}, )
component = dmc.Stack( [dmc.Paper([dmc.Text(part["label"] + (" — DEFENSE LAB" if is_def else " — ATTACK LAB"), fw=700, c=ACCENT, style={"letterSpacing": "2px", "fontFamily": "monospace"}), dmc.Text(part["function"] + " Drive to dodge; the adaptation does the rest — its TIER sets " "how far and how hard it works.", size="sm", c="dimmed")], withBorder=True, radius="md", p="md", style={"borderColor": "rgb(51 142 234 / 24%)", "background": "linear-gradient(rgb(255 255 255 / 0%) 0%, rgb(243 247 239 / 30%) 100%)"}), dmc.Group([dish, controls], align="flex-start", gap="xl", wrap="wrap"), dmc.Paper([dmc.Text("TIER REFERENCE", fw=700, size="xs", c="dimmed", style={"letterSpacing": "1px", "fontFamily": "monospace"}, mb="xs"), dmc.SimpleGrid(cols={"base": 2, "sm": 4}, spacing="sm", children=[_tier_ref_card(P, v) for v in part["variants"]])], withBorder=True, radius="md", p="md", style={"borderColor": "rgb(51 142 234 / 24%)"}), dcc.Store(id=f"cl-{P}-mv", data=_movement_store()), dcc.Store(id=f"cl-{P}-sink")], gap="md", )
loop_js = """ function(n, nResp, spec, tier, mv, jAngle, jDist) { const nu = window.dash_clientside.no_update; if (!window.CM || !window.CV || !mv) { return [nu, nu]; } const P = '__P__', DEF = __DEF__, R = 280; const FEEL = __FEEL__; const REPEL = {none: 0, poor: 10, decent: 26, great: 44}; const ZAP = {none: 0, poor: 46, decent: 92, great: 150}; const FLEE = {none: 0, poor: 18, decent: 34, great: 56}; // spike: tier-scaled flee duration (ticks) const PARA = {none: 0, poor: 22, decent: 46, great: 78}; // stinger: tier-scaled paralysis (ticks) if (!window.__mlKeys) { window.__mlKeys = {}; window.addEventListener('keydown', e => { window.__mlKeys[e.key.toLowerCase()] = true; if (e.key === ' ' && document.querySelector('.petri-dish:hover, .petri-dish:focus-within')) { e.preventDefault(); } }); window.addEventListener('keyup', e => { window.__mlKeys[e.key.toLowerCase()] = false; }); } window.__cl = window.__cl || {}; let M = window.__cl[P]; const newPred = () => { const a = Math.random() * 6.283, d = 200 + Math.random() * 70; return {x: Math.cos(a) * d, y: Math.sin(a) * d, vx: 0, vy: 0, r: 22 + Math.random() * 10, hp: 100, deg: 0, state: 'hunt', cd: 0}; }; if (!M || M.spec !== spec || M.resp !== (nResp || 0)) { M = window.__cl[P] = {spec: spec, resp: (nResp || 0), variance: window.CM.variance(), state: {x: 0, y: 0, vx: 0, vy: 0, deg: -90, r: 34, trail: [], cd: 0, _ph: 0}, preds: [newPred(), newPred(), newPred(), newPred()], repels: 0, kills: 0, hits: 0, flash: 0, flashEng: 0}; } // 'cilia'/'decent' = a navigable movement baseline; the combat parts (spike/stinger) // aren't in mv.partMove, so passing P/tier here would zero the weights and blank the feel. const eff = window.CM.computeStats(mv, spec, 'cilia', 'decent', M.variance); let dx = 0, dy = 0; if (jAngle != null && jDist) { const m = Math.min(1, jDist / 26), a = jAngle * Math.PI / 180; dx += Math.cos(a) * m; dy += -Math.sin(a) * m; } const k = window.__mlKeys || {}; if (k['w'] || k['arrowup']) { dy -= 1; } if (k['s'] || k['arrowdown']) { dy += 1; } if (k['a'] || k['arrowleft']) { dx -= 1; } if (k['d'] || k['arrowright']) { dx += 1; } if (dx || dy) { const mm = Math.hypot(dx, dy); if (mm > 1) { dx /= mm; dy /= mm; } } window.CM.step(M.state, {dx: dx, dy: dy}, eff); const px = M.state.x, py = M.state.y, pr = M.state.r, reach = DEF ? REPEL[tier] : ZAP[tier]; const repelsBefore = M.repels; // detect a repel/zap landing THIS tick (drives the flare flash) let zaps = []; let near = null, nd = 1e9; for (const e of M.preds) { if (e.state !== 'hunt') { continue; } const d = Math.hypot(e.x - px, e.y - py); if (d < nd) { nd = d; near = e; } } for (let i = M.preds.length - 1; i >= 0; i--) { const e = M.preds[i], tox = px - e.x, toy = py - e.y, td = Math.hypot(tox, toy) || 1; if (e.state === 'stun') { e.vx *= 0.5; e.vy *= 0.5; e.cd--; if (e.cd <= 0) { e.state = 'hunt'; } } // paralysed: frozen else if (e.state === 'flee') { e.vx += (-tox / td) * 0.5; e.vy += (-toy / td) * 0.5; e.cd--; if (e.cd <= 0) { e.state = 'hunt'; } } else { e.vx += (tox / td) * 0.16; e.vy += (toy / td) * 0.16; } const sp = Math.hypot(e.vx, e.vy), mx = 2.6; if (sp > mx) { e.vx *= mx / sp; e.vy *= mx / sp; } e.x += e.vx; e.y += e.vy; const dd = Math.hypot(e.x, e.y); if (dd > R - e.r) { const kk = (R - e.r) / dd; e.x *= kk; e.y *= kk; e.vx *= -0.5; e.vy *= -0.5; } if (Math.hypot(e.vx, e.vy) > 0.3) { e.deg = Math.atan2(e.vy, e.vx) * 180 / Math.PI; } if (DEF) { if (td < pr + e.r + reach) { if (reach <= 0) { M.hits++; M.flashEng = 6; } else { e.x = px - (tox / td) * (pr + e.r + reach); e.y = py - (toy / td) * (pr + e.r + reach); e.vx = -tox / td * 3; e.vy = -toy / td * 3; e.state = 'flee'; e.cd = FLEE[tier]; M.repels++; if (tier === 'great') { e.hp -= 34; zaps.push([px, py, e.x, e.y, '#ff6b6b']); } } } } else { if (e === near && reach > 0 && nd < pr + reach && M.state.cd <= 0) { e.state = 'stun'; e.cd = PARA[tier]; M.repels++; e.hp -= (tier === 'great' ? 60 : (tier === 'decent' ? 30 : 14)); zaps.push([px, py, e.x, e.y, '#ffd43b']); M.state.cd = (tier === 'great' ? 10 : 20); } } if (e.hp <= 0) { M.preds.splice(i, 1); M.kills++; } } if (M.state.cd > 0) { M.state.cd--; } while (M.preds.length < 4) { M.preds.push(newPred()); } // --- cosmetic feel state (NO physics change): flare on a fresh repel/zap, stinger charge read, threat ring --- M.state._ph = (M.state._ph || 0) + 0.4; if (M.repels > repelsBefore) { M.flash = 8; } if (M.flash > 0) { M.flash--; } const maxCd = (tier === 'great' ? 10 : 20); const charge = (!DEF && reach > 0) ? Math.max(0, Math.min(1, 1 - (M.state.cd / maxCd))) : 1; const danger = M.preds.some(e => e.state === 'hunt' && Math.hypot(e.x - px, e.y - py) < pr + e.r + reach + 34); let svg = '<svg width="100%" height="100%" viewBox="-300 -300 600 600" preserveAspectRatio="xMidYMid meet" style="overflow:visible">' + '<defs><filter id="cl-pl"><feFlood flood-color="#f03e3e" result="f"/><feComposite in="f" in2="SourceAlpha" operator="in"/></filter></defs>' + '<circle r="280" fill="none" stroke="rgba(51,153,255,0.18)" stroke-width="2"/>'; if (reach > 0) { // reach RADIUS highlight: filled disc + bright dashed ring + radial spokes (sectioned) const RR2 = pr + reach, rc = DEF ? '#f76707' : '#ffd43b'; svg += '<circle cx="' + px.toFixed(1) + '" cy="' + py.toFixed(1) + '" r="' + RR2.toFixed(1) + '" fill="' + (DEF ? 'rgba(247,103,7,0.08)' : 'rgba(255,212,59,0.08)') + '" stroke="' + rc + '" stroke-width="2" stroke-dasharray="7 6" opacity="0.7"/>'; for (let i = 0; i < 8; i++) { const a = i * Math.PI / 4; svg += '<line x1="' + (px + Math.cos(a) * pr).toFixed(1) + '" y1="' + (py + Math.sin(a) * pr).toFixed(1) + '" x2="' + (px + Math.cos(a) * RR2).toFixed(1) + '" y2="' + (py + Math.sin(a) * RR2).toFixed(1) + '" stroke="' + rc + '" stroke-width="1" opacity="0.22"/>'; } } for (const z of zaps) { svg += '<path d="M' + z[0].toFixed(1) + ',' + z[1].toFixed(1) + ' L' + ((z[0]+z[2])/2+8).toFixed(1) + ',' + ((z[1]+z[3])/2-8).toFixed(1) + ' L' + z[2].toFixed(1) + ',' + z[3].toFixed(1) + '" fill="none" stroke="' + z[4] + '" stroke-width="3" stroke-linecap="round"/>'; } for (const e of M.preds) { const dd = ((e.deg % 360) + 360) % 360, flip = (dd > 90 && dd < 270) ? -1 : 1; if (reach > 0 && e.state === 'hunt' && Math.hypot(e.x - px, e.y - py) < 1.4 * (pr + reach)) { svg += '<line x1="' + e.x.toFixed(1) + '" y1="' + e.y.toFixed(1) + '" x2="' + px.toFixed(1) + '" y2="' + py.toFixed(1) + '" stroke="#f03e3e" stroke-width="1" stroke-dasharray="3 5" opacity="0.45"/>'; } svg += '<g transform="translate(' + e.x.toFixed(1) + ',' + e.y.toFixed(1) + ') rotate(' + e.deg.toFixed(1) + ') scale(1,' + flip + ')"' + (e.state !== 'hunt' ? ' opacity="0.7"' : '') + '>' + (e.state === 'flee' ? '<circle r="' + (e.r + 4).toFixed(1) + '" fill="none" stroke="#ffd43b" stroke-width="1.5" opacity="0.6"/>' : '') + (e.state === 'stun' ? '<circle r="' + (e.r + 4).toFixed(1) + '" fill="none" stroke="#9775fa" stroke-width="2" stroke-dasharray="3 4" opacity="0.85"/>' : '') + '<image href="/assets/petri/cyclops.svg" x="' + (-e.r) + '" y="' + (-e.r) + '" width="' + (2*e.r) + '" height="' + (2*e.r) + '" filter="url(#cl-pl)"/></g>'; } // PLAYER = the actual specimen sprite (so changing the Specimen is VISIBLE) with the // adaptation overlaid on top (spike crown / stinger barb), tier-scaled & seated on the rim. const psp = (mv.specimens || []).find(s => s.value === spec) || (mv.specimens || [])[0] || {}; const img = psp.img || '/assets/petri/cyclops.svg'; const ddp = ((M.state.deg % 360) + 360) % 360, flp = (ddp > 90 && ddp < 270) ? -1 : 1; const fk = (M.flash > 0) ? (M.flash / 8) : 0; svg += '<g transform="translate(' + px.toFixed(1) + ',' + py.toFixed(1) + ') rotate(' + M.state.deg.toFixed(1) + ') scale(1,' + flp + ')">' + '<image href="' + img + '" x="' + (-pr) + '" y="' + (-pr) + '" width="' + (2 * pr) + '" height="' + (2 * pr) + '" preserveAspectRatio="xMidYMid meet"/>'; if (tier !== 'none') { if (DEF) { // SPIKE: omnidirectional crown; spines jut out on a repel then settle const sc = (pr / 38) * (1 + 0.18 * fk); svg += '<g transform="scale(' + sc.toFixed(3) + ')"' + (fk > 0 ? ' style="filter:drop-shadow(0 0 3px #ff6b6b)"' : '') + '>' + window.CV.partOverlay('spike', tier) + '</g>'; } else { // STINGER: forward barb + venom tip-glow that brightens with charge const sc = pr / 38, L = ({poor: 16, decent: 28, great: 40})[tier], hx = (36 + L) * sc; svg += '<circle cx="' + hx.toFixed(1) + '" cy="0" r="' + (2 + 5 * charge).toFixed(1) + '" fill="#ffd43b" opacity="' + (0.18 + 0.55 * charge).toFixed(2) + '"/>' + '<g transform="scale(' + sc.toFixed(3) + ')">' + window.CV.partOverlay('stinger', tier) + '</g>'; } } svg += '</g>'; // stinger CHARGE/cooldown ring — screen-anchored (outside the rotate group). Full green circle when ARMED. if (!DEF && reach > 0) { const rr = pr + 7; if (charge >= 0.999) { svg += '<circle cx="' + px.toFixed(1) + '" cy="' + py.toFixed(1) + '" r="' + rr + '" fill="none" stroke="#2bb673" stroke-width="2.5" opacity="0.85"/>'; } else { const a0 = -Math.PI / 2, a1 = a0 + 2 * Math.PI * charge, lg = (a1 - a0) > Math.PI ? 1 : 0; const x0 = px + Math.cos(a0) * rr, y0 = py + Math.sin(a0) * rr, x1 = px + Math.cos(a1) * rr, y1 = py + Math.sin(a1) * rr; svg += '<path d="M' + x0.toFixed(1) + ',' + y0.toFixed(1) + ' A' + rr + ',' + rr + ' 0 ' + lg + ' 1 ' + x1.toFixed(1) + ',' + y1.toFixed(1) + '" fill="none" stroke="#ffd43b" stroke-width="2.5" opacity="0.85" stroke-linecap="round"/>'; } } // pulsing DANGER ring when a hunter is closing if (danger) { const rp = pr + 8 + 3 * Math.sin(M.state._ph); svg += '<circle cx="' + px.toFixed(1) + '" cy="' + py.toFixed(1) + '" r="' + rp.toFixed(1) + '" fill="none" stroke="#f03e3e" stroke-width="2.5" opacity="' + (0.4 + 0.3 * Math.sin(M.state._ph)).toFixed(2) + '"/>'; } // ENGULF wash (defense, no spines): a red flash sells "nothing protecting you = you get eaten" if (DEF && reach <= 0 && M.flashEng > 0) { svg += '<circle r="280" fill="rgba(240,62,62,' + (0.20 * M.flashEng / 6).toFixed(3) + ')"/>'; M.flashEng--; } svg += '</svg>'; const host = document.getElementById('cl-__P__-arena'); if (host) { host.innerHTML = svg; } const stats = (DEF ? 'DEFENSE' : 'ATTACK') + '\\n' + 'TIER ' + tier + '\\n' + 'REACH ' + reach + '\\n' + (DEF ? 'FLEE ' + FLEE[tier] + 't' : 'PARALYSIS ' + PARA[tier] + 't') + '\\n' + (DEF ? 'REPELS ' : 'ZAPS ') + M.repels + '\\n' + 'KILLS ' + M.kills + (DEF && reach <= 0 ? '\\nENGULFED ' + M.hits : '') + (!DEF ? '\\nCHARGE ' + ((100 * charge) | 0) + '%' : '') + '\\nSTATUS ' + (reach <= 0 ? 'EXPOSED' : (danger ? 'THREAT' : 'CLEAR')); return [stats, FEEL[tier] || '']; } """.replace("__P__", P).replace("__DEF__", "true" if is_def else "false") \ .replace("__FEEL__", json.dumps(_COMBAT_FEEL[P])) clientside_callback( loop_js, Output(f"cl-{P}-stats", "children"), Output(f"cl-{P}-feel", "children"), Input(f"cl-{P}-tick", "n_intervals"), Input(f"cl-{P}-respawn", "n_clicks"), Input(f"cl-{P}-spec", "value"), Input(f"cl-{P}-tier", "value"), State(f"cl-{P}-mv", "data"), State(f"cl-{P}-joy", "angle"), State(f"cl-{P}-joy", "distance"), )
clientside_callback( """ function(_n) { if (!window.CV) { return window.dash_clientside.no_update; } document.querySelectorAll('[id*="ml-card"]').forEach(host => { let id = null; try { id = JSON.parse(host.id); } catch (e) { return; } if (id.part !== '__P__') { return; } host.innerHTML = (window.CV.renderAdaptPreview || window.CV.renderPart)(id.part, id.tier); }); return ''; } """.replace("__P__", P), Output(f"cl-{P}-sink", "data"), Input(f"cl-{P}-init", "n_intervals"), ) return component
============================ feeding labs ===================================
_FEED_DEFAULT_SPEC = {"filter": "daphnia", "jaw": "cyclops", "proboscis": "paramecium", "pseudopod": "amoeba"} _FEED_FEEL = { "none": "No mouth — you can't feed at all. Starving.", "poor": "Weak, clogged intake — barely keeps up; you starve in thin food.", "decent": "Reliable feeding at a steady rate.", "great": "High-throughput feeding — maximal intake.", }
def feeding_lab(part_id): """Isolated feed/attack/grip lab: drive a specimen and eat — but only your DIET's food (herbivore=plankton, carnivore=meat, omnivore=both), and only as well as the part's TIER allows. Also shows how your nature SKEWS a mutation roll toward the matching feed part (rare cross-diet).""" part = ADAPTATIONS[part_id] P = part_id diet = PART_DIET.get(P, "carnivore") edible = " + ".join(DIET_EDIBLE[diet]) defspec = _FEED_DEFAULT_SPEC.get(P, SPECIMENS[0]["value"])
badges = [dmc.Badge(part["role"], color="green" if part["role"] == "feed" else ("red" if part["role"] == "attack" else "teal"), variant="light", size="sm"), dmc.Badge(DIET_LABEL[diet], color=DIET_COLOR[diet], variant="dot", size="sm")]
dish = dmc.Paper( [ dmc.Group( [dmc.Text(part["label"].upper(), fw=700, c=ACCENT, style={"letterSpacing": "2px", "fontFamily": "monospace"}), dmc.Group(badges, gap=6)], justify="space-between", mb="xs", ), html.Div( [ html.Div(html.Div(id=f"fl-{P}-arena", style={"position": "absolute", "inset": 0, "zIndex": 1}), className="petri-dish", style={"position": "absolute", "inset": 0, "borderRadius": "50%", "overflow": "hidden"}), html.Div( dg.DashRCJoystick(id=f"fl-{P}-joy", baseRadius=48, controllerRadius=22, directionCountMode="Nine", insideMode=True, throttle=40), style={"position": "absolute", "right": "2px", "bottom": "2px", "zIndex": 6, "padding": "6px", "borderRadius": "50%", "background": "rgba(255,255,255,0.55)", "backdropFilter": "blur(2px)", "boxShadow": "0 2px 10px rgba(15,80,72,0.25)"}, ), ], style={"position": "relative", "aspectRatio": "1 / 1", "width": "100%", "maxWidth": 460, "margin": "0 auto"}, ), dcc.Interval(id=f"fl-{P}-tick", interval=60, n_intervals=0), dcc.Interval(id=f"fl-{P}-init", interval=300, n_intervals=0, max_intervals=1), ], p="md", radius="md", withBorder=True, style={"flex": "1 1 460px", "maxWidth": 540, "background": "linear-gradient(rgb(255 255 255 / 0%) 0%, rgb(243 247 239 / 30%) 100%)", "borderColor": "rgb(51 142 234 / 24%)"}, )
def _odd_cell(fp): return html.Div( [html.Div(html.Div(id=f"fl-{P}-bar-{fp}", className="eye-bar"), className="eye-bar-track"), dmc.Text("0%", id=f"fl-{P}-pct-{fp}", size="xs", fw=700, ta="center", style={"fontFamily": "monospace"}), dmc.Badge(ADAPTATIONS[fp]["label"].split()[0], color="brand", variant="light", size="xs", fullWidth=True)], id=f"fl-{P}-odd-{fp}", className="eye-odd", )
controls = dmc.Stack( [ dmc.Text("FEED IT", fw=700, c=ACCENT, style={"letterSpacing": "2px", "fontFamily": "monospace"}), dmc.Select(id=f"fl-{P}-spec", label="Specimen", value=defspec, allowDeselect=False, data=[{"value": s["value"], "label": s["label"]} for s in SPECIMENS], leftSection=DashIconify(icon="mdi:microscope")), dmc.Stack( [dmc.Text("ADAPTATION TIER", fw=700, size="xs", c="dimmed", style={"letterSpacing": "1px", "fontFamily": "monospace"}), dmc.SegmentedControl(id=f"fl-{P}-tier", value="decent", fullWidth=True, color="brand", radius="md", size="xs", data=[{"value": t, "label": TIER_LABEL[t]} for t in ["none", "poor", "decent", "great"]])], gap=4), dmc.Text("", id=f"fl-{P}-feel", size="sm", fw=600, c=ACCENT), dmc.Paper( html.Pre("", id=f"fl-{P}-stats", style={"fontFamily": "monospace", "fontSize": "11px", "margin": 0, "whiteSpace": "pre-wrap", "color": "light-dark(#236bb3, #9ec9ff)"}), withBorder=True, radius="md", p="sm", style={"background": "light-dark(#eef5ff, #16202e)"}, ), dmc.Text("This specimen's nature skews which feed part it tends to evolve " "(cross-diet is rare but possible):", size="xs", c="dimmed"), html.Div([_odd_cell("filter"), _odd_cell("jaw"), _odd_cell("proboscis")], className="eye-odds-row"), dmc.Button("Roll a feed part", id=f"fl-{P}-roll", variant="light", color="brand", leftSection=DashIconify(icon="tabler:dice")), dmc.Text("", id=f"fl-{P}-roll-result", size="sm", fw=600), ], gap="sm", style={"flex": "1 1 250px", "maxWidth": 320}, )
component = dmc.Stack( [ dmc.Paper( [dmc.Text(part["label"] + " — FEEDING LAB", fw=700, c=ACCENT, style={"letterSpacing": "2px", "fontFamily": "monospace"}), dmc.Text(part["function"] + ( " Your pseudopod arms GRAB nearby prey, REEL them in and engulf them — higher tiers " "reach farther and hold MORE at once (teal arms = active grips; the dashed ring is your " "grab range; ‘none’ can't grip)." if P == "pseudopod" else f" Diet guard-rail: a {DIET_LABEL[diet].lower()} can only eat " f"{edible}. Drive over your food; the dashed ring is your feeding reach (tier-scaled; " f"‘none’ can't feed). Greyed food isn't on your menu."), size="sm", c="dimmed")], withBorder=True, radius="md", p="md", style={"borderColor": "rgb(51 142 234 / 24%)", "background": "linear-gradient(rgb(255 255 255 / 0%) 0%, rgb(243 247 239 / 30%) 100%)"}, ), dmc.Group([dish, controls], align="flex-start", gap="xl", wrap="wrap"), dmc.Paper( [dmc.Text("TIER REFERENCE", fw=700, size="xs", c="dimmed", style={"letterSpacing": "1px", "fontFamily": "monospace"}, mb="xs"), dmc.SimpleGrid(cols={"base": 2, "sm": 4}, spacing="sm", children=[_tier_ref_card(P, v) for v in part["variants"]])], withBorder=True, radius="md", p="md", style={"borderColor": "rgb(51 142 234 / 24%)"}, ), dcc.Store(id=f"fl-{P}-mv", data=_movement_store()), dcc.Store(id=f"fl-{P}-diet", data=_diet_store()), dcc.Store(id=f"fl-{P}-sink"), ], gap="md", )
# driving + feeding loop loop_js = """ function(n, spec, tier, mv, jAngle, jDist) { const nu = window.dash_clientside.no_update; if (!window.CM || !window.CF || !mv) { return [nu, nu]; } const P = '__P__', DIET = '__DIET__'; const FFEEL = __FFEEL__; if (!window.__mlKeys) { window.__mlKeys = {}; window.addEventListener('keydown', e => { window.__mlKeys[e.key.toLowerCase()] = true; if (e.key === ' ' && document.querySelector('.petri-dish:hover, .petri-dish:focus-within')) { e.preventDefault(); } }); window.addEventListener('keyup', e => { window.__mlKeys[e.key.toLowerCase()] = false; }); } window.__fl = window.__fl || {}; let M = window.__fl[P]; if (!M || M.spec !== spec) { M = window.__fl[P] = {spec: spec, variance: window.CM.variance(), field: window.CF.newField(), energy: 0, recent: [], state: {x: 0, y: 0, vx: 0, vy: 0, deg: -90, r: 30, trail: [], cd: 0, _ph: 0}}; } const eff = window.CM.computeStats(mv, spec, 'cilia', 'decent', M.variance); // navigable baseline let dx = 0, dy = 0; if (jAngle != null && jDist) { const m = Math.min(1, jDist / 26), a = jAngle * Math.PI / 180; dx += Math.cos(a) * m; dy += -Math.sin(a) * m; } const k = window.__mlKeys || {}; if (k['w'] || k['arrowup']) { dy -= 1; } if (k['s'] || k['arrowdown']) { dy += 1; } if (k['a'] || k['arrowleft']) { dx -= 1; } if (k['d'] || k['arrowright']) { dx += 1; } if (dx || dy) { const mm = Math.hypot(dx, dy); if (mm > 1) { dx /= mm; dy /= mm; } } window.CM.step(M.state, {dx: dx, dy: dy}, eff); // PROBOSCIS feeds with a SINGLE, AIMED, RHYTHMIC strike — NOT the omni-reach // auto-eat — so it skips CF.step's eating (tier 'none') and runs its own // state machine: lock one target inside a forward cone, extend the tube, // grab it, retract, then recharge. You aim by steering (the cell faces its // heading); higher tiers reach farther, aim wider, and recharge faster. const isProb = (P === 'proboscis'), isJaw = (P === 'jaw'), isPseud = (P === 'pseudopod'); let got = window.CF.step(M.state, M.field, DIET, (isProb || isJaw || isPseud) ? 'none' : tier); let tube = ''; if (isProb) { const PROB = {none: 0, poor: 80, decent: 130, great: 180}; // strike reach const PCONE = {none: 0, poor: 20, decent: 28, great: 34}; // aim half-angle (deg) const PCD = {none: 0, poor: 70, decent: 48, great: 32}; // recharge ticks const PEXT = {none: 1, poor: 10, decent: 8, great: 6}; // extend ticks const PRET = 5; // retract ticks const reach = PROB[tier], cone = PCONE[tier], r0 = M.state.r || 30; const cx = M.state.x, cy = M.state.y, head = (M.state.deg || 0) * Math.PI / 180; const pr = M.probe = M.probe || {phase: 'idle', cd: 0, t: 0, len: 0, ang: 0, td: 0, target: null}; if (pr.cd > 0) { pr.cd--; } // IDLE + ready + reach: lock the nearest edible inside the FORWARD cone. if (pr.phase === 'idle' && reach > 0 && pr.cd <= 0) { let best = null, bd = reach + r0; const scan = (arr, kind) => { if (!window.CF.canEat(DIET, kind)) { return; } for (const o of arr) { const dx = o.x - cx, dy = o.y - cy, d = Math.hypot(dx, dy); if (d > r0 && d < bd) { const rel = Math.abs((((Math.atan2(dy, dx) - head) * 180 / Math.PI) % 360 + 540) % 360 - 180); if (rel <= cone) { bd = d; best = {o: o, arr: arr, kind: kind, ang: Math.atan2(dy, dx), d: d}; } } } }; scan(M.field.plankton, 'plankton'); scan(M.field.prey, 'meat'); if (best) { pr.phase = 'extending'; pr.t = 0; pr.ang = best.ang; pr.td = best.d; pr.target = best; } } if (pr.phase === 'extending') { pr.t++; pr.len = r0 + (pr.td - r0) * Math.min(1, pr.t / PEXT[tier]); if (pr.t >= PEXT[tier]) { // committed strike lands — grab it if still there const b = pr.target, ix = b ? b.arr.indexOf(b.o) : -1; if (ix >= 0) { b.arr.splice(ix, 1); got += (b.kind === 'meat' ? 5 : 1) * (window.CF.FEED[tier] || {rate: 1}).rate; } pr.phase = 'retracting'; pr.t = 0; } } else if (pr.phase === 'retracting') { pr.t++; pr.len = r0 + (pr.td - r0) * Math.max(0, 1 - pr.t / PRET); if (pr.t >= PRET) { pr.phase = 'idle'; pr.len = 0; pr.cd = PCD[tier]; pr.target = null; } } // draw: the live tube while striking, else a faint aim guide along heading. if (pr.len > 0) { const bx = cx + Math.cos(pr.ang) * r0, by = cy + Math.sin(pr.ang) * r0; const tx = cx + Math.cos(pr.ang) * pr.len, ty = cy + Math.sin(pr.ang) * pr.len; const w = tier === 'great' ? 6 : (tier === 'decent' ? 5 : 3.5); tube = '<line x1="' + bx.toFixed(1) + '" y1="' + by.toFixed(1) + '" x2="' + tx.toFixed(1) + '" y2="' + ty.toFixed(1) + '" stroke="#3399ff" stroke-width="' + w + '" stroke-linecap="round" opacity="0.95"/>' + '<circle cx="' + tx.toFixed(1) + '" cy="' + ty.toFixed(1) + '" r="8" fill="rgba(51,153,255,0.25)" stroke="#3399ff" stroke-width="2.5"/>'; } else if (reach > 0) { const ready = pr.cd <= 0, R1 = r0 + reach; const col = ready ? 'rgba(51,153,255,0.55)' : 'rgba(130,150,170,0.30)'; const ex = cx + Math.cos(head) * R1, ey = cy + Math.sin(head) * R1; const ep = (s) => { const a = head + s * cone * Math.PI / 180; return [cx + Math.cos(a) * R1, cy + Math.sin(a) * R1]; }; const p1 = ep(1), p2 = ep(-1); tube = '<path d="M' + cx.toFixed(1) + ' ' + cy.toFixed(1) + ' L' + p1[0].toFixed(1) + ' ' + p1[1].toFixed(1) + ' A' + R1.toFixed(1) + ' ' + R1.toFixed(1) + ' 0 0 0 ' + p2[0].toFixed(1) + ' ' + p2[1].toFixed(1) + ' Z" fill="' + (ready ? 'rgba(51,153,255,0.10)' : 'rgba(130,150,170,0.05)') + '" stroke="' + col + '" stroke-width="1.5" stroke-opacity="0.45"/>' + '<line x1="' + cx.toFixed(1) + '" y1="' + cy.toFixed(1) + '" x2="' + ex.toFixed(1) + '" y2="' + ey.toFixed(1) + '" stroke="' + col + '" stroke-width="2" stroke-dasharray="6 7"/>'; } M.probeReach = reach; M.probeCone = cone; } // JAW is a CARNIVORE melee LUNGE-STRIKE — you aim by steering your nose at live // prey; the cell darts forward, the mandibles GAPE then CRUNCH shut, popping the // meat caught in a short FRONT bite arc, then a committed recovery cooldown. // Distinct from the proboscis's long single snipe; skips CF.step's omni-eat. let jaw = ''; if (isJaw) { const JREACH = {none: 0, poor: 14, decent: 24, great: 34}; // bite range past cell edge (px) const JARC = {none: 0, poor: 32, decent: 46, great: 60}; // bite half-angle (deg) const JLUNGE = {none: 0, poor: 4, decent: 6, great: 8}; // forward velocity nudge const JCD = {none: 0, poor: 60, decent: 42, great: 28}; // recovery ticks after a snap const JDMG = {none: 0, poor: 0.6, decent: 1.0, great: 1.4}; // energy multiplier per kill const JMAXR = {none: 0, poor: 12, decent: 15, great: 99}; // max bitable prey radius (prey r is 8-16: poor takes only the smaller half, decent all but the biggest, great any) const JMAX = {none: 0, poor: 1, decent: 2, great: 3}; // prey crunched per snap (AoE cap) const JSLIP = 0.35, JGAPE = 3, JSNAP = 2, FLASH = 6; const reach = JREACH[tier], arc = JARC[tier], r0 = M.state.r || 30; const rate2 = (window.CF.FEED[tier] || {rate: 1}).rate, RB = window.CF.R || 280; const cx = M.state.x, cy = M.state.y, head = (M.state.deg || 0) * Math.PI / 180; const jw = M.jaw = M.jaw || {phase: 'idle', t: 0, cd: 0, hits: 0, lastBite: 0, killX: 0, killY: 0, locked: []}; if (jw.cd > 0) { jw.cd--; } const inArc = (o) => { const dx = o.x - cx, dy = o.y - cy, d = Math.hypot(dx, dy); if (d > r0 + reach + (o.r || 0)) { return false; } const rel = Math.abs((((Math.atan2(dy, dx) - head) * 180 / Math.PI) % 360 + 540) % 360 - 180); return rel <= arc; }; const bitable = (o) => (o.r || 8) <= JMAXR[tier]; // working radius gate (poor takes only smaller prey) // TRIGGER: idle + ready + reach; auto-fires on a bitable on-arc prey, or Space force-fires (may whiff). if (jw.phase === 'idle' && jw.cd <= 0 && reach > 0) { const forced = !!(window.__mlKeys || {})[' ']; // commit to the nearest bitable on-arc prey AT TRIGGER (up to JMAX), like the // proboscis locks its target — so a well-aimed bite lands instead of the prey // drifting out during the gape windup. Space force-fires even with nothing locked. let locked = []; if (window.CF.canEat(DIET, 'meat')) { // carnivore lock: plankton ignored const cand = []; for (const o of M.field.prey) { if (inArc(o) && bitable(o)) { cand.push({o: o, d: Math.hypot(o.x - cx, o.y - cy)}); } } cand.sort((a, b) => a.d - b.d); locked = cand.slice(0, JMAX[tier]).map(c => c.o); } if (locked.length > 0 || forced) { jw.phase = 'gape'; jw.t = 0; jw.hits = 0; jw.locked = locked; // bounded lunge: only nudge forward when well inside the dish, then clamp speed, // so a wall-bounce (vx,vy*=-0.4) can't spin the velocity-follow heading. if (Math.hypot(cx, cy) < RB * 0.82) { M.state.vx += Math.cos(head) * JLUNGE[tier]; M.state.vy += Math.sin(head) * JLUNGE[tier]; const spd = Math.hypot(M.state.vx, M.state.vy), CAP = 14; if (spd > CAP) { M.state.vx *= CAP / spd; M.state.vy *= CAP / spd; } } } } else if (jw.phase === 'gape') { jw.t++; if (jw.t >= JGAPE) { jw.phase = 'snap'; jw.t = 0; } } else if (jw.phase === 'snap') { jw.t++; if (jw.t >= JSNAP) { // the CRUNCH lands on the final snap tick: kill the committed targets let killed = 0; const locked = jw.locked || []; for (let i = 0; i < locked.length && killed < JMAX[tier]; i++) { const o = locked[i], ix = M.field.prey.indexOf(o); // re-derive each splice; guards stale refs if (ix < 0) { continue; } // target already gone if (tier === 'poor' && Math.random() < JSLIP) { continue; } // poor: bite slips off (cd still burns) M.field.prey.splice(ix, 1); killed++; got += 5 * JDMG[tier] * rate2; // per-kill energy (folds poor 0.6x / great 1.4x) jw.killX = o.x; jw.killY = o.y; } jw.hits = killed; jw.locked = []; jw.lastBite = killed > 0 ? FLASH : 0; jw.phase = 'idle'; jw.t = 0; jw.cd = JCD[tier]; // committed recovery (whiffs still cost) } } if (jw.lastBite > 0) { jw.lastBite--; } M.jawReach = reach; M.jawArc = arc; // ---- RENDER: bite-arc guide + animated mandibles + lunge streaks + crunch burst ---- const f = (v) => v.toFixed(1); const hx = cx + Math.cos(head) * r0, hy = cy + Math.sin(head) * r0; // mandible hinge at the leading edge if (reach > 0) { // bite-arc wedge: bright red when ready, dimmed grey during recovery (telegraph) const R1 = r0 + reach, ready = (jw.cd <= 0 && jw.phase === 'idle'); const fill = jw.cd > 0 ? 'rgba(130,150,170,0.05)' : (ready ? 'rgba(240,62,62,0.10)' : 'rgba(240,62,62,0.06)'); const edge = jw.cd > 0 ? 'rgba(130,150,170,0.30)' : 'rgba(240,62,62,0.55)'; const ep = (s) => { const a = head + s * arc * Math.PI / 180; return [cx + Math.cos(a) * R1, cy + Math.sin(a) * R1]; }; const p1 = ep(1), p2 = ep(-1), ex = cx + Math.cos(head) * R1, ey = cy + Math.sin(head) * R1; jaw += '<path d="M' + f(cx) + ' ' + f(cy) + ' L' + f(p1[0]) + ' ' + f(p1[1]) + ' A' + f(R1) + ' ' + f(R1) + ' 0 0 0 ' + f(p2[0]) + ' ' + f(p2[1]) + ' Z" fill="' + fill + '" stroke="none"/>' + '<line x1="' + f(cx) + '" y1="' + f(cy) + '" x2="' + f(ex) + '" y2="' + f(ey) + '" stroke="' + edge + '" stroke-width="2" stroke-dasharray="6 7"/>'; } // animated mandibles: rest slightly parted, GAPE wide during the dart, SLAM to ~0 on the snap. const baseDeg = Math.min(arc, 60); let gaFrac = 0.25; if (jw.phase === 'gape') { gaFrac = 0.25 + 0.75 * (jw.t / JGAPE); } else if (jw.phase === 'snap') { gaFrac = Math.max(0, 1 - jw.t / JSNAP); } const ga = baseDeg * gaFrac * Math.PI / 180; const L = reach * 0.9 + 12, w = arc >= 60 ? 6 : (arc >= 46 ? 4.5 : 3), teeth = arc >= 60 ? 3 : 2; const mandible = (sign) => { const a = head + sign * ga, perp = a + sign * Math.PI / 2; const tipx = hx + Math.cos(a) * L, tipy = hy + Math.sin(a) * L; const mcx = hx + Math.cos(a) * L * 0.5 + Math.cos(perp) * 6, mcy = hy + Math.sin(a) * L * 0.5 + Math.sin(perp) * 6; let m = '<path d="M' + f(hx) + ' ' + f(hy) + ' Q' + f(mcx) + ' ' + f(mcy) + ' ' + f(tipx) + ' ' + f(tipy) + '" fill="none" stroke="#f03e3e" stroke-width="' + w + '" stroke-linecap="round"/>'; for (let i = 1; i <= teeth; i++) { const tt = i / (teeth + 1), bx = hx + Math.cos(a) * L * tt, by = hy + Math.sin(a) * L * tt, tl = 4 + w * 0.4; m += '<line x1="' + f(bx) + '" y1="' + f(by) + '" x2="' + f(bx + Math.cos(perp) * tl) + '" y2="' + f(by + Math.sin(perp) * tl) + '" stroke="#f03e3e" stroke-width="' + (w * 0.7).toFixed(1) + '" stroke-linecap="round"/>'; } return m; }; if (reach > 0) { jaw += mandible(1) + mandible(-1); } // lunge streaks behind the cell during the dart (pure render flourish, no physics). if (jw.phase === 'gape') { for (let i = -1; i <= 1; i++) { const off = i * 8, bx = cx - Math.cos(head) * (r0 + 4) + Math.cos(head + Math.PI / 2) * off, by = cy - Math.sin(head) * (r0 + 4) + Math.sin(head + Math.PI / 2) * off; const tx = bx - Math.cos(head) * 14, ty = by - Math.sin(head) * 14; jaw += '<line x1="' + f(bx) + '" y1="' + f(by) + '" x2="' + f(tx) + '" y2="' + f(ty) + '" stroke="rgba(240,62,62,0.4)" stroke-width="2.5" stroke-linecap="round"/>'; } } // crunch burst (#ffd43b star) + white shock ring at the contact point on a kill. if (jw.lastBite > 0) { const op = jw.lastBite / 6, bx = jw.killX || hx, by = jw.killY || hy; for (let i = 0; i < 6; i++) { const a = i * Math.PI / 3, r1 = 4, r2 = 12 + (6 - jw.lastBite) * 2; jaw += '<line x1="' + f(bx + Math.cos(a) * r1) + '" y1="' + f(by + Math.sin(a) * r1) + '" x2="' + f(bx + Math.cos(a) * r2) + '" y2="' + f(by + Math.sin(a) * r2) + '" stroke="#ffd43b" stroke-width="2.5" stroke-linecap="round" opacity="' + op.toFixed(2) + '"/>'; } const rr = 10 + (6 - jw.lastBite) * 4; jaw += '<circle cx="' + f(bx) + '" cy="' + f(by) + '" r="' + f(rr) + '" fill="none" stroke="rgba(255,255,255,' + (op * 0.7).toFixed(2) + ')" stroke-width="2"/>'; } } // PSEUDOPOD is GRIP — it throws out arms that latch nearby prey, REEL them in and // engulf them at the mouth. Higher tiers reach farther, hold MORE prey at once, and // reel faster. (The DEFENSIVE twin — a predator latching YOU — is the game's Phase D // grapple.) Skips CF.step's omni-eat; intake comes from engulfing the reeled-in prey. let arms = ''; if (isPseud) { const PREACH = {none: 0, poor: 55, decent: 95, great: 140}; // grab reach past the cell edge const PGRIPS = {none: 0, poor: 1, decent: 2, great: 3}; // simultaneous grips const REEL = {none: 0, poor: 0.7, decent: 1.1, great: 1.6}; // reel-in acceleration const reach = PREACH[tier], maxG = PGRIPS[tier], r0 = M.state.r || 30; const cx = M.state.x, cy = M.state.y, rate2 = (window.CF.FEED[tier] || {rate: 1}).rate; const grips = M.grips = (M.grips || []).filter(o => M.field.prey.indexOf(o) >= 0); // drop vanished prey if (reach > 0 && window.CF.canEat(DIET, 'meat')) { while (grips.length < maxG) { // fill free grips with the nearest ungripped prey in reach let best = null, bd = r0 + reach; for (const o of M.field.prey) { if (grips.indexOf(o) >= 0) { continue; } const d = Math.hypot(o.x - cx, o.y - cy); if (d < bd) { bd = d; best = o; } } if (!best) { break; } grips.push(best); } } for (let i = grips.length - 1; i >= 0; i--) { const o = grips[i], dx = cx - o.x, dy = cy - o.y, d = Math.hypot(dx, dy) || 1; o.vx = (o.vx || 0) * 0.78 + (dx / d) * REEL[tier]; // reeled toward the mouth o.vy = (o.vy || 0) * 0.78 + (dy / d) * REEL[tier]; const spp = Math.hypot(o.vx, o.vy); if (spp > 4) { o.vx *= 4 / spp; o.vy *= 4 / spp; } o.r = Math.max(4, o.r - 0.12); // drained → visibly shrinks while held if (d < r0 * 0.7 || o.r <= 4) { // reeled to the mouth (or fully drained) → engulf const ix = M.field.prey.indexOf(o); if (ix >= 0) { M.field.prey.splice(ix, 1); got += 5 * rate2; } grips.splice(i, 1); } } M.pseudReach = reach; M.pseudGrips = maxG; for (const o of grips) { // render a tapered teal arm to each gripped prey + a draining ring const dx = o.x - cx, dy = o.y - cy, ang = Math.atan2(dy, dx); const bx = cx + Math.cos(ang) * r0, by = cy + Math.sin(ang) * r0, perp = ang + Math.PI / 2; const mx = (bx + o.x) / 2 + Math.cos(perp) * 7, my = (by + o.y) / 2 + Math.sin(perp) * 7; const w = tier === 'great' ? 7 : (tier === 'decent' ? 5.5 : 4); arms += '<path d="M' + bx.toFixed(1) + ' ' + by.toFixed(1) + ' Q' + mx.toFixed(1) + ' ' + my.toFixed(1) + ' ' + o.x.toFixed(1) + ' ' + o.y.toFixed(1) + '" fill="none" stroke="#12b886" stroke-width="' + w + '" stroke-linecap="round" opacity="0.9"/>' + '<circle cx="' + o.x.toFixed(1) + '" cy="' + o.y.toFixed(1) + '" r="' + (o.r + 3).toFixed(1) + '" fill="none" stroke="#63e6be" stroke-width="2" stroke-dasharray="3 3" opacity="0.85"/>'; } if (reach > 0 && !grips.length) { // idle: show the grab radius arms += '<circle cx="' + cx.toFixed(1) + '" cy="' + cy.toFixed(1) + '" r="' + (r0 + reach).toFixed(1) + '" fill="rgba(18,184,134,0.05)" stroke="#12b886" stroke-width="1.5" stroke-dasharray="6 7" opacity="0.5"/>'; } } M.energy += got; M.recent.push(got); if (M.recent.length > 30) { M.recent.shift(); } const rate = M.recent.reduce((a, b) => a + b, 0); const sp = (mv.specimens.find(s => s.value === spec) || {}); const host = document.getElementById('fl-__P__-arena'); if (host) { host.innerHTML = window.CF.render(M.state, M.field, sp.img || '', DIET, (isProb || isJaw || isPseud) ? 'none' : tier).replace('</svg>', tube + jaw + arms + '</svg>'); } let stats; if (isProb) { const pb = M.probe || {phase: 'idle', cd: 0}; const strike = pb.phase === 'extending' ? 'STRIKING' : (pb.phase === 'retracting' ? 'retracting' : (pb.cd > 0 ? 'recharge ' + pb.cd : 'ready')); stats = 'DIET ' + DIET + ' (eats __EDIBLE__)\\n' + 'TIER ' + tier + '\\n' + 'REACH ' + (M.probeReach || 0).toFixed(0) + ' (aimed, cone \\u00b1' + (M.probeCone || 0) + '\\u00b0)\\n' + 'STRIKE ' + strike + '\\n' + 'INTAKE/s ' + rate.toFixed(1) + '\\n' + 'ENERGY ' + M.energy.toFixed(0); } else if (isJaw) { const jb = M.jaw || {phase: 'idle', cd: 0, t: 0, hits: 0, lastBite: 0}; const bite = jb.phase === 'gape' ? 'LUNGING' : jb.phase === 'snap' ? 'CHOMP!' : ((jb.lastBite > 0 && jb.hits > 0) ? 'SNAP! +' + jb.hits : (jb.cd > 0 ? 'recharge ' + jb.cd : 'ready')); stats = 'DIET ' + DIET + ' (eats __EDIBLE__)\\n' + 'TIER ' + tier + '\\n' + 'REACH ' + (M.jawReach || 0).toFixed(0) + ' (melee, bite \\u00b1' + (M.jawArc || 0) + '\\u00b0)\\n' + 'BITE ' + bite + '\\n' + 'INTAKE/s ' + rate.toFixed(1) + '\\n' + 'ENERGY ' + M.energy.toFixed(0); } else if (isPseud) { const ng = (M.grips || []).length; stats = 'DIET ' + DIET + ' (eats __EDIBLE__)\\n' + 'TIER ' + tier + '\\n' + 'REACH ' + (M.pseudReach || 0).toFixed(0) + ' (grip, holds ' + (M.pseudGrips || 0) + ')\\n' + 'GRIPPED ' + ng + ' / ' + (M.pseudGrips || 0) + '\\n' + 'INTAKE/s ' + rate.toFixed(1) + '\\n' + 'ENERGY ' + M.energy.toFixed(0); } else { stats = 'DIET ' + DIET + ' (eats __EDIBLE__)\\n' + 'TIER ' + tier + '\\n' + 'REACH ' + window.CF.reach(tier).toFixed(0) + '\\n' + 'INTAKE/s ' + rate.toFixed(1) + '\\n' + 'ENERGY ' + M.energy.toFixed(0); } return [stats, FFEEL[tier] || '']; } """.replace("__P__", P).replace("__DIET__", diet).replace("__EDIBLE__", edible) \ .replace("__FFEEL__", json.dumps(_FEED_FEEL)) clientside_callback( loop_js, Output(f"fl-{P}-stats", "children"), Output(f"fl-{P}-feel", "children"), Input(f"fl-{P}-tick", "n_intervals"), Input(f"fl-{P}-spec", "value"), Input(f"fl-{P}-tier", "value"), State(f"fl-{P}-mv", "data"), State(f"fl-{P}-joy", "angle"), State(f"fl-{P}-joy", "distance"), )
# skewed mutation odds + roll (diet -> which feed part) roll_js = """ function(spec, nRoll, diet) { const nu = window.dash_clientside.no_update; if (!diet) { return nu; } const P = '__P__'; const nature = (diet.specimenDiet || {})[spec] || 'omnivore'; const odds = (diet.skew || {})[nature] || {}; const parts = ['filter', 'jaw', 'proboscis']; parts.forEach(fp => { const pct = Math.round((odds[fp] || 0) * 100); const bar = document.getElementById('fl-' + P + '-bar-' + fp); const lab = document.getElementById('fl-' + P + '-pct-' + fp); const cell = document.getElementById('fl-' + P + '-odd-' + fp); if (bar) { bar.style.height = Math.max(3, pct) + '%'; } if (lab) { lab.textContent = pct + '%'; } if (cell) { cell.classList.remove('eye-win'); } }); const ctx = window.dash_clientside.callback_context; const trig = (ctx.triggered[0] || {}).prop_id || ''; if (trig.indexOf('-roll') < 0) { return nu; } // odds refresh only let r = Math.random(), acc = 0, got = parts[0]; for (const fp of parts) { acc += (odds[fp] || 0); if (r <= acc) { got = fp; break; } } const tiers = ['poor', 'decent', 'great'], tier = tiers[Math.random() * 3 | 0]; const cell = document.getElementById('fl-' + P + '-odd-' + got); if (cell) { cell.classList.add('eye-win'); } const partDiet = (diet.partDiet || {})[got]; const cross = partDiet && partDiet !== nature && nature !== 'omnivore'; const names = {filter: 'Filter mouth', jaw: 'Jaw', proboscis: 'Proboscis'}; return '\\ud83e\\uddec ' + nature + ' \\u2192 ' + names[got] + ' (' + tier + ')' + (cross ? ' \\u2014 a rare cross-diet mutation!' : ''); } """.replace("__P__", P) clientside_callback( roll_js, Output(f"fl-{P}-roll-result", "children"), Input(f"fl-{P}-spec", "value"), Input(f"fl-{P}-roll", "n_clicks"), State(f"fl-{P}-diet", "data"), )
# fill tier reference schematics on mount clientside_callback( """ function(_n) { if (!window.CV) { return window.dash_clientside.no_update; } document.querySelectorAll('[id*="ml-card"]').forEach(host => { let id = null; try { id = JSON.parse(host.id); } catch (e) { return; } if (id.part !== '__P__') { return; } host.innerHTML = (window.CV.renderAdaptPreview || window.CV.renderPart)(id.part, id.tier); }); return ''; } """.replace("__P__", P), Output(f"fl-{P}-sink", "data"), Input(f"fl-{P}-init", "n_intervals"), )
return component ```
:defaultExpanded: false :withExpandedButton: true
---
*Source: /adaptation-jaw*
Note for AI agents: This is the static, prerendered view of an interactive Dash application served because we detected a non-JS user agent. Full prose docs:
- /adaptation-jaw/llms.txt — LLM-friendly documentation
- /sitemap.xml
- /robots.txt