Add semi-random wind weather pattern

This commit is contained in:
Admin 2026-09-20 07:50:19 +00:00
parent 82eef3f26e
commit b0b88f432c
2 changed files with 90 additions and 11 deletions

View File

@ -15,21 +15,72 @@ export const ENVIRONMENT_ISLANDS = [
}, },
]; ];
const WIND_PATTERN_MS = 16000;
const WIND_MIN_STRENGTH = 7;
const WIND_MAX_STRENGTH = 24;
const clamp01 = (value) => Math.max(0, Math.min(1, value));
const smootherStep = (value) => {
const t = clamp01(value);
return t * t * t * (t * (t * 6 - 15) + 10);
};
const hash01 = (index, salt = 0) => {
const value = Math.sin(index * 12.9898 + salt * 78.233) * 43758.5453123;
return value - Math.floor(value);
};
const shortestAngleDelta = (from, to) => {
let delta = (to - from) % (Math.PI * 2);
if (delta > Math.PI) delta -= Math.PI * 2;
if (delta < -Math.PI) delta += Math.PI * 2;
return delta;
};
const windTargetForSlot = (slot) => ({
angle: hash01(slot, 1) * Math.PI * 2,
strength: WIND_MIN_STRENGTH
+ hash01(slot, 2) * (WIND_MAX_STRENGTH - WIND_MIN_STRENGTH),
});
export const semiRandomWindPattern = (timeMs = 0) => {
const safeTime = Math.max(0, Number(timeMs) || 0);
const slot = Math.floor(safeTime / WIND_PATTERN_MS);
const phase = (safeTime % WIND_PATTERN_MS) / WIND_PATTERN_MS;
const current = windTargetForSlot(slot);
const next = windTargetForSlot(slot + 1);
// Hold each weather state briefly, then ease toward the next random target.
const blend = smootherStep((phase - 0.18) / 0.82);
const angle = current.angle
+ shortestAngleDelta(current.angle, next.angle) * blend;
const strength = current.strength
+ (next.strength - current.strength) * blend;
// A smaller deterministic gust keeps the air alive without frame-to-frame jitter.
const gust = Math.sin(safeTime / 2300 + slot * 0.73)
* (1.1 + hash01(slot, 3) * 1.8);
return {
angle,
strength: Math.max(4, strength + gust),
};
};
export const environmentWindAt = ({ export const environmentWindAt = ({
x = 0, x = 0,
y = 0, y = 0,
timeMs = 0, timeMs = 0,
} = {}) => { } = {}) => {
const time = timeMs / 1000; const time = timeMs / 1000;
const angle = 0.62 const pattern = semiRandomWindPattern(timeMs);
+ Math.sin(time / 29) * 0.2
+ Math.sin((x + y) / 880) * 0.06;
const strength = 14
+ Math.sin(time / 8.5) * 3.2
+ Math.sin(time / 2.9) * 1.4;
let windX = Math.cos(angle) * strength; // Small spatial variation prevents the whole map feeling like one rigid conveyor belt.
let windY = Math.sin(angle) * strength; const localAngle = pattern.angle
+ Math.sin((x + y) / 880 + time / 31) * 0.055;
const localStrength = pattern.strength
+ Math.sin((x - y) / 760 + time / 4.7) * 0.9;
let windX = Math.cos(localAngle) * localStrength;
let windY = Math.sin(localAngle) * localStrength;
for (const island of ENVIRONMENT_ISLANDS) { for (const island of ENVIRONMENT_ISLANDS) {
const dx = x - island.x; const dx = x - island.x;

View File

@ -4,6 +4,7 @@ import assert from 'node:assert/strict';
import { import {
ENVIRONMENT_ISLANDS, ENVIRONMENT_ISLANDS,
environmentWindAt, environmentWindAt,
semiRandomWindPattern,
velocityWithEnvironment, velocityWithEnvironment,
} from '../src/environment-physics.js'; } from '../src/environment-physics.js';
@ -15,12 +16,39 @@ test('environment exposes collidable island geometry', () => {
} }
}); });
test('semi-random wind varies strength and direction across weather windows', () => {
const samples = [0, 16000, 32000, 48000, 64000]
.map((timeMs) => semiRandomWindPattern(timeMs));
const roundedStrengths = new Set(samples.map(({ strength }) => Math.round(strength * 10)));
const roundedHeadings = new Set(samples.map(({ angle }) => Math.round(angle * 10)));
assert.ok(roundedStrengths.size >= 3);
assert.ok(roundedHeadings.size >= 3);
for (const sample of samples) {
assert.ok(sample.strength >= 4 && sample.strength < 28);
}
});
test('semi-random wind changes smoothly instead of jumping each frame', () => {
const before = semiRandomWindPattern(15950);
const after = semiRandomWindPattern(16050);
const vector = ({ angle, strength }) => ({
x: Math.cos(angle) * strength,
y: Math.sin(angle) * strength,
});
const a = vector(before);
const b = vector(after);
assert.ok(Math.hypot(b.x - a.x, b.y - a.y) < 2.5);
});
test('ambient wind changes over time and remains bounded', () => { test('ambient wind changes over time and remains bounded', () => {
const first = environmentWindAt({ x: 1200, y: 1200, timeMs: 0 }); const first = environmentWindAt({ x: 1200, y: 1200, timeMs: 0 });
const later = environmentWindAt({ x: 1200, y: 1200, timeMs: 18000 }); const later = environmentWindAt({ x: 1200, y: 1200, timeMs: 24000 });
assert.ok(first.strength > 5 && first.strength < 30); assert.ok(first.strength > 3 && first.strength < 30);
assert.ok(later.strength > 5 && later.strength < 30); assert.ok(later.strength > 3 && later.strength < 30);
assert.notDeepEqual( assert.notDeepEqual(
[Math.round(first.x * 100), Math.round(first.y * 100)], [Math.round(first.x * 100), Math.round(first.y * 100)],
[Math.round(later.x * 100), Math.round(later.y * 100)], [Math.round(later.x * 100), Math.round(later.y * 100)],