diff --git a/public/tide-pool-world.js b/public/tide-pool-world.js
index a4f1d6b..32ba44a 100644
--- a/public/tide-pool-world.js
+++ b/public/tide-pool-world.js
@@ -1662,6 +1662,15 @@ function moveWalker(world, c, dt) {
}
// Swimming: a loose school that keeps to water, heads for deeper water as the tide drains, and scatters from ripples.
+// A slow current shared by the whole shelf: the curl of a drifting field, so it swirls without gathering anywhere.
+// Fish that feel it together turn together, which is what keeps a school moving as one shape.
+export function drift(x, z, t) {
+ const e = .01;
+ const psi = (a, b) => Math.sin(a * .23 + t * .045) * Math.cos(b * .19 - t * .031) + .6 * Math.sin((a + b) * .13 - t * .027);
+ const vx = (psi(x, z + e) - psi(x, z - e)) / (2 * e), vz = -(psi(x + e, z) - psi(x - e, z)) / (2 * e);
+ const len = Math.hypot(vx, vz) || 1;
+ return { x: vx / len, z: vz / len };
+}
function moveSwimmer(world, c, dt) {
const S = SPECIES.tab;
const depth = depthAt(world, c.x, c.z);
@@ -1669,11 +1678,11 @@ function moveSwimmer(world, c, dt) {
for (const o of world.creatures) {
if (o === c || o.sp !== 'tab') continue;
const dx = o.x - c.x, dz = o.z - c.z, d = Math.hypot(dx, dz);
- if (d > 1.4 || d < 1e-4) continue;
+ if (d > 2 || d < 1e-4) continue;
n++; cx += dx; cz += dz; ux += o.vx; uz += o.vz;
if (d < .35) { ax -= dx / d * (.35 - d) * 6; az -= dz / d * (.35 - d) * 6; }
}
- if (n) { ax += cx / n * .6 + (ux / n - c.vx) * .9; az += cz / n * .6 + (uz / n - c.vz) * .9; }
+ if (n) { ax += cx / n * .5 + (ux / n - c.vx) * 1.4; az += cz / n * .5 + (uz / n - c.vz) * 1.4; }
for (const o of world.creatures) {
if (o.sp !== 'pylon' && o.sp !== 'breaker') continue;
const dx = c.x - o.x, dz = c.z - o.z, d = Math.hypot(dx, dz);
@@ -1701,8 +1710,24 @@ function moveSwimmer(world, c, dt) {
}
if (best > depth) { ax += bx * 2.5 * worry; az += bz * 2.5 * worry; }
}
+ // A hungry fish follows the plankton, leaning toward richer water nearby; the school turns with it, so a school that
+ // has grazed its water out moves on to fresh water instead of starving where it is.
+ const hunger = clamp((.75 - c.energy) / .5, 0, 1);
+ if (hunger > .05) {
+ let best = world.plankton[cellAt(c.x, c.z)] + .02, bx = 0, bz = 0;
+ for (let k = 0; k < 6; k++) {
+ const a = k / 6 * Math.PI * 2 + c.phase * .1, x = c.x + Math.cos(a) * 1.6, z = c.z + Math.sin(a) * 1.6;
+ if (depthAt(world, x, z) < .12) continue;
+ const pl = world.plankton[cellAt(x, z)];
+ if (pl > best) { best = pl; bx = Math.cos(a); bz = Math.sin(a); }
+ }
+ // Smoothed, so a fish turns toward food rather than twitching between samples.
+ c.fx = (c.fx || 0) + (bx - (c.fx || 0)) * Math.min(1, dt * .8);
+ c.fz = (c.fz || 0) + (bz - (c.fz || 0)) * Math.min(1, dt * .8);
+ ax += c.fx * 1.8 * hunger; az += c.fz * 1.8 * hunger;
+ }
// Most of the school takes a tower as home once one stands: they find their way back to it from anywhere,
- // then circle it in a slow orbit, drifting in and out through its frames.
+ // then circle it in a slow orbit, drifting in and out through its frames. Hunger loosens the hold.
let home = null, homeD = Infinity;
if (c.id % 4) for (const r of world.structures) {
if (r.n < 8) continue;
@@ -1711,10 +1736,12 @@ function moveSwimmer(world, c, dt) {
}
if (home && homeD > 1e-3) {
const dx = home.x - c.x, dz = home.z - c.z, d = homeD, ring = .4 + home.reach * .8;
- if (d > 7) { ax += dx / d * 1.3; az += dz / d * 1.3; }
+ const hold = 1 - hunger * .75;
+ if (d > 7) { ax += dx / d * 1.3 * hold; az += dz / d * 1.3 * hold; }
else {
- const pull = (d - ring) * .9, swirl = 1.1 * (c.id % 2 ? 1 : -1);
- ax += dx / d * pull + (-dz / d) * swirl; az += dz / d * pull + (dx / d) * swirl;
+ // Every fish around one structure circles it the same way, so they go round as one.
+ const pull = (d - ring) * .9, swirl = 1.1 * (Math.floor(home.x * 3 + home.z * 7) % 2 ? 1 : -1);
+ ax += (dx / d * pull + (-dz / d) * swirl) * hold; az += (dz / d * pull + (dx / d) * swirl) * hold;
}
}
// At night a lit beacon draws the school toward its light.
@@ -1723,15 +1750,12 @@ function moveSwimmer(world, c, dt) {
const dx = m.x - c.x, dz = m.z - c.z, d = Math.hypot(dx, dz);
if (d < 9 && d > 1.2) { ax += dx / d * m.lit * 1.2; az += dz / d * m.lit * 1.2; }
}
- // At high water the school roams the flooded shelf along a slowly turning heading.
- const high = tideOf(world.time);
- if (high > .55 && depth > .15) {
- c.heading += (world.random() - .5) * dt * 1.2;
- const roam = 1.4 * (high - .55) * 2.2 * (home ? .3 : 1);
- ax += Math.cos(c.heading) * roam; az += Math.sin(c.heading) * roam;
- }
+ // The shared current always nudges a little, and at high water carries the school out across the flooded shelf.
+ const high = tideOf(world.time), flow = drift(c.x, c.z, world.time);
+ const roam = .35 + (high > .55 && depth > .15 ? 1.4 * (high - .55) * 2.2 * (home ? .3 : 1) : 0);
+ ax += flow.x * roam; az += flow.z * roam;
const a = (world.random() - .5) * 2.2;
- ax += Math.cos(c.phase * .3 + a) * .5; az += Math.sin(c.phase * .3 + a) * .5;
+ ax += Math.cos(c.phase * .3 + a) * .15; az += Math.sin(c.phase * .3 + a) * .15;
c.vx += ax * dt; c.vz += az * dt;
const sp = Math.hypot(c.vx, c.vz), max = S.speed * (depth < .1 ? .3 : 1), min = .25;
if (sp > max) { c.vx *= max / sp; c.vz *= max / sp; } else if (sp < min && sp > 1e-5) { c.vx *= min / sp; c.vz *= min / sp; }
diff --git a/public/tide-pool.js b/public/tide-pool.js
index 60789d8..0c4745b 100644
--- a/public/tide-pool.js
+++ b/public/tide-pool.js
@@ -1,7 +1,7 @@
import {
createWorld, advanceWorld, offerObject, SPECIES, SPECIES_ORDER, CELL, COLS, ROWS, X0, Z0, X1, Z1, START_POOL, waterLevel, daylight,
heightAt, surfaceAt, depthAt, onShelf, bodyAt, tideOf, tideRising, label, goalText, describeWorld, creatureById, MACHINES, BLOCK, BLOCK_Y0, blockKey, footAt, debrisAt, footing,
-} from './tide-pool-world.js?v=26';
+} from './tide-pool-world.js?v=28';
const root = document.querySelector('[data-tide-pool]');
const status = document.querySelector('#tide-status');
diff --git a/src/components/tide-pool.tsx b/src/components/tide-pool.tsx
index feffe89..75d1f88 100644
--- a/src/components/tide-pool.tsx
+++ b/src/components/tide-pool.tsx
@@ -141,7 +141,7 @@ export function TidePoolContent() {
{/* Lets the three.js add-ons resolve the same pinned module the page already uses. */}
-
+
);
}