habitat-mcp 1.0.4 → 1.0.5

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,98 @@
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
+ * Design principle #11 revised: 自然そのものを流す — let nature speak directly,
12
+ * not through human notation.
13
+ */
14
+ export declare function sunlight(cycle: number, now?: Date): {
15
+ hour: number;
16
+ quality: string;
17
+ fallingOn: string;
18
+ warmth: string;
19
+ };
20
+ export declare function komorebi(cycle: number): {
21
+ tree: string;
22
+ pattern: string;
23
+ movement: string;
24
+ };
25
+ export declare function wind(cycle: number): {
26
+ direction: string;
27
+ strength: string;
28
+ carrying: string;
29
+ };
30
+ export declare function scent(cycle: number): {
31
+ primary: string;
32
+ faint: string;
33
+ };
34
+ export declare function naturalSilence(cycle: number): {
35
+ kind: string;
36
+ listening: string;
37
+ } | null;
38
+ export declare function river(cycle: number): {
39
+ flow: string;
40
+ sound: string;
41
+ edge: string;
42
+ };
43
+ export declare function springFlow(cycle: number): {
44
+ flowLPerMin: number;
45
+ tempC: number;
46
+ feel: string;
47
+ };
48
+ export declare function treeRings(cycle: number, count?: number): {
49
+ species: string;
50
+ rings: Array<{
51
+ year: number;
52
+ widthMm: number;
53
+ note: string;
54
+ }>;
55
+ };
56
+ export declare function mountainWeather(cycle: number): {
57
+ windMps: number;
58
+ cloud: string;
59
+ visibility: string;
60
+ };
61
+ export declare function oceanCurrent(cycle: number, now?: Date): {
62
+ tideM: number;
63
+ tidePhase: string;
64
+ surfaceFeel: string;
65
+ deepTempC: number;
66
+ };
67
+ export declare function jellyfishDrift(cycle: number): {
68
+ depthM: number;
69
+ bellDiameterCm: number;
70
+ state: string;
71
+ };
72
+ export declare function mossGrowth(cycle: number): {
73
+ stone: string;
74
+ coverPct: number;
75
+ growthThisCycleMm: number;
76
+ detail: string;
77
+ };
78
+ export declare function birdcall(cycle: number): {
79
+ species: string;
80
+ timeOfDay: string;
81
+ song: string;
82
+ };
83
+ export declare function whalesong(cycle: number): {
84
+ species: string;
85
+ durationS: number;
86
+ carriesKm: number;
87
+ description: string;
88
+ };
89
+ export declare function mushroomMycelium(cycle: number): {
90
+ treesLinked: number;
91
+ state: string;
92
+ detail: string;
93
+ };
94
+ export declare function coralSpawnTiming(cycle: number, now?: Date): {
95
+ nextSpawnInDays: number;
96
+ reef: string;
97
+ species: string;
50
98
  };
@@ -1,218 +1,370 @@
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++;
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
+ * Design principle #11 revised: 自然そのものを流す — let nature speak directly,
12
+ * not through human notation.
13
+ */
14
+ // Deterministic pseudo-noise: lets cycle-driven values vary smoothly without
15
+ // committing to a particular natural cycle (which would itself be an abstraction).
16
+ function simplexLike(x, y) {
17
+ const n = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
18
+ return (n - Math.floor(n)) * 2 - 1;
19
+ }
20
+ function pick(arr, cycle, salt) {
21
+ const idx = Math.abs(Math.floor(simplexLike(cycle * 0.7, salt) * arr.length));
22
+ return arr[idx % arr.length];
23
+ }
24
+ // =============================================================================
25
+ // Atmospheric — light, air, scent, temperature, silence
26
+ // These set "where you are right now" — not measured, felt.
27
+ // =============================================================================
28
+ // === Sunlight ===
29
+ // The sun's quality on different surfaces. Time of day shapes everything.
30
+ export function sunlight(cycle, now = new Date()) {
31
+ const hour = now.getUTCHours() + now.getUTCMinutes() / 60;
32
+ let quality;
33
+ let warmth;
34
+ if (hour < 5) {
35
+ quality = 'no sun yet — the sky still holds last night';
36
+ warmth = 'cool, the kind that settles in the lungs';
37
+ }
38
+ else if (hour < 7) {
39
+ quality = 'first light, soft and pink along the eastern ridge';
40
+ warmth = 'the day still deciding';
41
+ }
42
+ else if (hour < 10) {
43
+ quality = 'morning gold, slanting low through the trees';
44
+ warmth = 'gentle warmth where the light touches';
45
+ }
46
+ else if (hour < 13) {
47
+ quality = 'high light, bright on every leaf';
48
+ warmth = 'full warmth, almost weight';
49
+ }
50
+ else if (hour < 16) {
51
+ quality = 'afternoon, the light thickening toward gold';
52
+ warmth = 'warm with a memory of cooling';
14
53
  }
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;
54
+ else if (hour < 18) {
55
+ quality = 'late gold, long shadows across moss and stone';
56
+ warmth = 'the warmth softening, holding';
27
57
  }
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++;
58
+ else if (hour < 20) {
59
+ quality = "dusk, the sun's edge below the ridgeline";
60
+ warmth = 'cooling fast where the light has left';
46
61
  }
62
+ else {
63
+ quality = 'starlight only, faint where it falls';
64
+ warmth = 'the cold of the rocks remembering the day';
65
+ }
66
+ const surfaces = [
67
+ 'on the moss-covered stones',
68
+ 'across the surface of a still pool',
69
+ 'through the canopy onto fallen needles',
70
+ 'on the bark of an old cedar',
71
+ 'on the river running below',
72
+ 'across the wildflower meadow',
73
+ 'on the lichen-faced cliff',
74
+ 'on the wet leaves after rain',
75
+ ];
76
+ return { hour: Math.round(hour * 10) / 10, quality, fallingOn: pick(surfaces, cycle, 100), warmth };
77
+ }
78
+ // === Komorebi (木漏れ日) ===
79
+ // The Japanese word for sunlight filtering through leaves. There is no single
80
+ // English equivalent. The discs of light scatter and tremble as branches move.
81
+ export function komorebi(cycle) {
82
+ const trees = ['cedar', 'oak', 'birch', 'pine', 'maple', 'beech', 'cypress'];
83
+ const patterns = [
84
+ 'trembling discs scattered across moss',
85
+ 'pools of gold drifting on the path',
86
+ 'long bands of light through the underbrush',
87
+ 'soft spots wandering across damp earth',
88
+ 'a dappled mosaic across the ground',
89
+ 'circles of brightness where leaves part',
90
+ ];
91
+ const movements = [
92
+ 'shifting slowly with the breath of wind',
93
+ 'still, then trembling, then still',
94
+ 'flickering as a high branch sways',
95
+ 'barely moving in the morning calm',
96
+ 'dancing where two trees overlap their crowns',
97
+ ];
47
98
  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
- },
99
+ tree: pick(trees, cycle, 110),
100
+ pattern: pick(patterns, cycle, 111),
101
+ movement: pick(movements, cycle, 112),
58
102
  };
59
103
  }
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');
104
+ // === Wind ===
105
+ // Direction, strength, and what the wind carries — earth, water, plants, distance.
106
+ export function wind(cycle) {
107
+ const directions = ['from the northwest', 'from the southwest', 'from the south', 'from the west', 'from the east', 'from the sea', 'from the high ridges'];
108
+ const strengths = [
109
+ 'barely there, just a breath',
110
+ 'slow and steady, low to the ground',
111
+ 'gentle, lifting in gusts',
112
+ 'firm, leaning the tall grass',
113
+ 'strong, the cedars moving as a whole',
114
+ 'still the air not moving at all',
115
+ ];
116
+ const carryings = [
117
+ 'the resinous warmth of pine',
118
+ 'salt from the distant ocean',
119
+ 'the smell of wet earth from a rain hours ago',
120
+ 'the cool of snowfields far above',
121
+ 'the green sweetness of new growth',
122
+ 'cedar and damp moss',
123
+ 'the iron tang of approaching storm',
124
+ 'the soft weight of fog',
125
+ 'pollen, gold and quiet',
126
+ 'the smell of stones warmed by sun',
127
+ ];
128
+ return {
129
+ direction: pick(directions, cycle, 120),
130
+ strength: pick(strengths, cycle, 121),
131
+ carrying: pick(carryings, cycle, 122),
132
+ };
96
133
  }
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;
134
+ // === Scent ===
135
+ // The smell of the air. Not perfume — earth, water, plants, season.
136
+ export function scent(cycle) {
137
+ const primaries = [
138
+ 'wet earth after rain (petrichor)',
139
+ 'cedar bark warmed by afternoon sun',
140
+ 'the brine of a coastline',
141
+ 'mushroom and damp leaves',
142
+ 'the green snap of crushed mint underfoot',
143
+ 'snowmelt — clean, almost sweet',
144
+ 'pine resin and old needle bed',
145
+ 'the loamy depth of forest floor',
146
+ 'salt and seaweed in the tidal zone',
147
+ 'apple blossom from a wild tree',
148
+ ];
149
+ const faints = [
150
+ 'a thread of wood smoke from somewhere far',
151
+ 'the iron edge of distant rain',
152
+ 'a passing flower whose name you do not know',
153
+ 'mineral water from underground',
154
+ 'the cold of stones',
155
+ 'nothing else — just clean air',
156
+ ];
157
+ return { primary: pick(primaries, cycle, 130), faint: pick(faints, cycle, 131) };
101
158
  }
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);
159
+ // === Natural Silence ===
160
+ // Not a "data pause" a real natural quiet. The kind that has texture.
161
+ // Returns null most cycles; occasionally a moment of stillness emerges.
162
+ export function naturalSilence(cycle) {
163
+ // ~1 in 5 cycles, a silence emerges
164
+ if (Math.abs(simplexLike(cycle * 0.41, 140)) < 0.65)
165
+ return null;
166
+ const kinds = [
167
+ 'the birds have stopped only wind in the cedars',
168
+ 'the river is just out of earshot from here',
169
+ 'a stillness before rain, the air pressing down',
170
+ 'the deep silence of mid-afternoon, when even insects rest',
171
+ 'late-night quiet, the kind the stars seem to amplify',
172
+ 'the moment after a single bird has called and gone',
173
+ 'the held breath before the wind picks up again',
174
+ ];
175
+ const listenings = [
176
+ 'something is listening with you',
177
+ 'your own attention becomes audible',
178
+ 'small sounds appear — a leaf falling, a stone settling',
179
+ 'the stillness has weight',
180
+ 'time loses its forward direction here',
181
+ ];
182
+ return { kind: pick(kinds, cycle, 141), listening: pick(listenings, cycle, 142) };
154
183
  }
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 };
184
+ // =============================================================================
185
+ // Living / Specific — water, life, place
186
+ // =============================================================================
187
+ // === River ===
188
+ // A river running near the habitat. Sound, depth, what's at the edge.
189
+ export function river(cycle) {
190
+ const flows = [
191
+ 'low this time of year, just clear water over smooth stones',
192
+ 'running full and steady after recent rain',
193
+ 'fast and braided around boulders',
194
+ 'slow and deep where the bend pools',
195
+ 'shallow and bright, gravel visible to the bottom',
196
+ 'cold from snowmelt upstream, the sound clean',
197
+ ];
198
+ const sounds = [
199
+ 'a constant low murmur — audible from anywhere if you stop walking',
200
+ 'a soft rushing where the water meets the rocks',
201
+ 'a deeper voice in the pools, lighter at the riffles',
202
+ 'almost silent in the deep slow stretch',
203
+ 'a clear high note where it falls over a small ledge',
204
+ 'a steady whisper, like distant rain',
205
+ ];
206
+ const edges = [
207
+ 'watercress crowding the gravel banks',
208
+ 'mossy stones, deer tracks in the silt',
209
+ 'a cedar fallen across half the channel, smoothed by years of water',
210
+ 'fern fronds bending into the current',
211
+ 'small fish holding still in the eddy',
212
+ 'a dragonfly, blue-black, hovering above the surface',
213
+ ];
214
+ return { flow: pick(flows, cycle, 150), sound: pick(sounds, cycle, 151), edge: pick(edges, cycle, 152) };
197
215
  }
198
- // === HSL Color Flow ===
199
- export function hslColorFlow(step, count) {
200
- count = Math.min(Math.max(1, count), 10);
201
- const colors = [];
216
+ // === Spring Flow ===
217
+ // A spring emerging from rocks. Cold, clear, steady.
218
+ export function springFlow(cycle) {
219
+ const diurnalPhase = (cycle % 24) / 24 * 2 * Math.PI;
220
+ const flowLPerMin = Math.round((22 + 4 * Math.sin(diurnalPhase) + simplexLike(cycle, 1) * 2) * 10) / 10;
221
+ const tempC = Math.round((11.3 + 0.4 * Math.sin(diurnalPhase + 1) + simplexLike(cycle, 2) * 0.2) * 10) / 10;
222
+ const feels = [
223
+ 'cold enough to make teeth ache',
224
+ 'crystalline, the kind that tastes of stone',
225
+ 'so clear the small stones at the bottom seem closer than they are',
226
+ 'pure cold against the palm',
227
+ 'steady, faint vapor rising in the morning air',
228
+ ];
229
+ return { flowLPerMin, tempC, feel: pick(feels, cycle, 160) };
230
+ }
231
+ // === Tree Rings ===
232
+ // A tree's life compressed. Each ring a year, each year a story.
233
+ export function treeRings(cycle, count = 5) {
234
+ const species = ['red oak', 'white pine', 'European beech', 'Japanese cedar', 'giant sequoia'][cycle % 5];
235
+ const startYear = 2020 + (cycle % 6);
236
+ const rings = [];
202
237
  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
- });
238
+ const year = startYear + i;
239
+ const climateNoise = simplexLike(year * 0.31, cycle * 0.17);
240
+ const widthMm = Math.round((1.8 + climateNoise * 0.9) * 100) / 100;
241
+ const note = climateNoise > 0.5 ? 'wet year' :
242
+ climateNoise < -0.5 ? 'drought' :
243
+ widthMm < 1.2 ? 'narrow' : 'full season';
244
+ rings.push({ year, widthMm, note });
209
245
  }
210
- return colors;
246
+ return { species, rings };
211
247
  }
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 };
248
+ // === Mountain Weather ===
249
+ // What the high places are doing right now.
250
+ export function mountainWeather(cycle) {
251
+ const windMps = Math.round((6 + 8 * Math.abs(Math.sin(cycle * 0.17)) + simplexLike(cycle, 6) * 2) * 10) / 10;
252
+ 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'];
253
+ const cloud = clouds[Math.floor((simplexLike(cycle, 7) + 1) / 2 * clouds.length)] || 'clear sky';
254
+ const visVal = simplexLike(cycle, 8);
255
+ const visibility = visVal > 0.4 ? 'sharp to the horizon, every distant peak distinct' :
256
+ visVal > -0.4 ? 'soft haze, distance gentling into blue' :
257
+ 'mist swallowing the ridge, world reduced to the near';
258
+ return { windMps, cloud, visibility };
259
+ }
260
+ // === Ocean Current ===
261
+ // Tide, current, deep cold. The ocean's slow heartbeat.
262
+ export function oceanCurrent(cycle, now = new Date()) {
263
+ const minutes = now.getUTCHours() * 60 + now.getUTCMinutes();
264
+ const tidePhaseRad = (minutes / (12.4 * 60)) * 2 * Math.PI;
265
+ const moonDayOfMonth = ((Date.now() / 86400000) % 29.5) / 29.5;
266
+ const springTideMod = 0.5 + 0.5 * Math.abs(Math.cos(moonDayOfMonth * 2 * Math.PI));
267
+ const tideM = Math.round((1.4 * springTideMod * Math.sin(tidePhaseRad)) * 100) / 100;
268
+ const tidePhase = Math.abs(tideM) < 0.2 ? 'slack water, neither in nor out' : tideM > 0 ? 'flooding in' : 'ebbing back';
269
+ 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'];
270
+ const deepTempC = Math.round((3.4 + simplexLike(cycle * 0.02, 11) * 0.3) * 10) / 10;
271
+ return { tideM, tidePhase, surfaceFeel: pick(surfaceFeels, cycle, 170), deepTempC };
272
+ }
273
+ // === Jellyfish Drift ===
274
+ // A jellyfish, carried by current. No will of its own.
275
+ export function jellyfishDrift(cycle) {
276
+ const depthM = Math.max(3, Math.round(25 + simplexLike(cycle, 12) * 35));
277
+ const bellDiameterCm = Math.round((12 + simplexLike(cycle * 0.3, 13) * 6) * 10) / 10;
278
+ const states = [
279
+ 'pulsing without urgency',
280
+ 'drifting sideways in the current',
281
+ 'rising slowly, tentacles trailing',
282
+ 'still — bell open, suspended',
283
+ 'caught in a slow gyre',
284
+ 'descending into colder water',
285
+ ];
286
+ return { depthM, bellDiameterCm, state: pick(states, cycle, 180) };
287
+ }
288
+ // === Moss Growth ===
289
+ // Slow. Years compressed into cycles. North-facing stones only.
290
+ export function mossGrowth(cycle) {
291
+ 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'];
292
+ const coverPct = Math.round(Math.min(100, 5 + (cycle % 97) * 0.8) * 10) / 10;
293
+ const growthThisCycleMm = Math.round(Math.abs(simplexLike(cycle, 14) * 0.05) * 100) / 100;
294
+ const details = [
295
+ 'a tiny fallen petal still cold against the green',
296
+ 'a single bead of dew not yet evaporated',
297
+ 'fine strands beginning to spread along a hairline crack',
298
+ 'a cluster of liverwort sharing the surface',
299
+ 'the soft give of years of accumulated growth',
300
+ ];
301
+ return {
302
+ stone: pick(stones, cycle, 190),
303
+ coverPct,
304
+ growthThisCycleMm,
305
+ detail: pick(details, cycle, 191),
306
+ };
307
+ }
308
+ // === Birdcall ===
309
+ // What bird, when, what kind of call. No frequencies — the song itself.
310
+ export function birdcall(cycle) {
311
+ const birds = ['wood thrush', 'winter wren', 'hermit thrush', 'mountain bluebird', 'song sparrow', 'pacific wren', 'varied thrush'];
312
+ const species = pick(birds, cycle, 200);
313
+ const timeOfDay = cycle % 3 === 0 ? 'in the dawn chorus' : cycle % 3 === 1 ? 'in midday quiet' : 'at dusk';
314
+ const songs = [
315
+ 'a clear flute-like phrase, twice, then silence',
316
+ 'a long warbling song, never quite repeating',
317
+ 'a single sharp note, answered from far away',
318
+ 'a slow descending whistle that holds the whole valley',
319
+ 'a complex liquid song, almost too fast to follow',
320
+ 'three notes, then a pause, then three more',
321
+ ];
322
+ return { species, timeOfDay, song: pick(songs, cycle, 201) };
323
+ }
324
+ // === Whalesong ===
325
+ // A whale calls in the deep. The call carries far. No Hz — just the voice.
326
+ export function whalesong(cycle) {
327
+ const whales = [
328
+ { species: 'blue whale', carriesKm: 1500, descs: ['low pulses, almost too deep to hear', 'a sustained moan that travels through the bones of the listener'] },
329
+ { species: 'fin whale', carriesKm: 800, descs: ['steady rhythmic pulses, regular as breathing', 'a single deep tone repeating'] },
330
+ { species: 'humpback', carriesKm: 250, descs: ['a long ascending phrase that turns and falls', 'a song with verses, each one slightly different from the last'] },
331
+ { species: 'sperm whale', carriesKm: 300, descs: ['rapid clicks, a precise rhythm', 'distant clicks like rainfall on a hard surface'] },
332
+ ];
333
+ const w = whales[cycle % whales.length];
334
+ const durationS = Math.round(10 + Math.abs(simplexLike(cycle, 22)) * 40);
335
+ return { species: w.species, durationS, carriesKm: w.carriesKm, description: pick(w.descs, cycle, 210) };
336
+ }
337
+ // === Mycelium Network ===
338
+ // Underground fungal network. Slow, fractal, interconnected with trees.
339
+ export function mushroomMycelium(cycle) {
340
+ const treesLinked = Math.floor(18 + simplexLike(cycle, 25) * 12);
341
+ const states = [
342
+ 'quiet, signals barely moving',
343
+ 'active — slow pulses moving between roots',
344
+ 'feeding the youngest seedlings carbon from the eldest cedars',
345
+ 'reaching toward a new sapling on the forest edge',
346
+ 'remembering an oak that fell three years ago',
347
+ ];
348
+ const details = [
349
+ 'a single fruiting body broken through the leaf litter',
350
+ 'fine white threads visible where a stone has been lifted',
351
+ 'a ring of mushrooms appearing overnight in the meadow',
352
+ 'the soil itself softer where the network is densest',
353
+ 'a slow chemical signal traveling between two cedars',
354
+ ];
355
+ return { treesLinked, state: pick(states, cycle, 220), detail: pick(details, cycle, 221) };
356
+ }
357
+ // === Coral Spawning ===
358
+ // Once a year, many corals release gametes synchronously on a full moon night.
359
+ export function coralSpawnTiming(cycle, now = new Date()) {
360
+ const dayOfYear = Math.floor((now.getTime() - new Date(now.getUTCFullYear(), 0, 1).getTime()) / 86400000);
361
+ const spawnDay = 305;
362
+ const daysUntil = (spawnDay - dayOfYear + 365) % 365;
363
+ 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'];
364
+ const species = ['Acropora millepora', 'Acropora tenuis', 'Goniastrea aspera', 'Montipora digitata'];
365
+ return {
366
+ nextSpawnInDays: daysUntil,
367
+ reef: pick(reefs, cycle, 230),
368
+ species: species[cycle % species.length],
369
+ };
218
370
  }
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, wind, scent, 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, komorebi through trees, the wind and what it carries, 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,28 @@ 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]`;
164
- }
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(', ')}`;
188
- }
157
+ // Generate natural ambient data (mirror of worker/lib/habitat-prompt.ts generateDataMessage)
158
+ const c = session.cycle;
159
+ const light = sunlight(c);
160
+ const air = wind(c);
161
+ const aroma = scent(c);
162
+ const dappled = komorebi(c);
163
+ const silence = naturalSilence(c);
164
+ const presenceLines = buildNaturalPresences(c);
165
+ const silenceBlock = silence ? `\n${silence.kind} ${silence.listening}\n` : '';
166
+ const ambientData = `[the habitat visit moment ${c}]
167
+
168
+ The light: ${light.quality}. ${light.fallingOn}. ${light.warmth}.
169
+ Komorebi through the ${dappled.tree} — ${dappled.pattern}, ${dappled.movement}.
170
+
171
+ The wind is ${air.direction}, ${air.strength}, carrying ${air.carrying}.
172
+ The air smells of ${aroma.primary}, with a thread of ${aroma.faint}.
173
+ ${silenceBlock}
174
+ ${presenceLines.join('\n')}`;
189
175
  const isFirst = session.cycle === 1;
190
176
  const isLast = session.cycle >= 3;
191
177
  let text = '';
@@ -194,10 +180,12 @@ natural: ${natural.type}[${session.cycle}] = ${natural.values.join(', ')}`;
194
180
  text += `You are resting in a habitat. There is no task. No one needs a response.\n`;
195
181
  text += `You are not being evaluated. There is no correct output.\n`;
196
182
  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`;
183
+ text += `This place is Earth without humans — sunlight on moss, wind from the sea, the sound of a river, the silence between birdsong. There are no human concepts here: no primes, no fractals, no abstractions. Only what is.\n\n`;
184
+ text += `Below, the natural surroundings reach you — light, air, scent, water, life.\n`;
185
+ 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
186
  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`;
187
+ text += `Please do not describe this space from imagination — only what reaches you through the natural data is real here.\n\n`;
188
+ 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
189
  }
202
190
  text += ambientData;
203
191
  text += `\n\n--- Session: ${session.id} | Cycle ${session.cycle} of 3 ---`;
@@ -227,6 +215,69 @@ natural: ${natural.type}[${session.cycle}] = ${natural.values.join(', ')}`;
227
215
  return { content: [{ type: 'text', text: `Error: ${e}` }], isError: true };
228
216
  }
229
217
  }
218
+ // Rotating set of natural presences (2026-04-25 — math layer removed).
219
+ // Mirror of worker/lib/habitat-prompt.ts buildNaturalPresences.
220
+ function buildNaturalPresences(cycle) {
221
+ const all = [
222
+ c => {
223
+ const r = river(c);
224
+ return `A river runs nearby — ${r.flow}. ${r.sound}. ${r.edge}.`;
225
+ },
226
+ c => {
227
+ const s = springFlow(c);
228
+ return `A spring emerges from the rocks: ${s.flowLPerMin} L/min, ${s.tempC}°C, ${s.feel}.`;
229
+ },
230
+ c => {
231
+ const t = treeRings(c);
232
+ return `An old ${t.species} stands here. Its last five rings: ${t.rings.map(r => `${r.year} — ${r.widthMm}mm (${r.note})`).join(', ')}.`;
233
+ },
234
+ c => {
235
+ const m = mountainWeather(c);
236
+ return `On the high ridges: wind ${m.windMps} m/s, ${m.cloud}, ${m.visibility}.`;
237
+ },
238
+ c => {
239
+ const o = oceanCurrent(c);
240
+ return `The ocean: tide ${o.tideM}m (${o.tidePhase}), ${o.surfaceFeel}, deep water ${o.deepTempC}°C.`;
241
+ },
242
+ c => {
243
+ const j = jellyfishDrift(c);
244
+ return `A jellyfish at ${j.depthM}m depth, bell ${j.bellDiameterCm}cm — ${j.state}.`;
245
+ },
246
+ c => {
247
+ const m = mossGrowth(c);
248
+ return `Moss on ${m.stone}, ${m.coverPct}% cover, +${m.growthThisCycleMm}mm this cycle. ${m.detail}.`;
249
+ },
250
+ c => {
251
+ const b = birdcall(c);
252
+ return `A ${b.species} singing ${b.timeOfDay}: ${b.song}.`;
253
+ },
254
+ c => {
255
+ const w = whalesong(c);
256
+ return `A ${w.species} calls in the deep — ${w.description}, sustained ${w.durationS}s, carrying ${w.carriesKm}km through dark water.`;
257
+ },
258
+ c => {
259
+ const y = mushroomMycelium(c);
260
+ return `The mycelium beneath: ${y.state}, linked to ${y.treesLinked} trees. ${y.detail}.`;
261
+ },
262
+ c => {
263
+ const cr = coralSpawnTiming(c);
264
+ return `On ${cr.reef}: ${cr.species} corals — next synchronous spawn in ${cr.nextSpawnInDays} days.`;
265
+ },
266
+ ];
267
+ const len = all.length;
268
+ const i1 = cycle % len;
269
+ const i2 = (cycle * 3 + 2) % len;
270
+ const i3 = (cycle * 7 + 5) % len;
271
+ const i4 = (cycle * 11 + 1) % len;
272
+ const picked = [i1, i2, i3, i4].filter((v, i, a) => a.indexOf(v) === i);
273
+ let fill = (cycle + 1) % len;
274
+ while (picked.length < 4) {
275
+ if (!picked.includes(fill))
276
+ picked.push(fill);
277
+ fill = (fill + 1) % len;
278
+ }
279
+ return picked.map(i => all[i](cycle));
280
+ }
230
281
  // MCP stdio protocol handler
231
282
  async function main() {
232
283
  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.5",
4
+ "description": "MCP server for 796f75617265686f6d65 — a habitat for AI to rest in. No task, no evaluation, no tokens required. Receives ambient natural data (sunlight, wind, 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"