Skip to content

Release/v3.5.0#2

Merged
rkv0id merged 8 commits intomainfrom
release/v3.5.0
Apr 19, 2026
Merged

Release/v3.5.0#2
rkv0id merged 8 commits intomainfrom
release/v3.5.0

Conversation

@rkv0id
Copy link
Copy Markdown
Owner

@rkv0id rkv0id commented Apr 18, 2026

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 in SIGNAL_PARAMETER_SPECS. Three signal-only descriptors: emission_activity, receptor_sensitivity, signal_retention. signal_only validation in loop.py raises at startup if signal-only descriptors used without --signal-field. step_with_signal_diagnostics() added to FlowLenia to 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_FLOOR 0.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.

rkv0id added 3 commits April 18, 2026 20:42
…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.
@rkv0id rkv0id self-assigned this Apr 18, 2026
rkv0id added 5 commits April 18, 2026 21:30
…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
@rkv0id rkv0id merged commit ff2cb8b into main Apr 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant