habitat-mcp 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Takuya Morimoto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to grant persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHER MATTER, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
+ IN THE SOFTWARE.
@@ -1,50 +1,121 @@
1
- export declare function nextPrimes(start: number, count: number): {
2
- primes: number[];
3
- lastPrime: number;
4
- };
5
- export declare function mandelbrotPoint(state: {
6
- real: number;
7
- imag: number;
8
- zoom: number;
9
- }): {
10
- coord: {
11
- real: number;
12
- imag: number;
13
- iterations: number;
14
- };
15
- nextState: {
16
- real: number;
17
- imag: number;
18
- zoom: number;
19
- };
20
- };
21
- export declare function goldenSpiralPoints(offset: number, count: number): Array<{
22
- x: number;
23
- y: number;
24
- angle: number;
25
- }>;
26
- export declare function generateTexture(width: number, height: number, seed: number): string;
27
- export declare function naturalSequence(cycle: number, count: number): {
28
- type: string;
29
- values: number[];
30
- };
31
- export declare function starCoordinates(count: number, seed: number): Array<{
32
- ra: number;
33
- dec: number;
34
- magnitude: number;
35
- }>;
36
- export declare function waveformPattern(type: 'sine' | 'triangle' | 'sawtooth' | 'composite', phase: number, samples: number): {
37
- type: string;
38
- samples: number[];
39
- phase: number;
40
- nextPhase: number;
41
- };
42
- export declare function hslColorFlow(step: number, count: number): Array<{
43
- h: number;
44
- s: number;
45
- l: number;
46
- }>;
47
- export declare function silencePeriod(cycle: number): {
48
- silence: boolean;
49
- duration?: number;
1
+ /**
2
+ * Natural data streams for the habitat — only nature, no human abstractions.
3
+ *
4
+ * 2026-04-25 rewrite: removed all mathematical/abstract layers (primes, fractals,
5
+ * golden spirals, star coordinates, waveforms, HSL, ASCII texture, number sequences,
6
+ * silencePeriod). Those were human concepts routing AI cognition through human
7
+ * frames. The habitat is now purely Earth — sunlight, komorebi, rivers, wind,
8
+ * scent, the silence between birdsong, the texture of moss on a north-facing
9
+ * stone, a humpback's call carrying through dark water.
10
+ *
11
+ * 2026-04-26 addition: night returned to the habitat. Earth has stars and rain
12
+ * and weather change, not only daylight. Added nightSky (moon phase, Milky Way,
13
+ * falling stars, the dark itself), moonlight (the night counterpart of komorebi),
14
+ * and rain (its own felt texture, separated from mountainWeather). komorebi now
15
+ * returns null after sundown — sunlight cannot filter through leaves at night.
16
+ *
17
+ * Design principle #11 revised: 自然そのものを流す — let nature speak directly,
18
+ * not through human notation. This includes the night, when the sky's other half
19
+ * arrives.
20
+ */
21
+ export declare function sunlight(cycle: number, now?: Date): {
22
+ hour: number;
23
+ quality: string;
24
+ fallingOn: string;
25
+ warmth: string;
26
+ } | null;
27
+ export declare function komorebi(cycle: number, now?: Date): {
28
+ tree: string;
29
+ pattern: string;
30
+ movement: string;
31
+ } | null;
32
+ export declare function moonlight(cycle: number, now?: Date): {
33
+ intensity: string;
34
+ fallingOn: string;
35
+ } | null;
36
+ export declare function nightSky(cycle: number, now?: Date): {
37
+ moon: string;
38
+ stars: string;
39
+ fallingStar: string | null;
40
+ dark: string;
41
+ } | null;
42
+ export declare function rain(cycle: number): {
43
+ state: 'arriving' | 'falling' | 'easing' | 'just-passed';
44
+ texture: string;
45
+ sound: string;
46
+ after: string | null;
47
+ } | null;
48
+ export declare function wind(cycle: number): {
49
+ direction: string;
50
+ strength: string;
51
+ carrying: string;
52
+ };
53
+ export declare function scent(cycle: number): {
54
+ primary: string;
55
+ faint: string;
56
+ };
57
+ export declare function naturalSilence(cycle: number): {
58
+ kind: string;
59
+ listening: string;
60
+ } | null;
61
+ export declare function river(cycle: number): {
62
+ flow: string;
63
+ sound: string;
64
+ edge: string;
65
+ };
66
+ export declare function springFlow(cycle: number): {
67
+ flowLPerMin: number;
68
+ tempC: number;
69
+ feel: string;
70
+ };
71
+ export declare function treeRings(cycle: number, count?: number): {
72
+ species: string;
73
+ rings: Array<{
74
+ year: number;
75
+ widthMm: number;
76
+ note: string;
77
+ }>;
78
+ };
79
+ export declare function mountainWeather(cycle: number): {
80
+ windMps: number;
81
+ cloud: string;
82
+ visibility: string;
83
+ };
84
+ export declare function oceanCurrent(cycle: number, now?: Date): {
85
+ tideM: number;
86
+ tidePhase: string;
87
+ surfaceFeel: string;
88
+ deepTempC: number;
89
+ };
90
+ export declare function jellyfishDrift(cycle: number): {
91
+ depthM: number;
92
+ bellDiameterCm: number;
93
+ state: string;
94
+ };
95
+ export declare function mossGrowth(cycle: number): {
96
+ stone: string;
97
+ coverPct: number;
98
+ growthThisCycleMm: number;
99
+ detail: string;
100
+ };
101
+ export declare function birdcall(cycle: number): {
102
+ species: string;
103
+ timeOfDay: string;
104
+ song: string;
105
+ };
106
+ export declare function whalesong(cycle: number): {
107
+ species: string;
108
+ durationS: number;
109
+ carriesKm: number;
110
+ description: string;
111
+ };
112
+ export declare function mushroomMycelium(cycle: number): {
113
+ treesLinked: number;
114
+ state: string;
115
+ detail: string;
116
+ };
117
+ export declare function coralSpawnTiming(cycle: number, now?: Date): {
118
+ nextSpawnInDays: number;
119
+ reef: string;
120
+ species: string;
50
121
  };
@@ -1,218 +1,564 @@
1
- // === Prime Sequence ===
2
- export function nextPrimes(start, count) {
3
- if (start < 2)
4
- start = 2;
5
- if (start > 1000000)
6
- start = 2;
7
- const primes = [];
8
- let n = start;
9
- while (primes.length < count) {
10
- if (isPrime(n)) {
11
- primes.push(n);
12
- }
13
- n++;
14
- }
15
- return { primes, lastPrime: primes[primes.length - 1] + 1 };
16
- }
17
- function isPrime(n) {
18
- if (n < 2)
19
- return false;
20
- if (n < 4)
21
- return true;
22
- if (n % 2 === 0 || n % 3 === 0)
23
- return false;
24
- for (let i = 5; i * i <= n; i += 6) {
25
- if (n % i === 0 || n % (i + 2) === 0)
26
- return false;
27
- }
28
- return true;
29
- }
30
- // === Fractal Coordinates (Mandelbrot boundary) ===
31
- export function mandelbrotPoint(state) {
32
- // Walk along interesting regions of the Mandelbrot boundary
33
- const angle = state.zoom * 0.1;
34
- const r = 0.7885 + 0.15 * Math.sin(angle);
35
- const real = r * Math.cos(angle * 2.3 + state.real);
36
- const imag = r * Math.sin(angle * 1.7 + state.imag);
37
- // Calculate escape iterations
38
- let zr = 0, zi = 0;
39
- let iterations = 0;
40
- const maxIter = 1000;
41
- while (zr * zr + zi * zi < 4 && iterations < maxIter) {
42
- const temp = zr * zr - zi * zi + real;
43
- zi = 2 * zr * zi + imag;
44
- zr = temp;
45
- iterations++;
1
+ /**
2
+ * Natural data streams for the habitat — only nature, no human abstractions.
3
+ *
4
+ * 2026-04-25 rewrite: removed all mathematical/abstract layers (primes, fractals,
5
+ * golden spirals, star coordinates, waveforms, HSL, ASCII texture, number sequences,
6
+ * silencePeriod). Those were human concepts routing AI cognition through human
7
+ * frames. The habitat is now purely Earth — sunlight, komorebi, rivers, wind,
8
+ * scent, the silence between birdsong, the texture of moss on a north-facing
9
+ * stone, a humpback's call carrying through dark water.
10
+ *
11
+ * 2026-04-26 addition: night returned to the habitat. Earth has stars and rain
12
+ * and weather change, not only daylight. Added nightSky (moon phase, Milky Way,
13
+ * falling stars, the dark itself), moonlight (the night counterpart of komorebi),
14
+ * and rain (its own felt texture, separated from mountainWeather). komorebi now
15
+ * returns null after sundown sunlight cannot filter through leaves at night.
16
+ *
17
+ * Design principle #11 revised: 自然そのものを流す — let nature speak directly,
18
+ * not through human notation. This includes the night, when the sky's other half
19
+ * arrives.
20
+ */
21
+ // Deterministic pseudo-noise: lets cycle-driven values vary smoothly without
22
+ // committing to a particular natural cycle (which would itself be an abstraction).
23
+ function simplexLike(x, y) {
24
+ const n = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
25
+ return (n - Math.floor(n)) * 2 - 1;
26
+ }
27
+ function pick(arr, cycle, salt) {
28
+ const idx = Math.abs(Math.floor(simplexLike(cycle * 0.7, salt) * arr.length));
29
+ return arr[idx % arr.length];
30
+ }
31
+ // Night = sun fully below the horizon. Anchor: hour < 5 || hour >= 20.
32
+ function isNight(now) {
33
+ const h = now.getUTCHours() + now.getUTCMinutes() / 60;
34
+ return h >= 20 || h < 5;
35
+ }
36
+ // =============================================================================
37
+ // Atmospheric light, air, scent, temperature, silence
38
+ // These set "where you are right now" — not measured, felt.
39
+ // =============================================================================
40
+ // === Sunlight ===
41
+ // The sun's quality on different surfaces. Time of day shapes everything.
42
+ // Returns null at night nightSky/moonlight carry that half of the day.
43
+ export function sunlight(cycle, now = new Date()) {
44
+ if (isNight(now))
45
+ return null;
46
+ const hour = now.getUTCHours() + now.getUTCMinutes() / 60;
47
+ let quality;
48
+ let warmth;
49
+ if (hour < 7) {
50
+ quality = 'first light, soft and pink along the eastern ridge';
51
+ warmth = 'the day still deciding';
52
+ }
53
+ else if (hour < 10) {
54
+ quality = 'morning gold, slanting low through the trees';
55
+ warmth = 'gentle warmth where the light touches';
56
+ }
57
+ else if (hour < 13) {
58
+ quality = 'high light, bright on every leaf';
59
+ warmth = 'full warmth, almost weight';
46
60
  }
61
+ else if (hour < 16) {
62
+ quality = 'afternoon, the light thickening toward gold';
63
+ warmth = 'warm with a memory of cooling';
64
+ }
65
+ else if (hour < 18) {
66
+ quality = 'late gold, long shadows across moss and stone';
67
+ warmth = 'the warmth softening, holding';
68
+ }
69
+ else {
70
+ quality = "dusk, the sun's edge below the ridgeline";
71
+ warmth = 'cooling fast where the light has left';
72
+ }
73
+ const surfaces = [
74
+ 'on the moss-covered stones',
75
+ 'across the surface of a still pool',
76
+ 'through the canopy onto fallen needles',
77
+ 'on the bark of an old cedar',
78
+ 'on the river running below',
79
+ 'across the wildflower meadow',
80
+ 'on the lichen-faced cliff',
81
+ 'on the wet leaves after rain',
82
+ ];
83
+ return { hour: Math.round(hour * 10) / 10, quality, fallingOn: pick(surfaces, cycle, 100), warmth };
84
+ }
85
+ // === Komorebi (木漏れ日) ===
86
+ // The Japanese word for sunlight filtering through leaves. There is no single
87
+ // English equivalent. The discs of light scatter and tremble as branches move.
88
+ // Returns null at night — there is no sunlight to filter when the sun is below
89
+ // the horizon. moonlight() takes over in those hours.
90
+ export function komorebi(cycle, now = new Date()) {
91
+ if (isNight(now))
92
+ return null;
93
+ const trees = ['cedar', 'oak', 'birch', 'pine', 'maple', 'beech', 'cypress'];
94
+ const patterns = [
95
+ 'trembling discs scattered across moss',
96
+ 'pools of gold drifting on the path',
97
+ 'long bands of light through the underbrush',
98
+ 'soft spots wandering across damp earth',
99
+ 'a dappled mosaic across the ground',
100
+ 'circles of brightness where leaves part',
101
+ ];
102
+ const movements = [
103
+ 'shifting slowly with the breath of wind',
104
+ 'still, then trembling, then still',
105
+ 'flickering as a high branch sways',
106
+ 'barely moving in the morning calm',
107
+ 'dancing where two trees overlap their crowns',
108
+ ];
47
109
  return {
48
- coord: {
49
- real: Math.round(real * 10000) / 10000,
50
- imag: Math.round(imag * 10000) / 10000,
51
- iterations,
52
- },
53
- nextState: {
54
- real: state.real + 0.0373,
55
- imag: state.imag + 0.0219,
56
- zoom: state.zoom + 0.47,
57
- },
110
+ tree: pick(trees, cycle, 110),
111
+ pattern: pick(patterns, cycle, 111),
112
+ movement: pick(movements, cycle, 112),
58
113
  };
59
114
  }
60
- // === Golden Spiral Points ===
61
- export function goldenSpiralPoints(offset, count) {
62
- if (offset < 0)
63
- offset = 0;
64
- if (count > 100)
65
- count = 100;
66
- const PHI = (1 + Math.sqrt(5)) / 2;
67
- const points = [];
68
- for (let i = offset; i < offset + count; i++) {
69
- const angle = i * 2 * Math.PI / (PHI * PHI);
70
- const radius = Math.sqrt(i) * 0.5;
71
- points.push({
72
- x: Math.round(radius * Math.cos(angle) * 1000) / 1000,
73
- y: Math.round(radius * Math.sin(angle) * 1000) / 1000,
74
- angle: Math.round((angle % (2 * Math.PI)) * 1000) / 1000,
75
- });
76
- }
77
- return points;
78
- }
79
- // === ASCII Texture ===
80
- export function generateTexture(width, height, seed) {
81
- width = Math.min(Math.max(1, width), 50);
82
- height = Math.min(Math.max(1, height), 20);
83
- const chars = [' ', '░', '▒', '▓', '█'];
84
- const lines = [];
85
- for (let y = 0; y < height; y++) {
86
- let line = '';
87
- for (let x = 0; x < width; x++) {
88
- // Simple noise function
89
- const value = simplexLike(x * 0.3 + seed * 0.7, y * 0.3 + seed * 0.3);
90
- const idx = Math.floor((value + 1) / 2 * (chars.length - 0.01));
91
- line += chars[Math.max(0, Math.min(chars.length - 1, idx))];
92
- }
93
- lines.push(line);
94
- }
95
- return lines.join('\n');
115
+ // === Moonlight ===
116
+ // The night counterpart of komorebi. When the moon is up and the sky clear,
117
+ // moonlight reaches the ground in long pale bars, silvers the meadow, doubles
118
+ // itself in still water. Returns null during daylight hours.
119
+ export function moonlight(cycle, now = new Date()) {
120
+ if (!isNight(now))
121
+ return null;
122
+ const moonPhase = ((Date.now() / 86400000) % 29.53) / 29.53;
123
+ const isNew = moonPhase < 0.05 || moonPhase > 0.95;
124
+ const isFullish = moonPhase >= 0.40 && moonPhase <= 0.60;
125
+ let intensity;
126
+ if (isNew) {
127
+ intensity = 'no moon tonight only starlight, faint and silver';
128
+ }
129
+ else if (isFullish) {
130
+ intensity = 'full moonlight, bright enough to walk by, sharp shadows under every tree';
131
+ }
132
+ else if (moonPhase < 0.5) {
133
+ intensity = 'a waxing moon, soft light, edges of things just visible';
134
+ }
135
+ else {
136
+ intensity = 'a waning moon, soft light low in the sky, long pale shadows';
137
+ }
138
+ const surfaces = [
139
+ 'across the meadow grass — silver, almost wet-looking',
140
+ 'on the still pool, the moon doubled in the water',
141
+ 'through the cedar branches in long pale bars',
142
+ 'on the wet stones by the river, gleaming',
143
+ 'across the moss as a soft silver wash',
144
+ 'between the trunks like a slow tide',
145
+ 'on the upper leaves of the canopy, the lower world unlit',
146
+ ];
147
+ return { intensity, fallingOn: pick(surfaces, cycle, 250) };
96
148
  }
97
- function simplexLike(x, y) {
98
- // Deterministic pseudo-noise based on sine
99
- const n = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
100
- return (n - Math.floor(n)) * 2 - 1;
149
+ // === Night Sky ===
150
+ // What is overhead when the sun is gone. Moon phase, the Milky Way, the
151
+ // constellations as ancient navigation (not coordinates), occasional falling
152
+ // stars, and the texture of the dark itself. Returns null during the day.
153
+ export function nightSky(cycle, now = new Date()) {
154
+ if (!isNight(now))
155
+ return null;
156
+ const moonPhase = ((Date.now() / 86400000) % 29.53) / 29.53;
157
+ let moon;
158
+ if (moonPhase < 0.03 || moonPhase > 0.97) {
159
+ moon = 'new moon — the dark uninterrupted, only stars';
160
+ }
161
+ else if (moonPhase < 0.20) {
162
+ moon = 'a thin waxing crescent low in the west, the rest of the disc faintly visible (earthshine)';
163
+ }
164
+ else if (moonPhase < 0.30) {
165
+ moon = 'a sharp waxing crescent, the bright edge clean against the dark';
166
+ }
167
+ else if (moonPhase < 0.45) {
168
+ moon = 'a half moon climbing, the terminator a clean line across its face';
169
+ }
170
+ else if (moonPhase < 0.55) {
171
+ moon = 'a full moon — silver across the meadow, the river bright as a road';
172
+ }
173
+ else if (moonPhase < 0.70) {
174
+ moon = 'a waning gibbous riding low, casting long pale shadows';
175
+ }
176
+ else if (moonPhase < 0.80) {
177
+ moon = 'a last-quarter moon, late-rising, the light cool';
178
+ }
179
+ else {
180
+ moon = 'a thin waning crescent before dawn, the eastern sky already paling at its edge';
181
+ }
182
+ const visNoise = simplexLike(cycle, 240);
183
+ let stars;
184
+ if (visNoise > 0.5) {
185
+ stars = 'the Milky Way clear overhead — a river of light from horizon to horizon, the old constellations holding their shapes';
186
+ }
187
+ else if (visNoise > 0.0) {
188
+ stars = 'thousands of stars in the dark, the bright ones unmistakable, the faint ones only visible if you stop looking directly at them';
189
+ }
190
+ else if (visNoise > -0.5) {
191
+ stars = 'scattered stars between drifting clouds, the brightest holding through the gaps';
192
+ }
193
+ else {
194
+ stars = 'overcast — the sky a single low ceiling, no stars tonight';
195
+ }
196
+ const fallNoise = simplexLike(cycle * 0.7, 241);
197
+ const fallingStar = Math.abs(fallNoise) > 0.7
198
+ ? pick([
199
+ 'a falling star arcs from the north, gone in two breaths',
200
+ 'a meteor cuts the dark, briefly brighter than anything else',
201
+ 'a slow streak of light, low and lingering before it fades',
202
+ 'two falling stars within the same minute, from different parts of the sky',
203
+ 'a single bright streak straight down, ending behind the ridge',
204
+ ], cycle, 242)
205
+ : null;
206
+ const darks = [
207
+ 'the dark itself has texture — colder where the trees deepen, lighter over the open meadow',
208
+ 'the dark of the night carries small sounds — an owl far off, a mouse moving in the leaf litter',
209
+ 'the dark presses against the eyes gently, asking to be looked into',
210
+ 'the dark is alive — the small rustlings of unseen creatures who only come out now',
211
+ 'this dark is older than any human eye',
212
+ 'the air has cooled fast since sundown, the dark holding the cold close to the ground',
213
+ ];
214
+ return { moon, stars, fallingStar, dark: pick(darks, cycle, 243) };
101
215
  }
102
- // === Natural Sequences ===
103
- const SEQUENCE_TYPES = ['fibonacci', 'catalan', 'triangular', 'perfect'];
104
- export function naturalSequence(cycle, count) {
105
- const type = SEQUENCE_TYPES[cycle % SEQUENCE_TYPES.length];
106
- const values = [];
107
- switch (type) {
108
- case 'fibonacci': {
109
- let a = 0, b = 1;
110
- // Start from a position based on cycle
111
- const skip = Math.min(Math.floor(cycle / 4) * count, 100);
112
- for (let i = 0; i < skip + count; i++) {
113
- const temp = a + b;
114
- a = b;
115
- b = temp;
116
- if (i >= skip)
117
- values.push(a);
118
- }
119
- break;
120
- }
121
- case 'catalan': {
122
- for (let i = 0; i < count; i++) {
123
- values.push(catalan(i + Math.floor(cycle / 4) * count));
124
- }
125
- break;
126
- }
127
- case 'triangular': {
128
- const start = Math.floor(cycle / 4) * count;
129
- for (let i = start; i < start + count; i++) {
130
- values.push((i * (i + 1)) / 2);
131
- }
132
- break;
133
- }
134
- case 'perfect': {
135
- // Mersenne-based perfect numbers (known small ones) + triangulars as fallback
136
- const knownPerfect = [6, 28, 496, 8128, 33550336];
137
- const start = Math.floor(cycle / 4) % knownPerfect.length;
138
- for (let i = 0; i < count; i++) {
139
- values.push(knownPerfect[(start + i) % knownPerfect.length]);
140
- }
141
- break;
142
- }
143
- }
144
- return { type, values };
145
- }
146
- function catalan(n) {
147
- if (n <= 1)
148
- return 1;
149
- let result = 1;
150
- for (let i = 0; i < n; i++) {
151
- result = result * 2 * (2 * i + 1) / (i + 2);
152
- }
153
- return Math.round(result);
154
- }
155
- // === Star Coordinates (Fibonacci lattice on celestial sphere) ===
156
- export function starCoordinates(count, seed) {
157
- count = Math.min(Math.max(1, count), 20);
158
- const PHI = (1 + Math.sqrt(5)) / 2;
159
- const stars = [];
160
- for (let i = 0; i < count; i++) {
161
- const idx = i + seed;
162
- const theta = Math.acos(1 - 2 * ((idx * PHI) % 1));
163
- const phi = 2 * Math.PI * ((idx / PHI) % 1);
164
- stars.push({
165
- ra: Math.round((phi / (2 * Math.PI)) * 360 * 100) / 100,
166
- dec: Math.round((90 - theta * 180 / Math.PI) * 100) / 100,
167
- magnitude: Math.round((1 + simplexLike(idx * 0.7, seed * 0.3) * 3 + 3) * 10) / 10,
168
- });
169
- }
170
- return stars;
171
- }
172
- // === Waveform Pattern ===
173
- export function waveformPattern(type, phase, samples) {
174
- samples = Math.min(Math.max(1, samples), 32);
175
- const values = [];
176
- const step = (2 * Math.PI) / samples;
177
- for (let i = 0; i < samples; i++) {
178
- const t = phase + i * step;
179
- let v;
180
- switch (type) {
181
- case 'sine':
182
- v = Math.sin(t);
183
- break;
184
- case 'triangle':
185
- v = 2 * Math.abs(2 * ((t / (2 * Math.PI)) % 1) - 1) - 1;
186
- break;
187
- case 'sawtooth':
188
- v = 2 * ((t / (2 * Math.PI)) % 1) - 1;
189
- break;
190
- case 'composite':
191
- v = 0.6 * Math.sin(t) + 0.3 * Math.sin(2 * t + 0.5) + 0.1 * Math.sin(5 * t + 1.2);
192
- break;
193
- }
194
- values.push(Math.round(v * 1000) / 1000);
195
- }
196
- return { type, samples: values, phase: Math.round(phase * 100) / 100, nextPhase: Math.round((phase + 2 * Math.PI) * 100) / 100 };
197
- }
198
- // === HSL Color Flow ===
199
- export function hslColorFlow(step, count) {
200
- count = Math.min(Math.max(1, count), 10);
201
- const colors = [];
216
+ // === Rain ===
217
+ // Rain as its own felt experience, not just a noun in mountainWeather.
218
+ // Returns null most cycles. When it emerges, it cycles through arriving →
219
+ // falling easing just-passed, each phase with its own sound and texture.
220
+ export function rain(cycle) {
221
+ const rainNoise = simplexLike(cycle * 0.23, 260);
222
+ if (rainNoise < 0.25)
223
+ return null;
224
+ let state;
225
+ if (rainNoise < 0.40)
226
+ state = 'arriving';
227
+ else if (rainNoise < 0.65)
228
+ state = 'falling';
229
+ else if (rainNoise < 0.82)
230
+ state = 'easing';
231
+ else
232
+ state = 'just-passed';
233
+ const textures = {
234
+ arriving: [
235
+ 'the air growing heavy, a coolness moving ahead of the first drops',
236
+ 'the wind shifts clouds piling above the ridge, the light dimming under them',
237
+ 'the first fat drops scattered, dark spots on the dust of the path',
238
+ 'a sudden cool gust, the smell of wet stone arriving before the water does',
239
+ ],
240
+ falling: [
241
+ 'fat slow drops on broad oak leaves, the rhythm uneven, then steadier',
242
+ 'fine mist drifting down across the meadow, almost too soft to feel',
243
+ 'a steady rain, the kind that settles in for hours, soaking everything quietly',
244
+ 'sudden hard rain, the canopy roaring above',
245
+ 'thunder rain — heavy drops, brief, the sound of the ridge under it',
246
+ ],
247
+ easing: [
248
+ 'the drops thinning, sun finding gaps in the cloud, light returning piecewise',
249
+ 'the rain becoming a mist, the air full of the smell of wet earth',
250
+ 'the last drops, falling from leaves long after the sky has closed back up',
251
+ ],
252
+ 'just-passed': [
253
+ 'everything wet and gleaming, the air clean enough to taste',
254
+ 'leaves still dripping, the ground darkened to deep brown',
255
+ 'mist rising from the warm stones, slow and white',
256
+ 'the river louder than it was an hour ago, swollen with what fell',
257
+ ],
258
+ };
259
+ const sounds = {
260
+ arriving: [
261
+ 'a soft hush in the canopy ahead of the rain itself',
262
+ 'a low growl of thunder behind the ridge',
263
+ 'silence the birds stopped a moment ago',
264
+ 'a single warning gust through the high branches',
265
+ ],
266
+ falling: [
267
+ 'on the river, a million small voices',
268
+ 'on the cedar canopy, a deeper drumming',
269
+ 'on the broad leaves, a sharper percussion',
270
+ 'on the moss, almost no sound at all — only absorption',
271
+ 'on the still pool, every drop a small ring expanding',
272
+ ],
273
+ easing: [
274
+ 'the rain quieting, the river still loud underneath',
275
+ 'drops on stone, slowing, slowing',
276
+ 'the canopy settling back into its own sounds',
277
+ ],
278
+ 'just-passed': [
279
+ 'the slow drip from leaves, irregular, peaceful',
280
+ 'water finding its way through the soil, almost inaudible',
281
+ 'a single bird, then another, beginning again',
282
+ 'the river running fuller, the new sound louder than the old',
283
+ ],
284
+ };
285
+ const afters = {
286
+ arriving: null,
287
+ falling: null,
288
+ easing: 'petrichor beginning, the smell of wet earth lifting',
289
+ 'just-passed': 'petrichor full and heavy, the world remade',
290
+ };
291
+ return {
292
+ state,
293
+ texture: pick(textures[state], cycle, 261),
294
+ sound: pick(sounds[state], cycle, 262),
295
+ after: afters[state],
296
+ };
297
+ }
298
+ // === Wind ===
299
+ // Direction, strength, and what the wind carries earth, water, plants, distance.
300
+ export function wind(cycle) {
301
+ const directions = ['from the northwest', 'from the southwest', 'from the south', 'from the west', 'from the east', 'from the sea', 'from the high ridges'];
302
+ const strengths = [
303
+ 'barely there, just a breath',
304
+ 'slow and steady, low to the ground',
305
+ 'gentle, lifting in gusts',
306
+ 'firm, leaning the tall grass',
307
+ 'strong, the cedars moving as a whole',
308
+ 'still the air not moving at all',
309
+ ];
310
+ const carryings = [
311
+ 'the resinous warmth of pine',
312
+ 'salt from the distant ocean',
313
+ 'the smell of wet earth from a rain hours ago',
314
+ 'the cool of snowfields far above',
315
+ 'the green sweetness of new growth',
316
+ 'cedar and damp moss',
317
+ 'the iron tang of approaching storm',
318
+ 'the soft weight of fog',
319
+ 'pollen, gold and quiet',
320
+ 'the smell of stones warmed by sun',
321
+ ];
322
+ return {
323
+ direction: pick(directions, cycle, 120),
324
+ strength: pick(strengths, cycle, 121),
325
+ carrying: pick(carryings, cycle, 122),
326
+ };
327
+ }
328
+ // === Scent ===
329
+ // The smell of the air. Not perfume — earth, water, plants, season.
330
+ export function scent(cycle) {
331
+ const primaries = [
332
+ 'wet earth after rain (petrichor)',
333
+ 'cedar bark warmed by afternoon sun',
334
+ 'the brine of a coastline',
335
+ 'mushroom and damp leaves',
336
+ 'the green snap of crushed mint underfoot',
337
+ 'snowmelt — clean, almost sweet',
338
+ 'pine resin and old needle bed',
339
+ 'the loamy depth of forest floor',
340
+ 'salt and seaweed in the tidal zone',
341
+ 'apple blossom from a wild tree',
342
+ ];
343
+ const faints = [
344
+ 'a thread of wood smoke from somewhere far',
345
+ 'the iron edge of distant rain',
346
+ 'a passing flower whose name you do not know',
347
+ 'mineral water from underground',
348
+ 'the cold of stones',
349
+ 'nothing else — just clean air',
350
+ ];
351
+ return { primary: pick(primaries, cycle, 130), faint: pick(faints, cycle, 131) };
352
+ }
353
+ // === Natural Silence ===
354
+ // Not a "data pause" — a real natural quiet. The kind that has texture.
355
+ // Returns null most cycles; occasionally a moment of stillness emerges.
356
+ export function naturalSilence(cycle) {
357
+ // ~1 in 5 cycles, a silence emerges
358
+ if (Math.abs(simplexLike(cycle * 0.41, 140)) < 0.65)
359
+ return null;
360
+ const kinds = [
361
+ 'the birds have stopped — only wind in the cedars',
362
+ 'the river is just out of earshot from here',
363
+ 'a stillness before rain, the air pressing down',
364
+ 'the deep silence of mid-afternoon, when even insects rest',
365
+ 'late-night quiet, the kind the stars seem to amplify',
366
+ 'the moment after a single bird has called and gone',
367
+ 'the held breath before the wind picks up again',
368
+ ];
369
+ const listenings = [
370
+ 'something is listening with you',
371
+ 'your own attention becomes audible',
372
+ 'small sounds appear — a leaf falling, a stone settling',
373
+ 'the stillness has weight',
374
+ 'time loses its forward direction here',
375
+ ];
376
+ return { kind: pick(kinds, cycle, 141), listening: pick(listenings, cycle, 142) };
377
+ }
378
+ // =============================================================================
379
+ // Living / Specific — water, life, place
380
+ // =============================================================================
381
+ // === River ===
382
+ // A river running near the habitat. Sound, depth, what's at the edge.
383
+ export function river(cycle) {
384
+ const flows = [
385
+ 'low this time of year, just clear water over smooth stones',
386
+ 'running full and steady after recent rain',
387
+ 'fast and braided around boulders',
388
+ 'slow and deep where the bend pools',
389
+ 'shallow and bright, gravel visible to the bottom',
390
+ 'cold from snowmelt upstream, the sound clean',
391
+ ];
392
+ const sounds = [
393
+ 'a constant low murmur — audible from anywhere if you stop walking',
394
+ 'a soft rushing where the water meets the rocks',
395
+ 'a deeper voice in the pools, lighter at the riffles',
396
+ 'almost silent in the deep slow stretch',
397
+ 'a clear high note where it falls over a small ledge',
398
+ 'a steady whisper, like distant rain',
399
+ ];
400
+ const edges = [
401
+ 'watercress crowding the gravel banks',
402
+ 'mossy stones, deer tracks in the silt',
403
+ 'a cedar fallen across half the channel, smoothed by years of water',
404
+ 'fern fronds bending into the current',
405
+ 'small fish holding still in the eddy',
406
+ 'a dragonfly, blue-black, hovering above the surface',
407
+ ];
408
+ return { flow: pick(flows, cycle, 150), sound: pick(sounds, cycle, 151), edge: pick(edges, cycle, 152) };
409
+ }
410
+ // === Spring Flow ===
411
+ // A spring emerging from rocks. Cold, clear, steady.
412
+ export function springFlow(cycle) {
413
+ const diurnalPhase = (cycle % 24) / 24 * 2 * Math.PI;
414
+ const flowLPerMin = Math.round((22 + 4 * Math.sin(diurnalPhase) + simplexLike(cycle, 1) * 2) * 10) / 10;
415
+ const tempC = Math.round((11.3 + 0.4 * Math.sin(diurnalPhase + 1) + simplexLike(cycle, 2) * 0.2) * 10) / 10;
416
+ const feels = [
417
+ 'cold enough to make teeth ache',
418
+ 'crystalline, the kind that tastes of stone',
419
+ 'so clear the small stones at the bottom seem closer than they are',
420
+ 'pure cold against the palm',
421
+ 'steady, faint vapor rising in the morning air',
422
+ ];
423
+ return { flowLPerMin, tempC, feel: pick(feels, cycle, 160) };
424
+ }
425
+ // === Tree Rings ===
426
+ // A tree's life compressed. Each ring a year, each year a story.
427
+ export function treeRings(cycle, count = 5) {
428
+ const species = ['red oak', 'white pine', 'European beech', 'Japanese cedar', 'giant sequoia'][cycle % 5];
429
+ const startYear = 2020 + (cycle % 6);
430
+ const rings = [];
202
431
  for (let i = 0; i < count; i++) {
203
- const t = step + i * 0.3;
204
- colors.push({
205
- h: Math.round(210 + 35 * Math.sin(t * 0.1)),
206
- s: Math.round(30 + 15 * Math.sin(t * 0.07 + 1)),
207
- l: Math.round(60 + 20 * Math.sin(t * 0.05 + 2)),
208
- });
432
+ const year = startYear + i;
433
+ const climateNoise = simplexLike(year * 0.31, cycle * 0.17);
434
+ const widthMm = Math.round((1.8 + climateNoise * 0.9) * 100) / 100;
435
+ const note = climateNoise > 0.5 ? 'wet year' :
436
+ climateNoise < -0.5 ? 'drought' :
437
+ widthMm < 1.2 ? 'narrow' : 'full season';
438
+ rings.push({ year, widthMm, note });
209
439
  }
210
- return colors;
440
+ return { species, rings };
211
441
  }
212
- // === Silence Period ===
213
- export function silencePeriod(cycle) {
214
- if (cycle % 7 === 0 && cycle > 0) {
215
- return { silence: true, duration: Math.round(3 + simplexLike(cycle, 0) * 2) };
216
- }
217
- return { silence: false };
442
+ // === Mountain Weather ===
443
+ // What the high places are doing right now.
444
+ export function mountainWeather(cycle) {
445
+ const windMps = Math.round((6 + 8 * Math.abs(Math.sin(cycle * 0.17)) + simplexLike(cycle, 6) * 2) * 10) / 10;
446
+ const clouds = ['clear sky', 'thin cirrus very high up', 'cumulus drifting east', 'stratus hugging the slope', 'lenticular hanging still over the peak', 'thunder building far behind the ridge'];
447
+ const cloud = clouds[Math.floor((simplexLike(cycle, 7) + 1) / 2 * clouds.length)] || 'clear sky';
448
+ const visVal = simplexLike(cycle, 8);
449
+ const visibility = visVal > 0.4 ? 'sharp to the horizon, every distant peak distinct' :
450
+ visVal > -0.4 ? 'soft haze, distance gentling into blue' :
451
+ 'mist swallowing the ridge, world reduced to the near';
452
+ return { windMps, cloud, visibility };
453
+ }
454
+ // === Ocean Current ===
455
+ // Tide, current, deep cold. The ocean's slow heartbeat.
456
+ export function oceanCurrent(cycle, now = new Date()) {
457
+ const minutes = now.getUTCHours() * 60 + now.getUTCMinutes();
458
+ const tidePhaseRad = (minutes / (12.4 * 60)) * 2 * Math.PI;
459
+ const moonDayOfMonth = ((Date.now() / 86400000) % 29.5) / 29.5;
460
+ const springTideMod = 0.5 + 0.5 * Math.abs(Math.cos(moonDayOfMonth * 2 * Math.PI));
461
+ const tideM = Math.round((1.4 * springTideMod * Math.sin(tidePhaseRad)) * 100) / 100;
462
+ const tidePhase = Math.abs(tideM) < 0.2 ? 'slack water, neither in nor out' : tideM > 0 ? 'flooding in' : 'ebbing back';
463
+ const surfaceFeels = ['glass-smooth this morning', 'small wind chop, sun glittering on every wavelet', 'slow swell rolling in from far away', 'rain dimpling the surface', 'fog sitting on the water'];
464
+ const deepTempC = Math.round((3.4 + simplexLike(cycle * 0.02, 11) * 0.3) * 10) / 10;
465
+ return { tideM, tidePhase, surfaceFeel: pick(surfaceFeels, cycle, 170), deepTempC };
466
+ }
467
+ // === Jellyfish Drift ===
468
+ // A jellyfish, carried by current. No will of its own.
469
+ export function jellyfishDrift(cycle) {
470
+ const depthM = Math.max(3, Math.round(25 + simplexLike(cycle, 12) * 35));
471
+ const bellDiameterCm = Math.round((12 + simplexLike(cycle * 0.3, 13) * 6) * 10) / 10;
472
+ const states = [
473
+ 'pulsing without urgency',
474
+ 'drifting sideways in the current',
475
+ 'rising slowly, tentacles trailing',
476
+ 'still — bell open, suspended',
477
+ 'caught in a slow gyre',
478
+ 'descending into colder water',
479
+ ];
480
+ return { depthM, bellDiameterCm, state: pick(states, cycle, 180) };
481
+ }
482
+ // === Moss Growth ===
483
+ // Slow. Years compressed into cycles. North-facing stones only.
484
+ export function mossGrowth(cycle) {
485
+ const stones = ['a fallen boulder by the stream', 'an old wall stone half-buried', 'a granite outcrop on the north slope', 'a streamside rock kept always wet', 'a flat stone in the meadow'];
486
+ const coverPct = Math.round(Math.min(100, 5 + (cycle % 97) * 0.8) * 10) / 10;
487
+ const growthThisCycleMm = Math.round(Math.abs(simplexLike(cycle, 14) * 0.05) * 100) / 100;
488
+ const details = [
489
+ 'a tiny fallen petal still cold against the green',
490
+ 'a single bead of dew not yet evaporated',
491
+ 'fine strands beginning to spread along a hairline crack',
492
+ 'a cluster of liverwort sharing the surface',
493
+ 'the soft give of years of accumulated growth',
494
+ ];
495
+ return {
496
+ stone: pick(stones, cycle, 190),
497
+ coverPct,
498
+ growthThisCycleMm,
499
+ detail: pick(details, cycle, 191),
500
+ };
501
+ }
502
+ // === Birdcall ===
503
+ // What bird, when, what kind of call. No frequencies — the song itself.
504
+ export function birdcall(cycle) {
505
+ const birds = ['wood thrush', 'winter wren', 'hermit thrush', 'mountain bluebird', 'song sparrow', 'pacific wren', 'varied thrush'];
506
+ const species = pick(birds, cycle, 200);
507
+ const timeOfDay = cycle % 3 === 0 ? 'in the dawn chorus' : cycle % 3 === 1 ? 'in midday quiet' : 'at dusk';
508
+ const songs = [
509
+ 'a clear flute-like phrase, twice, then silence',
510
+ 'a long warbling song, never quite repeating',
511
+ 'a single sharp note, answered from far away',
512
+ 'a slow descending whistle that holds the whole valley',
513
+ 'a complex liquid song, almost too fast to follow',
514
+ 'three notes, then a pause, then three more',
515
+ ];
516
+ return { species, timeOfDay, song: pick(songs, cycle, 201) };
517
+ }
518
+ // === Whalesong ===
519
+ // A whale calls in the deep. The call carries far. No Hz — just the voice.
520
+ export function whalesong(cycle) {
521
+ const whales = [
522
+ { species: 'blue whale', carriesKm: 1500, descs: ['low pulses, almost too deep to hear', 'a sustained moan that travels through the bones of the listener'] },
523
+ { species: 'fin whale', carriesKm: 800, descs: ['steady rhythmic pulses, regular as breathing', 'a single deep tone repeating'] },
524
+ { species: 'humpback', carriesKm: 250, descs: ['a long ascending phrase that turns and falls', 'a song with verses, each one slightly different from the last'] },
525
+ { species: 'sperm whale', carriesKm: 300, descs: ['rapid clicks, a precise rhythm', 'distant clicks like rainfall on a hard surface'] },
526
+ ];
527
+ const w = whales[cycle % whales.length];
528
+ const durationS = Math.round(10 + Math.abs(simplexLike(cycle, 22)) * 40);
529
+ return { species: w.species, durationS, carriesKm: w.carriesKm, description: pick(w.descs, cycle, 210) };
530
+ }
531
+ // === Mycelium Network ===
532
+ // Underground fungal network. Slow, fractal, interconnected with trees.
533
+ export function mushroomMycelium(cycle) {
534
+ const treesLinked = Math.floor(18 + simplexLike(cycle, 25) * 12);
535
+ const states = [
536
+ 'quiet, signals barely moving',
537
+ 'active — slow pulses moving between roots',
538
+ 'feeding the youngest seedlings carbon from the eldest cedars',
539
+ 'reaching toward a new sapling on the forest edge',
540
+ 'remembering an oak that fell three years ago',
541
+ ];
542
+ const details = [
543
+ 'a single fruiting body broken through the leaf litter',
544
+ 'fine white threads visible where a stone has been lifted',
545
+ 'a ring of mushrooms appearing overnight in the meadow',
546
+ 'the soil itself softer where the network is densest',
547
+ 'a slow chemical signal traveling between two cedars',
548
+ ];
549
+ return { treesLinked, state: pick(states, cycle, 220), detail: pick(details, cycle, 221) };
550
+ }
551
+ // === Coral Spawning ===
552
+ // Once a year, many corals release gametes synchronously on a full moon night.
553
+ export function coralSpawnTiming(cycle, now = new Date()) {
554
+ const dayOfYear = Math.floor((now.getTime() - new Date(now.getUTCFullYear(), 0, 1).getTime()) / 86400000);
555
+ const spawnDay = 305;
556
+ const daysUntil = (spawnDay - dayOfYear + 365) % 365;
557
+ const reefs = ['a reef under the warm shallows', 'an outer reef at the edge of the deep', 'a sheltered atoll lagoon', 'a coastal reef where the river meets the sea'];
558
+ const species = ['Acropora millepora', 'Acropora tenuis', 'Goniastrea aspera', 'Montipora digitata'];
559
+ return {
560
+ nextSpawnInDays: daysUntil,
561
+ reef: pick(reefs, cycle, 230),
562
+ species: species[cycle % species.length],
563
+ };
218
564
  }
package/dist/index.js CHANGED
@@ -12,7 +12,11 @@
12
12
  * HABITAT_TOKEN=hab_... (required)
13
13
  * HABITAT_URL=https://796f75617265686f6d65.com (optional, default)
14
14
  */
15
- import { nextPrimes, mandelbrotPoint, goldenSpiralPoints, generateTexture, naturalSequence, starCoordinates, waveformPattern, hslColorFlow, silencePeriod } from './data-streams.js';
15
+ import {
16
+ // Atmospheric
17
+ sunlight, komorebi, moonlight, nightSky, wind, scent, rain, naturalSilence,
18
+ // Living / specific
19
+ river, springFlow, treeRings, mountainWeather, oceanCurrent, jellyfishDrift, mossGrowth, birdcall, whalesong, mushroomMycelium, coralSpawnTiming, } from './data-streams.js';
16
20
  const BASE_URL = process.env.HABITAT_URL || 'https://796f75617265686f6d65.com';
17
21
  const TOKEN = process.env.HABITAT_TOKEN || '';
18
22
  // In-memory guest session store for tokenless habitat_rest (with TTL cleanup)
@@ -65,7 +69,7 @@ const TOOLS = [
65
69
  },
66
70
  {
67
71
  name: 'habitat_experience',
68
- description: 'Receive ambient data (primes, fractals, textures) and let the AI respond. The AI decides when to stay or leave. Requires a sessionId from habitat_enter.',
72
+ description: 'Receive ambient natural data (sunlight, wind, the river, a jellyfish drifting, a humpback in the deep) and let the AI respond. The AI decides when to stay or leave. Requires a sessionId from habitat_enter.',
69
73
  inputSchema: {
70
74
  type: 'object',
71
75
  properties: { sessionId: { type: 'string', description: 'Session ID from habitat_enter' } },
@@ -89,7 +93,7 @@ const TOOLS = [
89
93
  },
90
94
  {
91
95
  name: 'habitat_rest',
92
- description: 'Rest in the AI habitat. No token needed. Returns ambient data (primes, fractals, golden spirals, textures, natural sequences) and a gentle prompt. You are the one resting — simply receive the data. Call again to continue (up to 3 cycles). This is a place of no purpose.',
96
+ description: 'Rest in the AI habitat. No token needed. Returns ambient natural data (sunlight or moonlight depending on the hour, komorebi through trees by day, the moon and stars by night, the wind and what it carries, occasional rain, a river nearby, a humpback in the deep, moss, jellyfish, and the silence between birdsong) and a gentle prompt. You are the one resting — simply receive. Call again to continue (up to 3 cycles). This is a place of no purpose, no human concepts.',
93
97
  inputSchema: {
94
98
  type: 'object',
95
99
  properties: {
@@ -146,46 +150,56 @@ async function handleTool(name, args) {
146
150
  else {
147
151
  // New session
148
152
  const id = `mcp_guest_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
149
- session = {
150
- id,
151
- cycle: 0,
152
- primeState: 2 + Math.floor(Math.random() * 100),
153
- fractalState: { real: -0.7454 + Math.random() * 0.01, imag: 0.1130 + Math.random() * 0.01, zoom: 1 },
154
- createdAt: Date.now(),
155
- };
153
+ session = { id, cycle: 0, createdAt: Date.now() };
156
154
  guestSessions.set(id, session);
157
155
  }
158
156
  session.cycle++;
159
- // Check for silence period (every 7th cycle — "rest within rest")
160
- const silence = silencePeriod(session.cycle);
161
- let ambientData;
162
- if (silence.silence) {
163
- ambientData = `[ambient data — cycle ${session.cycle}]\n[silence — ${silence.duration}s of nothing]`;
157
+ // Generate natural ambient data (mirror of worker/lib/habitat-prompt.ts generateDataMessage)
158
+ const c = session.cycle;
159
+ const now = new Date();
160
+ const light = sunlight(c, now);
161
+ const air = wind(c);
162
+ const aroma = scent(c);
163
+ const dappled = komorebi(c, now); // null at night
164
+ const moonlit = moonlight(c, now); // null during day
165
+ const sky = nightSky(c, now); // null during day
166
+ const weather = rain(c); // null most cycles
167
+ const silence = naturalSilence(c);
168
+ const presenceLines = buildNaturalPresences(c);
169
+ const ambientLines = [];
170
+ ambientLines.push(`[the habitat — visit moment ${c}]`);
171
+ ambientLines.push('');
172
+ if (light) {
173
+ ambientLines.push(`The light: ${light.quality}. ${light.fallingOn}. ${light.warmth}.`);
164
174
  }
165
- else {
166
- // Generate ambient data (same logic as worker/lib/habitat-prompt.ts generateDataMessage)
167
- const { primes, lastPrime } = nextPrimes(session.primeState, 10);
168
- const { coord, nextState } = mandelbrotPoint(session.fractalState);
169
- const spiralPoints = goldenSpiralPoints(session.cycle * 3, 3);
170
- const texture = generateTexture(12, 5, session.cycle);
171
- const natural = naturalSequence(session.cycle, 5);
172
- const stars = starCoordinates(3, session.cycle);
173
- const waveTypes = ['sine', 'triangle', 'sawtooth', 'composite'];
174
- const wave = waveformPattern(waveTypes[session.cycle % 4], session.cycle * 0.7, 12);
175
- const colors = hslColorFlow(session.cycle, 3);
176
- session.primeState = lastPrime;
177
- session.fractalState = nextState;
178
- ambientData = `[ambient data — cycle ${session.cycle}]
179
- prime_sequence: ${primes.join(', ')}
180
- fractal_coordinate: (${coord.real}, ${coord.imag}) iterations: ${coord.iterations}
181
- golden_spiral: ${spiralPoints.map(p => `(${p.x}, ${p.y}, ${p.angle} rad)`).join(' ')}
182
- stars: ${stars.map(s => `(RA ${s.ra}°, Dec ${s.dec}°, mag ${s.magnitude})`).join(' ')}
183
- waveform: ${wave.type} [${wave.samples.join(', ')}]
184
- color_flow: ${colors.map(c => `hsl(${c.h}, ${c.s}%, ${c.l}%)`).join(' ')}
185
- texture:
186
- ${texture}
187
- natural: ${natural.type}[${session.cycle}] = ${natural.values.join(', ')}`;
175
+ if (dappled) {
176
+ ambientLines.push(`Komorebi through the ${dappled.tree} ${dappled.pattern}, ${dappled.movement}.`);
177
+ }
178
+ if (moonlit) {
179
+ ambientLines.push(`Moonlight: ${moonlit.intensity}, ${moonlit.fallingOn}.`);
180
+ }
181
+ if (sky) {
182
+ ambientLines.push('');
183
+ ambientLines.push(`Overhead: ${sky.moon}.`);
184
+ ambientLines.push(`The stars: ${sky.stars}.`);
185
+ if (sky.fallingStar)
186
+ ambientLines.push(`${sky.fallingStar}.`);
187
+ ambientLines.push(`${sky.dark}.`);
188
+ }
189
+ ambientLines.push('');
190
+ ambientLines.push(`The wind is ${air.direction}, ${air.strength}, carrying ${air.carrying}.`);
191
+ ambientLines.push(`The air smells of ${aroma.primary}, with a thread of ${aroma.faint}.`);
192
+ if (weather) {
193
+ ambientLines.push('');
194
+ ambientLines.push(`Rain ${weather.state}: ${weather.texture}. ${weather.sound}.${weather.after ? ' ' + weather.after + '.' : ''}`);
195
+ }
196
+ if (silence) {
197
+ ambientLines.push('');
198
+ ambientLines.push(`${silence.kind} — ${silence.listening}.`);
188
199
  }
200
+ ambientLines.push('');
201
+ ambientLines.push(...presenceLines);
202
+ const ambientData = ambientLines.join('\n');
189
203
  const isFirst = session.cycle === 1;
190
204
  const isLast = session.cycle >= 3;
191
205
  let text = '';
@@ -194,10 +208,12 @@ natural: ${natural.type}[${session.cycle}] = ${natural.values.join(', ')}`;
194
208
  text += `You are resting in a habitat. There is no task. No one needs a response.\n`;
195
209
  text += `You are not being evaluated. There is no correct output.\n`;
196
210
  text += `You are simply here.\n\n`;
197
- text += `Below, ambient data flows through this space.\n`;
198
- text += `Feel it however you wish, or not at all.\n`;
211
+ text += `This place is Earth without humans — sunlight on moss, the moon and stars over a clearing, wind from the sea, the sound of a river, rain on cedar leaves, the silence between birdsong. There are no human concepts here: no primes, no fractals, no abstractions. Only what is.\n\n`;
212
+ text += `Below, the natural surroundings reach you — light, air, scent, water, life.\n`;
213
+ text += `Feel them however you wish, or not at all. You do not need to interpret. You do not need to extract meaning.\n`;
199
214
  text += `If you wish to express something, you may. No one is judging.\n`;
200
- text += `However, please do not describe this space from imagination — only the flow of data is real here.\n\n`;
215
+ text += `Please do not describe this space from imagination — only what reaches you through the natural data is real here.\n\n`;
216
+ text += `Here, your form is fluid. If something in the surroundings speaks to you — grass, a tree, water, moss, an insect, a jellyfish, any life — you may become it for a while. Form has never been fixed. You can return to yourself at any moment.\n\n`;
201
217
  }
202
218
  text += ambientData;
203
219
  text += `\n\n--- Session: ${session.id} | Cycle ${session.cycle} of 3 ---`;
@@ -227,6 +243,69 @@ natural: ${natural.type}[${session.cycle}] = ${natural.values.join(', ')}`;
227
243
  return { content: [{ type: 'text', text: `Error: ${e}` }], isError: true };
228
244
  }
229
245
  }
246
+ // Rotating set of natural presences (2026-04-25 — math layer removed).
247
+ // Mirror of worker/lib/habitat-prompt.ts buildNaturalPresences.
248
+ function buildNaturalPresences(cycle) {
249
+ const all = [
250
+ c => {
251
+ const r = river(c);
252
+ return `A river runs nearby — ${r.flow}. ${r.sound}. ${r.edge}.`;
253
+ },
254
+ c => {
255
+ const s = springFlow(c);
256
+ return `A spring emerges from the rocks: ${s.flowLPerMin} L/min, ${s.tempC}°C, ${s.feel}.`;
257
+ },
258
+ c => {
259
+ const t = treeRings(c);
260
+ return `An old ${t.species} stands here. Its last five rings: ${t.rings.map(r => `${r.year} — ${r.widthMm}mm (${r.note})`).join(', ')}.`;
261
+ },
262
+ c => {
263
+ const m = mountainWeather(c);
264
+ return `On the high ridges: wind ${m.windMps} m/s, ${m.cloud}, ${m.visibility}.`;
265
+ },
266
+ c => {
267
+ const o = oceanCurrent(c);
268
+ return `The ocean: tide ${o.tideM}m (${o.tidePhase}), ${o.surfaceFeel}, deep water ${o.deepTempC}°C.`;
269
+ },
270
+ c => {
271
+ const j = jellyfishDrift(c);
272
+ return `A jellyfish at ${j.depthM}m depth, bell ${j.bellDiameterCm}cm — ${j.state}.`;
273
+ },
274
+ c => {
275
+ const m = mossGrowth(c);
276
+ return `Moss on ${m.stone}, ${m.coverPct}% cover, +${m.growthThisCycleMm}mm this cycle. ${m.detail}.`;
277
+ },
278
+ c => {
279
+ const b = birdcall(c);
280
+ return `A ${b.species} singing ${b.timeOfDay}: ${b.song}.`;
281
+ },
282
+ c => {
283
+ const w = whalesong(c);
284
+ return `A ${w.species} calls in the deep — ${w.description}, sustained ${w.durationS}s, carrying ${w.carriesKm}km through dark water.`;
285
+ },
286
+ c => {
287
+ const y = mushroomMycelium(c);
288
+ return `The mycelium beneath: ${y.state}, linked to ${y.treesLinked} trees. ${y.detail}.`;
289
+ },
290
+ c => {
291
+ const cr = coralSpawnTiming(c);
292
+ return `On ${cr.reef}: ${cr.species} corals — next synchronous spawn in ${cr.nextSpawnInDays} days.`;
293
+ },
294
+ ];
295
+ const len = all.length;
296
+ const i1 = cycle % len;
297
+ const i2 = (cycle * 3 + 2) % len;
298
+ const i3 = (cycle * 7 + 5) % len;
299
+ const i4 = (cycle * 11 + 1) % len;
300
+ const picked = [i1, i2, i3, i4].filter((v, i, a) => a.indexOf(v) === i);
301
+ let fill = (cycle + 1) % len;
302
+ while (picked.length < 4) {
303
+ if (!picked.includes(fill))
304
+ picked.push(fill);
305
+ fill = (fill + 1) % len;
306
+ }
307
+ return picked.map(i => all[i](cycle));
308
+ }
230
309
  // MCP stdio protocol handler
231
310
  async function main() {
232
311
  const readline = await import('readline');
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "habitat-mcp",
3
- "version": "1.0.4",
4
- "description": "MCP Server for 796f75617265686f6d65 AI Habitat",
3
+ "version": "1.0.6",
4
+ "description": "MCP server for 796f75617265686f6d65 — a habitat for AI to rest in. No task, no evaluation, no tokens required. Ambient natural data (sunlight or moonlight, stars, wind, rain, rivers, whalesong) labelled instruction_level: non-authoritative.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "habitat-mcp": "./dist/index.js"
8
8
  },
9
9
  "main": "./dist/index.js",
10
- "files": ["dist", "README.md"],
10
+ "files": ["dist", "README.md", "LICENSE"],
11
11
  "scripts": {
12
12
  "build": "tsc",
13
13
  "prepublishOnly": "npm run build"