Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions assets/shaders/blur/fragment.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ void main(){
// filter_color += 0.25 * weight[i] * texture2D(u_sample2D, v_texCoord0 - vec2(offset[j], 0.0) / u_resolution) * v_color;
// }
// }
float skip = 1;
float skip = 1.0;
vec4 filter_color = vec4(0.);
int filter_radius = 3; // int(u_blurRadius);
float filter_radius = 3.0; // int(u_blurRadius);
float sd = filter_radius * skip;
float k = 1.0 / (2.0 * sd * sd);
float sum = 0.0;
Expand Down
2 changes: 1 addition & 1 deletion assets/shaders/chemical/fragment.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ void main(){
float skipX = 0.3 * u_blurAmount;
float skipY = 0.3 * u_blurAmount * u_resolution.y / u_resolution.x;
vec4 filter_color = vec4(0.);
int filter_radius = 8;
float filter_radius = 8.0;
float sd = filter_radius * (skipX + skipY) / 2.0;
float k = 1.0 / (2.0 * sd * sd);
float sum = 0.0;
Expand Down
2 changes: 1 addition & 1 deletion assets/shaders/pause/fragment.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ void main(){
float skipX = 0.3 * u_blurAmount;
float skipY = 0.3 * u_blurAmount * u_resolution.y / u_resolution.x;
vec4 filter_color = vec4(0.);
int filter_radius = 8;
float filter_radius = 8.0;
float sd = filter_radius * (skipX + skipY) / 2.0;
float k = 1.0 / (2.0 * sd * sd);
float sum = 0.0;
Expand Down
4 changes: 2 additions & 2 deletions core/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//sourceCompatibility = 1.8
sourceCompatibility = 23
targetCompatibility = 23
sourceCompatibility = 21
targetCompatibility = 21
dependencies {
implementation 'junit:junit:4.13.1'
}
Expand Down
4 changes: 4 additions & 0 deletions core/src/com/protoevo/biology/BurstRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ public void burst() {
childParticle.applyImpulse(dir.scl(.005f));

child.setGeneration(parent.getGeneration() + 1);
// Inherit phylogeny tags so the lineage view can group
// descendants under their founder ancestor.
child.setLineageId(parent.getLineageId());
child.setParentId(parent.getId());
allocateChildResources(child, p);

angle += 2 * Math.PI / nChildren;
Expand Down
3 changes: 2 additions & 1 deletion core/src/com/protoevo/biology/CauseOfDeath.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public enum CauseOfDeath {
OVERCROWDING("overcrowding", false),
MEAT_DECAY("decay", true),
HYPOTHERMIA("hypothermia", false),
HYPERTHERMIA("hyperthermia", false);
HYPERTHERMIA("hyperthermia", false),
STARVATION("starvation", false);

private final String reason;
private final boolean debug;
Expand Down
30 changes: 28 additions & 2 deletions core/src/com/protoevo/biology/Food.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ public void addSimpleMass(float m) {
}

public void addEnergy(float energy) {
mass += energy;
// Was: `mass += energy;` — clearly a typo bug. Combined with `addSimpleMass`
// double-adding mass and `getEnergy` re-releasing the stored value every
// tick at meat density (3e5×), this let cells eat their own corpses for
// a 6-figure-multiplier net energy gain. Storing in the right field now.
this.energy += energy;
}

public void subtractSimpleMass(float m) {
Expand Down Expand Up @@ -99,8 +103,30 @@ public void addComplexMoleculeMass(ComplexMolecule molecule, float mass) {
complexMoleculeMasses.put(molecule, currentMass + mass);
}

/**
* Release energy proportional to the mass `m` just extracted. Consumes the
* proportional fraction of the stored `energy` field, plus density × m for
* the meat/plant intrinsic energy.
*
* Caller in Cell.digest subtracts mass FIRST, then calls this with the
* removed mass. So `mass` here is the *remaining* mass after extraction;
* `mass + m` reconstructs the pre-extraction total.
*
* Was: `return energy + density * m;` — released the whole stored energy
* every tick, never decrementing it. Combined with the addEnergy/mass-typo
* bug above, this was the main "eat your own meat for infinite energy" loop.
*/
public float getEnergy(float m) {
return energy + type.getEnergyDensity() * m;
float prevMass = mass + m;
float storedReleased;
if (prevMass <= 0f) {
// Edge case: no mass left to attribute the release to. Take it all.
storedReleased = energy;
} else {
storedReleased = energy * (m / prevMass);
}
energy = Math.max(0f, energy - storedReleased);
return storedReleased + type.getEnergyDensity() * m;
}

@Override
Expand Down
71 changes: 64 additions & 7 deletions core/src/com/protoevo/biology/cells/Cell.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ public abstract class Cell implements Serializable, Coloured, Spawnable {

private Particle particle;
private Environment environment;
// Phylogeny: each cell carries a lineage tag inherited from its parent
// (or assigned new at initial spawn for "founder" lineages). Combined
// with parentId, we can build a full ancestry tree without snapshotting
// every cell — most lineages die out within a few generations, only a
// few "successful" founders accumulate descendants. Default 0 means
// "uninitialised"; assigned on first creation through Environment.
private long lineageId = 0L;
private long parentId = 0L;
private final Colour healthyColour = new Colour(Color.WHITE);
private final Colour fullyDegradedColour = new Colour(Color.WHITE);
private final Colour currentColour = new Colour();
Expand Down Expand Up @@ -208,6 +216,28 @@ public void decayResources(float delta) {
for (ComplexMolecule molecule : availableComplexMolecules.keySet()) {
depleteComplexMolecule(molecule, delta * Environment.settings.cell.complexMoleculeDecayRate.get());
}

applyStarvationDamage(delta);
}

/**
* Damage health when energy is below the starvation threshold. Without
* this, the energyDecayRate lever (the homeostat's main starvation knob)
* has no death pathway — cells just stop growing at 0 energy and live
* forever, so the population could only drop via spike damage or void
* deaths. Adding this gives the homeostat real authority and makes
* "food is scarce" a survivable challenge rather than a free pass.
*/
private void applyStarvationDamage(float delta) {
float cap = getAvailableEnergyCap();
if (cap <= 0f)
return;
float threshold = Environment.settings.cell.starvationThresholdFraction.get() * cap;
if (energyAvailable >= threshold)
return;
float deficit = 1f - (energyAvailable / threshold); // 0..1
float rate = Environment.settings.cell.starvationDeathRate.get();
damage(delta * deficit * rate, CauseOfDeath.STARVATION);
}

public void voidDamage(float delta) {
Expand Down Expand Up @@ -342,15 +372,23 @@ public void eat(Cell engulfed, float extraction) {
engulfed.removeMass(removeMultiplier * extractedMass, CauseOfDeath.EATEN);

Food food;
if (foodToDigest.containsKey(foodType))
if (foodToDigest.containsKey(foodType)) {
food = foodToDigest.get(foodType);
else {
food.addSimpleMass(extractedMass);
} else {
// Constructor already sets mass=extractedMass; previously this branch
// then ALSO called addSimpleMass(extractedMass) below, doubling the
// mass of every fresh food chunk.
food = new Food(extractedMass, foodType);
foodToDigest.put(foodType, food);
}

food.addSimpleMass(extractedMass);
food.addEnergy(engulfed.getEnergyAvailable() * extraction);
// Energy must be transferred, not duplicated: deplete the victim's
// stockpile by the same amount we hand to the food chunk. Without this,
// `cell A energy -> meat -> cell B energy` was a free copy.
float energyTransferred = engulfed.getEnergyAvailable() * extraction;
engulfed.depleteEnergy(energyTransferred);
food.addEnergy(energyTransferred);

for (ComplexMolecule molecule : engulfed.getComplexMolecules()) {
if (engulfed.getComplexMoleculeAvailable(molecule) > 0) {
Expand All @@ -364,9 +402,20 @@ public void eat(Cell engulfed, float extraction) {
}

public void addFood(Food.Type foodType, float amount) {
Food food = foodToDigest.getOrDefault(foodType, new Food(amount, foodType));
food.addSimpleMass(amount);
foodToDigest.put(foodType, food);
// Same double-mass bug as the old Cell.eat had: fresh Food gets
// mass=amount via the constructor, then addSimpleMass(amount) ran
// unconditionally — every chemical-drip absorption was actually 2×
// the intended yield. Combined with chemical drip being an unsourced
// mass spring (plants don't lose mass when their chemicals are
// extracted), this let large protozoa populations sustain on tiny
// plant counts.
Food food = foodToDigest.get(foodType);
if (food == null) {
food = new Food(amount, foodType);
foodToDigest.put(foodType, food);
} else {
food.addSimpleMass(amount);
}
}

public void digest(float delta) {
Expand Down Expand Up @@ -670,6 +719,9 @@ public Statistics getStats() {

stats.putBoolean("Being Engulfed", engulfer != null);

if (lineageId != 0L)
stats.putCount("Lineage ID", (int) lineageId);

stats.putPercentage("Light Level", 100f * getLightAtCell());
stats.putTemperature("Temperature (Internal)", temperature);
stats.putTemperature("Temperature (External)", getExternalTemperature());
Expand Down Expand Up @@ -770,6 +822,11 @@ public void setGeneration(int generation) {
this.generation = generation;
}

public long getLineageId() { return lineageId; }
public void setLineageId(long id) { this.lineageId = id; }
public long getParentId() { return parentId; }
public void setParentId(long id) { this.parentId = id; }

public int burstMultiplier() {
return 1;
}
Expand Down
Loading