Skip to content

Commit a986591

Browse files
committed
fix: signal search viability -- emission_rate range + mass floor
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
1 parent c9c1da6 commit a986591

6 files changed

Lines changed: 22 additions & 19 deletions

File tree

src/biota/search/descriptors.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -698,9 +698,9 @@ def compute_emission_activity(trace: RolloutTrace) -> float:
698698
return 0.0
699699
mean_activity = float(trace.signal_emission_history.mean())
700700
# Normalizer calibrated to mean(G_pos * rate): G_pos averaged over grid
701-
# is typically 0.0-0.1; rate in [0.001, 0.05]. So mean(G_pos * rate)
702-
# spans ~0.00005-0.003 in practice. 0.001 gives good spread.
703-
return float(np.clip(mean_activity / 0.001, 0.0, 1.0))
701+
# is typically 0.0-0.1; rate in [0.0001, 0.01]. So mean(G_pos * rate)
702+
# spans ~0.00001-0.0005 in practice. 0.0002 gives good spread.
703+
return float(np.clip(mean_activity / 0.0002, 0.0, 1.0))
704704

705705

706706
def compute_receptor_sensitivity(trace: RolloutTrace) -> float:

src/biota/search/params.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def width(self) -> float:
6868
SIGNAL_PARAMETER_SPECS: tuple[ParameterSpec, ...] = (
6969
ParameterSpec("emission_vector", "c", 0.0, 1.0, 0.1),
7070
ParameterSpec("receptor_profile", "c_sym", -1.0, 1.0, 0.2),
71-
ParameterSpec("emission_rate", "scalar", 0.001, 0.05, 0.005),
71+
ParameterSpec("emission_rate", "scalar", 0.0001, 0.01, 0.001),
7272
ParameterSpec("decay_rates", "c", 0.0, 0.9, 0.05),
7373
ParameterSpec("alpha_coupling", "scalar", -1.0, 1.0, 0.15),
7474
ParameterSpec("beta_modulation", "scalar", -1.0, 1.0, 0.15),

src/biota/search/quality.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@
5757
LOCALIZED_THRESHOLD = 0.6
5858
PERSISTENT_DESCRIPTOR_DRIFT = 0.2
5959
# Signal creatures: mass field must not drop below this fraction of initial_mass
60-
# even when total (mass + signal) is conserved.
61-
CREATURE_MASS_FLOOR = 0.2
60+
# even when total (mass + signal) is conserved. Set low (0.05) so that
61+
# signal_retention in the quality metric does the selection work rather
62+
# than this hard floor killing viable emitters.
63+
CREATURE_MASS_FLOOR = 0.05
6264

6365
# Quality component weights
6466
_W_COMPACT_BASE = 0.6

src/biota/search/result.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ class _SignalParams(TypedDict, total=False):
2727
receptor_profile: (C,) floats in [-1, 1]. Dot product with the convolved
2828
signal field produces a scalar growth boost. Negative
2929
values produce an inhibitory (aversive) response.
30-
emission_rate: Scalar in [0.001, 0.05]. Fraction of positive growth
31-
activity converted to signal per step.
30+
emission_rate: Scalar in [0.0001, 0.01]. Fraction of positive growth
31+
activity converted to signal per step. Kept low to
32+
prevent catastrophic mass bleed over 500+ steps.
3233
decay_rates: (C,) floats in [0, 0.9]. Per-channel decay rate.
3334
alpha_coupling: Scalar in [-1, 1]. Reception-to-growth coupling strength.
3435
Positive = chemotaxis (grow into favorable signal gradients,

tests/search/test_descriptors.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -580,8 +580,8 @@ def test_emission_activity_zero_for_empty_history() -> None:
580580

581581

582582
def test_emission_activity_scales_with_emission() -> None:
583-
low = _make_signal_trace(emission_history=np.full(TRACE_LEN, 0.0002, dtype=np.float32))
584-
high = _make_signal_trace(emission_history=np.full(TRACE_LEN, 0.0008, dtype=np.float32))
583+
low = _make_signal_trace(emission_history=np.full(TRACE_LEN, 0.00005, dtype=np.float32))
584+
high = _make_signal_trace(emission_history=np.full(TRACE_LEN, 0.00015, dtype=np.float32))
585585
assert compute_emission_activity(low) < compute_emission_activity(high)
586586

587587

tests/test_signal.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def test_signal_vector_lengths() -> None:
108108

109109
def test_emission_rate_in_range() -> None:
110110
params = _make_signal_params()
111-
assert 0.001 <= params["emission_rate"] <= 0.05 # type: ignore[typeddict-item]
111+
assert 0.0001 <= params["emission_rate"] <= 0.01 # type: ignore[typeddict-item]
112112

113113

114114
def test_decay_rates_in_range() -> None:
@@ -150,7 +150,7 @@ def test_mutate_preserves_signal_keys() -> None:
150150
assert len(child["emission_vector"]) == SIGNAL_CHANNELS # type: ignore[typeddict-item]
151151
assert len(child["receptor_profile"]) == SIGNAL_CHANNELS # type: ignore[typeddict-item]
152152
assert len(child["decay_rates"]) == SIGNAL_CHANNELS # type: ignore[typeddict-item]
153-
assert 0.001 <= child["emission_rate"] <= 0.05 # type: ignore[typeddict-item]
153+
assert 0.0001 <= child["emission_rate"] <= 0.01 # type: ignore[typeddict-item]
154154
assert -1.0 <= child["alpha_coupling"] <= 1.0 # type: ignore[typeddict-item]
155155
assert -1.0 <= child["beta_modulation"] <= 1.0 # type: ignore[typeddict-item]
156156

@@ -213,13 +213,13 @@ def test_alive_filter_fails_when_creature_mass_collapses() -> None:
213213
"""Creature converted almost all mass to signal -- below creature floor."""
214214
trace = _minimal_trace()
215215
initial_mass = 100.0
216-
# Creature mass dropped to 5% of initial -- below CREATURE_MASS_FLOOR (20%)
216+
# Creature mass dropped to 3% of initial -- below CREATURE_MASS_FLOOR (5%)
217217
eval_input = RolloutEvaluation(
218218
initial_mass=initial_mass,
219-
final_mass=5.0,
219+
final_mass=3.0,
220220
trace=trace,
221221
initial_total=initial_mass,
222-
final_signal_mass=95.0,
222+
final_signal_mass=97.0,
223223
)
224224
result = evaluate(eval_input)
225225
assert result.rejection_reason == "dead"
@@ -240,15 +240,15 @@ def test_alive_filter_fails_when_total_mass_lost() -> None:
240240

241241

242242
def test_alive_filter_signal_creature_mass_floor_is_stricter() -> None:
243-
"""Signal creature mass floor is 0.2 (vs no effective floor for non-signal)."""
243+
"""Signal creature mass floor is 0.05 -- only truly collapsed creatures fail."""
244244
trace = _minimal_trace()
245-
# 15% of initial_mass remaining -- above old 10% floor, below new 20% floor
245+
# 4% of initial_mass remaining -- below CREATURE_MASS_FLOOR (5%)
246246
eval_input = RolloutEvaluation(
247247
initial_mass=100.0,
248-
final_mass=15.0,
248+
final_mass=4.0,
249249
trace=trace,
250250
initial_total=100.0,
251-
final_signal_mass=85.0, # total conserved
251+
final_signal_mass=96.0, # total conserved
252252
)
253253
result = evaluate(eval_input)
254254
assert result.rejection_reason == "dead"

0 commit comments

Comments
 (0)