Merged
Conversation
…oupling + adaptive signal)
sim/flowlenia.py
Params gains two new optional signal parameters:
alpha_coupling: float | None in [-1, 1]
Reception-to-growth coupling strength. Multiplicative, applied globally
(not ownership-gated): G *= (1 + alpha * reception).clamp(min=0)
Positive = chemotaxis: grow into favorable signal gradients, including
other species territory -- the mechanism that enables cross-species mass
exchange and predation.
Negative = chemorepulsion: grow away from signal gradients.
Zero = previous behavior (no cross-species growth coupling).
beta_modulation: float | None in [-1, 1]
Adaptive emission: modulates emission_rate based on received signal.
effective_rate = clip(emission_rate * (1 + beta * received_mean), 0, 0.1)
Positive = quorum sensing: amplify emission when stimulated (positive
chemical feedback, cascade behavior at population density thresholds).
Negative = feedback inhibition: suppress emission when stimulated
(self-limiting, stabilizes coexistence by preventing runaway emission).
Zero = static emission rate (previous behavior).
Reception now computed before emission so beta_modulation reads the
pre-emission field. Growth multiplier clamped to [0, inf) to prevent
growth reversal.
sim/localized.py
Per-species alpha_coupling and beta_modulation applied in the hetero loop.
receptor_response_s computed once and reused for both alpha coupling and
beta modulation. Alpha coupling applied globally (not ownership-weighted)
so species B can grow into species A's territory when B's receptor profile
aligns with A's emitted signal -- this is the predation pathway.
search/params.py
alpha_coupling and beta_modulation added to SIGNAL_PARAMETER_SPECS:
alpha_coupling: scalar [-1, 1], sigma 0.15
beta_modulation: scalar [-1, 1], sigma 0.15
Both added to sample_random, mutate, in_range.
search/result.py
_SignalParams extended with alpha_coupling: float and beta_modulation: float.
Docstrings describe ecological roles for all signal parameters.
search/rollout.py
_params_dict_to_tensors extracts both new scalar params.
ecosystem/run.py
_params_from_creature extracts both new scalar params.
viz/render.py
_serialize_params includes alpha_coupling and beta_modulation for creature modal.
viz/templates/archive.html
Creature modal signal section: α_coupling and β_modulation shown inline
with emission_rate and signal_kernel_r on the same scalar row.
tests/test_signal.py
New keys added to: has_all_signal_keys, no_signal_lacks_keys, vector lengths,
mutate_preserves, _make_fl helper.
New range tests: test_alpha_coupling_in_range, test_beta_modulation_in_range.
New physics tests: test_alpha_zero_unchanged_growth,
test_positive_alpha_amplifies_growth_in_favorable_region,
test_beta_zero_uses_base_emission_rate.
406 passed, 0 errors, 0 warnings
descriptors.py
Descriptor gains signal_only: bool = False field. When True, the descriptor
requires a signal-enabled rollout; the loop validates this at startup.
RolloutTrace gains three optional signal history fields:
signal_emission_history: (T,) mean emission activity per trace step
signal_reception_history: (T,) mean |reception response| per trace step
signal_retention: float final_mass / initial_mass scalar
slice() updated to preserve all three fields.
Three new signal descriptor compute functions:
compute_emission_activity: mean emission over trace tail, normalized by
0.05 (max emission_rate). Measures dynamic emission, not just the
emission_rate parameter. Zero for non-signal traces.
compute_receptor_sensitivity: mean |reception response| over trace tail,
normalized by 0.5. Measures chemical responsiveness. Zero for non-signal.
compute_signal_retention: final_mass / initial_mass clipped to [0,1].
High = conservative emitter, low = aggressive broadcaster. 1.0 for
non-signal traces (no mass lost).
All three registered in REGISTRY with signal_only=True.
Ecological roles: broadcaster (high emit, low retain), listener (low emit,
high sensitivity), predator candidate (high emit + high sensitivity + high alpha).
rollout.py
emission_np and reception_np buffers allocated only for signal rollouts.
Per-step: emission activity = G_pos_mean * rate; reception = |dot(signal_mean,
receptor_profile)|. Both captured into trace tail. signal_retention_val
computed as final_mass / initial_mass. All three passed to RolloutTrace.
loop.py
After resolve_descriptors(), validate that no signal_only descriptor is
active when signal_field=False. Raises ValueError with message pointing
to --signal-field if violated.
tests
test_descriptors.py: 15 new tests for all three signal descriptors covering
zero-for-non-signal, zero-for-empty, scaling, clipping, unit interval.
test_descriptor_registry.py: registry count updated 15→18; expected names
set updated with emission_activity, receptor_sensitivity, signal_retention.
test_loop.py: 3 new validation tests covering error-on-signal-only-without-
signal-field, no-error-with-signal-field, no-error-for-standard-descriptors.
422 passed, 0 errors, 0 warnings
quality.py Signal run weights adjusted: stability 0.3→0.2, retention 0.2→0.3. Creatures with nonzero beta_modulation produce mild descriptor variance during solo rollout due to the signal feedback loop -- the old stability weight penalised this even though it is ecologically desirable behaviour. Retention at 0.3 more directly rewards creatures that don't bleed mass through adaptive emission, creating better selection pressure for the alpha/beta parameter space we now search. Non-signal weights unchanged (0.6 compactness / 0.4 stability). docs/signal-field.svg Full redraw showing alpha and beta in the diagram. Eight nodes total: left column (mass → convolve → growth G → α coupling → emission → mass t+1) and right column (signal → convolve → reception → β modulation → decay → signal t+1). Three cross-arrows: α (reception → growth multiplier, teal dashed), β (mean reception → modulate emission rate, purple dashed), emission drain (amber dashed). Parameter legend lists all six key signal params with ranges and ecological roles (chemotaxis/repulsion, quorum/inhibition). index.html (System tab) Signal field parameter math block: alpha_coupling [-1,1] and beta_modulation [-1,1] rows added with ecological descriptions. Physics step-by-step updated: steps (4) and (5) now describe alpha multiplicative coupling and beta adaptive emission modulation. Descriptors grid: three new signal-only descriptor cards with teal/purple SIGNAL ONLY badges -- emission_activity, receptor_sensitivity, signal_retention -- each with ecological interpretation and zero-for-non-signal note. README.md Signal parameter table: alpha_coupling and beta_modulation rows added. Physics paragraph: updated to describe alpha chemotaxis/chemorepulsion and beta quorum sensing/feedback inhibition. Quality metric: signal weights updated to match (0.5/0.2/0.3). Test count: 422.
…ceptor_sensitivity
Two bugs caused all 300 rollouts to land in 1 archive cell.
flowlenia.py
Add step_with_signal_diagnostics() alongside step(). Returns the same
(new_A, new_signal) result plus two diagnostic scalars computed as
natural byproducts of the signal physics block:
emission_activity: mean(G_pos * effective_rate) over the grid --
actual per-cell emission this step, correctly
derived from the growth field (not the mass field).
receptor_sensitivity: mean(|receptor_response|) over the grid --
spatially-resolved reception after convolution,
not the spatially-averaged approximation.
Zero code duplication, zero performance cost for non-diagnostic paths.
rollout.py
Signal rollout loop calls step_with_signal_diagnostics() instead of
step() when capturing descriptor histories. Removes the two broken
approximations:
Bug 1: emission was computed as mean(mass_field) * emission_rate.
mass_field is always positive and nearly constant for viable
solitons -- so every creature scored identically on emission_activity.
Bug 2: reception was computed as |dot(mean_signal, receptor_profile)|
where mean_signal is the spatially-averaged signal field. This is
near-zero for almost all of a solo rollout (signal starts tiny,
builds slowly) -- so every creature scored ~0 on receptor_sensitivity.
descriptors.py
Normalizers recalibrated against corrected measurements:
emission_activity: 0.05 → 0.001
mean(G_pos * rate) spans ~0.00005-0.003 in practice (G_pos averaged
over grid ~0.0-0.1; rate in [0.001, 0.05]).
receptor_sensitivity: 0.5 → 0.005
mean|receptor_response| spans ~0.00001-0.003 (convolved signal
builds from near-zero; spatial receptor response is small but real).
tests/search/test_descriptors.py
Test input values updated to stay within the new normalizer ranges
so scaling tests remain meaningful rather than clipping to 1.0.
422 passed, 0 errors, 0 warnings
ecosystem/result.py mass_history was never written to to_summary_dict() -- only species_mass_history was serialized. build_index read an empty list and produced no SVG path, showing "total mass conserved across run" even when mass dropped from 6789 to 303 au in a signal run. Fixed: mass_history added to the measures dict in to_summary_dict(). scripts/build_index.py Added backward-compat fallback: if mass_history is absent (older runs without the fix), reconstruct it by summing species_mass_history rows. viz/templates/ecosystem.html GIF sync: on tab switch, both mass and signal GIFs now restart from frame 0 via src-clear trick (img.src = ''; img.src = src) before the opacity swap. Switching mass↔signal always shows the same simulation moment on both sides. viz/templates/index.html receptor_sensitivity descriptor card changed from purple to teal to match emission_activity and signal_retention -- all three are signal-only descriptors and should share the same visual treatment. docs/signal-field.svg Full redraw: tighter font sizes (8-9.5px), proper label/mono/dim hierarchy, all text fits within containers, cross-arrows labeled concisely, footer note on conservation. No text overflow anywhere.
The actual root cause of 1-cell archives: CREATURE_MASS_FLOOR = 0.2 was killing ~98% of randomly sampled signal creatures before they could enter the archive. final_signal ≈ 0 in the diagnostic confirmed the mass wasn't going into the signal field -- it was burning through the emission→decay cycle. The hard floor was doing nothing useful; signal_retention in the quality metric already penalizes mass bleed. quality.py CREATURE_MASS_FLOOR: 0.2 → 0.05. Only truly collapsed creatures (essentially zero mass) are now rejected by the floor. The retention term at weight 0.3 in the quality metric provides the actual selection pressure against mass bleed, as intended. search/params.py emission_rate range: [0.001, 0.05] → [0.0001, 0.01]. At 0.05 over 500 steps with any decent growth activity a creature bleeds to death. 0.01 is still high enough for meaningful signal dynamics. search/descriptors.py emission_activity normalizer: 0.001 → 0.0002, recalibrated for the new emission_rate ceiling of 0.01. search/result.py emission_rate docstring updated to reflect new range. tests/test_signal.py Alive filter tests updated: floor is now 0.05 not 0.2. Test values adjusted to sit below (3%, 4%) vs above (15%) the new threshold. tests/search/test_descriptors.py Scaling test values adjusted for new normalizer. 422 passed, 0 errors, 0 warnings
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds chemical coupling and adaptive signal to the signal field, three signal-only behavioral descriptors, and fixes the signal search viability issues discovered during testing.
Signal physics (
flowlenia.py,localized.py):alpha_coupling [-1,1]applies reception multiplicatively to growth globally (not ownership-gated) -- the cross-species predation pathway.beta_modulation [-1,1]modulates emission rate based on received signal (quorum sensing / feedback inhibition). Both searchable.Search (
params.py,result.py,rollout.py): alpha and beta inSIGNAL_PARAMETER_SPECS. Three signal-only descriptors:emission_activity,receptor_sensitivity,signal_retention.signal_onlyvalidation inloop.pyraises at startup if signal-only descriptors used without--signal-field.step_with_signal_diagnostics()added toFlowLeniato correctly capture G_pos and receptor_response from inside the step -- fixing two bugs where descriptors measured proxies (mass field instead of growth field, spatially-averaged signal instead of convolved field).Bug fixes:
CREATURE_MASS_FLOOR0.2 → 0.05 (old value killed 98% of signal creatures). Signal descriptor normalizers calibrated against real rollout distributions.Docs and viewer: signal-field.svg redrawn with alpha/beta. Index.html updated with new descriptors, physics description, quality weights. Archive run cards show TORUS/SIGNAL badges. Mass chart fixed (was missing from homogeneous runs). GIF sync on Mass/Signal tab switch. Descriptor library 15 → 18.
422 tests, 0 errors, 0 warnings.