diff --git a/index.html b/index.html
index 40a0ade..33e118a 100644
--- a/index.html
+++ b/index.html
@@ -89,34 +89,43 @@
interactions
-
-
+
+
-
- bug infects → virus
+
+ →
+
+ bug infects
-
-
+
+
-
- bug cures → dna
+
+ →
+
+ bug cures virus
-
-
+
+
-
- virus kills 90%
+
+ →
+ 90%
+ virus kills
-
-
+
+
-
- mutates → bacteria 10%
+
+ →
+
+ 10%
+ mutation
-
-
-
-
- bacteria immune to virus
+
+ ×
+
+ bacteria immune
diff --git a/src/js/evolution.js b/src/js/evolution.js
index f0ae755..9259cf1 100644
--- a/src/js/evolution.js
+++ b/src/js/evolution.js
@@ -385,7 +385,10 @@ export class EvolutionController {
}
// ── Virus kill / mutation ──────────────────────────────
- // virus-filled hitting a non-immune entity: 90% kills it, 10% triggers mutation.
+ // virus-filled hitting a non-immune entity: the target may die or mutate.
+ // On a failed kill roll the contact will convert the target into bacteria
+ // (10% shown in the guide) if that icon exists. Bacteria are immune to
+ // subsequent virus or bug interactions.
const virusImmune = new Set(['bug', 'virus-filled', 'bacteria']);
const aIsVirus = a.entityKey === 'virus-filled';
const bIsVirus = b.entityKey === 'virus-filled';
@@ -403,10 +406,11 @@ export class EvolutionController {
/**
* Handle a collision between a virus-filled entity and a vulnerable target.
- * Bug is the sole mutation/transformation trigger — virus only kills.
*
- * - virusKillChance: target begins slow-death sequence.
- * - else: target survives unaffected (virus bounces away normally).
+ * 50%–100% of the time (controlled by virusKillChance) the target is killed.
+ * On the remaining rolls the virus fails to kill and the contact triggers a
+ * rare mutation: the target becomes a "bacteria" entity if that icon is
+ * defined. Bacteria are immune to further virus attacks and bugs.
*
* @param {import('./entity.js').Entity} _virus the virus-filled entity (unused)
* @param {import('./entity.js').Entity} target the entity being contacted
@@ -414,8 +418,14 @@ export class EvolutionController {
#resolveVirusContact(_virus, target) {
if (Math.random() < this.#virusKillChance) {
target.die();
+ } else {
+ // mutation path – only if bacteria is registered
+ if (this.#iconsData && this.#iconsData.icons['bacteria']) {
+ const bacMeta = this.#iconsData.icons['bacteria'];
+ const bacColor = this.#iconsData.types[bacMeta.type]?.color || '#80ffee';
+ target.infectWith('bacteria', bacColor, { force: true });
+ }
}
- // else: target survives — virus bounces, no transformation
}
// ── State persistence ────────────────────────────────────────
diff --git a/src/styles/guide.css b/src/styles/guide.css
index 7372d32..0bac353 100644
--- a/src/styles/guide.css
+++ b/src/styles/guide.css
@@ -106,10 +106,10 @@
/* Icon container — SVG injected here by guide.js */
.guide-entity__icon {
- width: 16px;
- height: 16px;
+ width: 18px;
+ height: 18px;
flex-shrink: 0;
- color: var(--c, rgba(255, 255, 255, 0.6));
+ color: var(--c, rgba(255, 255, 255, 0.75));
display: flex;
align-items: center;
justify-content: center;
@@ -154,10 +154,11 @@
}
.guide-rule {
- display: grid;
- grid-template-columns: 9px 9px 9px 1fr;
+ display: flex;
align-items: center;
- gap: 0.28rem;
+ gap: 0.4rem;
+ flex-wrap: nowrap; /* absolutely no wrapping */
+ white-space: nowrap; /* safeguard for inline elements */
}
/* Coloured circle representing an entity type */
@@ -172,9 +173,14 @@
.guide-rule__op {
font-family: var(--font-mono);
- font-size: 0.55rem;
- color: rgba(255, 255, 255, 0.22);
+ display: inline-block;
+ /* 1.2rem ≈ 19px on a 16px root, sized to match the 18px icons */
+ font-size: 1.2rem;
+ line-height: 1;
+ font-weight: 500;
+ color: rgba(255, 255, 255, 0.75);
text-align: center;
+ flex-shrink: 0;
}
.guide-rule__desc {
@@ -182,11 +188,13 @@
font-size: 0.62rem;
color: rgba(255, 255, 255, 0.45);
line-height: 1.3;
+ margin-left: 0.4rem;
}
.guide-rule__pct {
color: rgba(255, 255, 255, 0.22);
- font-size: 0.58rem;
+ font-size: 0.7rem;
+ flex-shrink: 0;
}
/* ── Guide footer + restart button ───────────────────────── */