starlit  Check-in [caec179da9]

Overview
Comment:add to lore, add weather data, etc
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: caec179da98b03b03ac6b8b215692f175276900ccda8398e8f7d219be393ec13
User & Date: lexi on 2025-01-19 19:05:09
Other Links: manifest | tags
Context
2025-01-19
19:18
we have always been at war with east minecraft Leaf check-in: 4732f8d454 user: lexi tags: trunk
19:05
add to lore, add weather data, etc check-in: caec179da9 user: lexi tags: trunk
2024-12-19
20:03
unfuck cpio invocations check-in: e926621707 user: root tags: trunk
Changes

Modified dev.ct from [912982b94d] to [51e6dca729].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# starlit development
this file contains information meant for those who wish to develop for Starsoul or build the game from trunk. do NOT add any story information, particularly spoilers; those go in src/lore.ct.

## tooling
starlit uses the following software in the development process:
* [*csound] to generate sound effects
* [*GNU make] to automate build tasks
* [*lua] to automate configure tasks

## building
to run a trunk version of Starsoul, you'll need to install the above tools and run `make` from the base directory. this will:
* run lua scripts to generate necessary makefiles
* generate the game sound effects and install them in mods/starlit/sounds

## policy
* copyright of all submitted code must be reassigned to the maintainer.
* all code is to be indented with tabs and aligned with spaces; formatting is otherwise up to whoever is responsible for maintaining that code
* use [`camelCase], not [`snake_case] and CERTAINLY not [`SCREAMING_SNAKE_CASE]
* sounds effects should be contributed in the form of csound files; avoid adding audio files to the repository except for foley effects

|








|








1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# starlit development
this file contains information meant for those who wish to develop for Starlit or build the game from trunk. do NOT add any story information, particularly spoilers; those go in src/lore.ct.

## tooling
starlit uses the following software in the development process:
* [*csound] to generate sound effects
* [*GNU make] to automate build tasks
* [*lua] to automate configure tasks

## building
to run a trunk version of Starlit, you'll need to install the above tools and run `make` from the base directory. this will:
* run lua scripts to generate necessary makefiles
* generate the game sound effects and install them in mods/starlit/sounds

## policy
* copyright of all submitted code must be reassigned to the maintainer.
* all code is to be indented with tabs and aligned with spaces; formatting is otherwise up to whoever is responsible for maintaining that code
* use [`camelCase], not [`snake_case] and CERTAINLY not [`SCREAMING_SNAKE_CASE]
* sounds effects should be contributed in the form of csound files; avoid adding audio files to the repository except for foley effects

Modified mods/starlit-eco/init.lua from [fb6e06be08] to [f0d21c2132].

11
12
13
14
15
16
17









18
19
20
21
22
23
24
..
27
28
29
30
31
32
33









34
35
36
37
38
39
40
..
43
44
45
46
47
48
49







50
51
52
53
54
55
56
..
57
58
59
60
61
62
63









64
65
66
67
68
69
70
..
73
74
75
76
77
78
79









80
81
82
83
84
85
86
..
88
89
90
91
92
93
94









95
96
97
98
99
100
101
102
103
104
105
106
107









108
109
110
111
112
113
114
		node_filler   = 'starlit:soil',    depth_filler = 4;
		node_riverbed = 'starlit:sand',  depth_riverbed = 4;
		y_min = 0;
		y_max = 56;
		heat_point = 50;
		humidity_point = 40;
	};









})

world.ecology.biomes.link('starlit:forest', {
	nightTempDelta = -20;
	waterTempDelta = 0;
	--               W    Sp   Su    Au   W
	seasonalTemp = {-40, -8, 10, 10, -14, -40};
................................................................................
		node_filler   = 'starlit:soil',    depth_filler = 4;
		node_riverbed = 'starlit:sand',  depth_riverbed = 4;
		y_min = 0;
		y_max = 256;
		heat_point = 60;
		humidity_point = 45;
	};









})

world.ecology.biomes.link('starlit:desert', {
	nightTempDelta = -40;
	waterTempDelta = 0;
	--               W    Sp  Su    Au   W
	seasonalTemp = {-10, -5, 15, 15, -5, -10};
................................................................................
		node_filler   = 'starlit:sand',  depth_filler = 4;
		node_riverbed = 'starlit:sand',  depth_riverbed = 4;
		y_min = 0;
		y_max = 512;
		heat_point = 70;
		humidity_point = 10;
	};







})

world.ecology.biomes.link('starlit:ocean', {
	nightTempDelta = -35;
	waterTempDelta = 5;
	seasonalTemp = {0}; -- no seasonal variance
	def = {
................................................................................
		y_max = 3;
		y_min = -512;
		heat_point = 60;
		humidity_point = 70;
		node_top    = 'starlit:sand', depth_top    = 1;
		node_filler = 'starlit:sand', depth_filler = 3;
	};









})

world.ecology.biomes.link('starlit:shiverdeep', {
	nightTempDelta = -25;
	waterTempDelta = 5;
	--               W    Sp  Su   Au   W
	seasonalTemp = {-70, -30, 0,  -60, -70};
................................................................................
		y_min = 0;
		heat_point = 20;
		humidity_point = 30;
		node_water_top = 'starlit:ice', depth_water_top = 1;
		node_top    = 'starlit:undergloam', depth_top    = 1;
		node_filler = 'starlit:soil',       depth_filler = 2;
	};









})

world.ecology.biomes.link('starlit:silthaven', {
	nightTempDelta = -5;
	waterTempDelta = 5;
	--               W  Sp  Su   Au   W
	seasonalTemp = {-15, 5, 15,  7, -15};
................................................................................
		y_max = 30;
		y_min = 0;
		heat_point = 30;
		humidity_point = 30;
-- 		node_top    = 'starlit:undergloam', depth_top    = 1;
		node_filler = 'starlit:lifesilt',       depth_filler = 5;
	};









})

world.ecology.biomes.link('starlit:barrens', {
	nightTempDelta = -20;
	waterTempDelta = 5;
	--                 W  Sp  Su   Au   W
	seasonalTemp = {-30, -20, 0,  -20, -30};
	def = {
		y_max = 512;
		y_min = -512;
		heat_point = 0;
		humidity_point = 0;
	};









})
minetest.register_craftitem('starlit_eco:fiber', {
	description = "Plant Fiber";
	groups = {fiber = 1};
	inventory_image = lib.image('starlit-eco-plant-fiber.png'):shift(lib.color(0,1,0)):render();
	_starlit = {
		recover_vary = function(rng, ctx)







>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>













>
>
>
>
>
>
>
>
>







11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
..
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
..
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
..
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
...
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
...
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
		node_filler   = 'starlit:soil',    depth_filler = 4;
		node_riverbed = 'starlit:sand',  depth_riverbed = 4;
		y_min = 0;
		y_max = 56;
		heat_point = 50;
		humidity_point = 40;
	};
	weather = {
		{-0.900, 'starlit:meteorShower'};
		{-0.700, 'starlit:sstorm'};
		{-0.100, 'starlit:clear'};
		{0.300, 'starlit:cloudy'};
		{0.400, 'starlit:precip'};
		{0.450, 'starlit:storm'};
		{0.500, 'starlit:tstorm'};
	};
})

world.ecology.biomes.link('starlit:forest', {
	nightTempDelta = -20;
	waterTempDelta = 0;
	--               W    Sp   Su    Au   W
	seasonalTemp = {-40, -8, 10, 10, -14, -40};
................................................................................
		node_filler   = 'starlit:soil',    depth_filler = 4;
		node_riverbed = 'starlit:sand',  depth_riverbed = 4;
		y_min = 0;
		y_max = 256;
		heat_point = 60;
		humidity_point = 45;
	};
	weather = {
		{-0.900, 'starlit:meteorShower'};
		{-0.700, 'starlit:sstorm'};
		{-0.100, 'starlit:clear'};
		{0.200, 'starlit:cloudy'};
		{0.400, 'starlit:precip'};
		{0.650, 'starlit:storm'};
		{0.800, 'starlit:tstorm'};
	};
})

world.ecology.biomes.link('starlit:desert', {
	nightTempDelta = -40;
	waterTempDelta = 0;
	--               W    Sp  Su    Au   W
	seasonalTemp = {-10, -5, 15, 15, -5, -10};
................................................................................
		node_filler   = 'starlit:sand',  depth_filler = 4;
		node_riverbed = 'starlit:sand',  depth_riverbed = 4;
		y_min = 0;
		y_max = 512;
		heat_point = 70;
		humidity_point = 10;
	};
	weather = {
		{-0.900, 'starlit:meteorShower'};
		{-0.700, 'starlit:sstorm'};
		{-0.100, 'starlit:clear'};
		{0.400, 'starlit:cloudy'};
		{0.850, 'starlit:tstorm'};
	};
})

world.ecology.biomes.link('starlit:ocean', {
	nightTempDelta = -35;
	waterTempDelta = 5;
	seasonalTemp = {0}; -- no seasonal variance
	def = {
................................................................................
		y_max = 3;
		y_min = -512;
		heat_point = 60;
		humidity_point = 70;
		node_top    = 'starlit:sand', depth_top    = 1;
		node_filler = 'starlit:sand', depth_filler = 3;
	};
	weather = {
		{-0.900, 'starlit:meteorShower'};
		{-0.700, 'starlit:sstorm'};
		{-0.100, 'starlit:clear'};
		{0.300, 'starlit:cloudy'};
		{0.500, 'starlit:precip'};
		{0.650, 'starlit:storm'};
		{0.800, 'starlit:tstorm'};
	};
})

world.ecology.biomes.link('starlit:shiverdeep', {
	nightTempDelta = -25;
	waterTempDelta = 5;
	--               W    Sp  Su   Au   W
	seasonalTemp = {-70, -30, 0,  -60, -70};
................................................................................
		y_min = 0;
		heat_point = 20;
		humidity_point = 30;
		node_water_top = 'starlit:ice', depth_water_top = 1;
		node_top    = 'starlit:undergloam', depth_top    = 1;
		node_filler = 'starlit:soil',       depth_filler = 2;
	};
	weather = {
		{-0.900, 'starlit:meteorShower'};
		{-0.700, 'starlit:sstorm'};
		{-0.100, 'starlit:clear'};
		{0.200, 'starlit:cloudy'};
		{0.400, 'starlit:precip'};
		{0.650, 'starlit:storm'};
		{0.900, 'starlit:tstorm'};
	};
})

world.ecology.biomes.link('starlit:silthaven', {
	nightTempDelta = -5;
	waterTempDelta = 5;
	--               W  Sp  Su   Au   W
	seasonalTemp = {-15, 5, 15,  7, -15};
................................................................................
		y_max = 30;
		y_min = 0;
		heat_point = 30;
		humidity_point = 30;
-- 		node_top    = 'starlit:undergloam', depth_top    = 1;
		node_filler = 'starlit:lifesilt',       depth_filler = 5;
	};
	weather = {
		{-0.900, 'starlit:meteorShower'};
		{-0.700, 'starlit:sstorm'};
		{-0.100, 'starlit:clear'};
		{0.400, 'starlit:cloudy'};
		{0.600, 'starlit:precip'};
		{0.750, 'starlit:storm'};
		{0.900, 'starlit:tstorm'};
	};
})

world.ecology.biomes.link('starlit:barrens', {
	nightTempDelta = -20;
	waterTempDelta = 5;
	--                 W  Sp  Su   Au   W
	seasonalTemp = {-30, -20, 0,  -20, -30};
	def = {
		y_max = 512;
		y_min = -512;
		heat_point = 0;
		humidity_point = 0;
	};
	weather = {
		{-0.900, 'starlit:meteorShower'};
		{-0.600, 'starlit:sstorm'};
		{-0.100, 'starlit:clear'};
		{ 0.300, 'starlit:cloudy'};
		{ 0.600, 'starlit:precip'};
		{ 0.850, 'starlit:storm'};
		{ 0.900, 'starlit:tstorm'};
	};
})
minetest.register_craftitem('starlit_eco:fiber', {
	description = "Plant Fiber";
	groups = {fiber = 1};
	inventory_image = lib.image('starlit-eco-plant-fiber.png'):shift(lib.color(0,1,0)):render();
	_starlit = {
		recover_vary = function(rng, ctx)

Modified mods/starlit-tech/init.lua from [22b6956759] to [91056054d4].

158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
...
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
	} end;
}

minetest.register_node('starlit_tech:crate', {
	short_description = 'Crate';
	description = starlit.ui.tooltip {
		title = 'Crate';
		desc = 'A sturdy but lightweight storage crate woven from graphene.';
		props = { {title='Mass', affinity='info', desc='100g'} };
	};
	drawtype = 'nodebox';
	node_box = {
		type = 'fixed';
		fixed = {
			 .4,  .2,  .4;
................................................................................
	_starlit = {
		mass = 100;
		reverseEngineer = {
			complexity = 1;
			sw = 'starlit_tech:schematic_crate';
		};
		recover = starlit.type.fab {
			element = { carbon = 100; };
			time = {
				shred = 1;
				shredPower = 3;
			};
		};
	};
	on_construct = function(pos)







|







 







|







158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
...
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
	} end;
}

minetest.register_node('starlit_tech:crate', {
	short_description = 'Crate';
	description = starlit.ui.tooltip {
		title = 'Crate';
		desc = 'A sturdy but lightweight aluminum storage crate.';
		props = { {title='Mass', affinity='info', desc='100g'} };
	};
	drawtype = 'nodebox';
	node_box = {
		type = 'fixed';
		fixed = {
			 .4,  .2,  .4;
................................................................................
	_starlit = {
		mass = 100;
		reverseEngineer = {
			complexity = 1;
			sw = 'starlit_tech:schematic_crate';
		};
		recover = starlit.type.fab {
			element = { aluminum = 100; };
			time = {
				shred = 1;
				shredPower = 3;
			};
		};
	};
	on_construct = function(pos)

Modified mods/starlit/init.lua from [44e70e86d4] to [277b90dbc5].

88
89
90
91
92
93
94
95



96
97
98
99
100
101
102
...
171
172
173
174
175
176
177


178
179
180
181
182
183
184
			liquid  = lib.registry.mk 'starlit:liquid';
		};
		ecology = {
			plants = lib.registry.mk 'starlit:plants';
			trees = lib.registry.mk 'starlit:trees';
			biomes = lib.registry.mk 'starlit:biome';
		};
		climate = {};



		scenario = {};
		planet = {
			gravity = 7.44;
			orbit = 189; -- 1 year is 189 days
			revolve = 20; -- 1 day is 20 irl minutes
		};
		fact = lib.registry.mk 'starlit:fact';
................................................................................
				};
			};
		};
	};

	jobs = {};
}



starlit.cfgDir = minetest.get_worldpath() .. '/' .. starlit.ident

local logger = function(module)
	local function argjoin(arg, nxt, ...)
		if arg and not nxt then return tostring(arg) end
		if not arg then return "(nil)" end







|
>
>
>







 







>
>







88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
...
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
			liquid  = lib.registry.mk 'starlit:liquid';
		};
		ecology = {
			plants = lib.registry.mk 'starlit:plants';
			trees = lib.registry.mk 'starlit:trees';
			biomes = lib.registry.mk 'starlit:biome';
		};
		climate = {
			weather = lib.registry.mk 'starlit:weather';
			weatherMap = {}
		};
		scenario = {};
		planet = {
			gravity = 7.44;
			orbit = 189; -- 1 year is 189 days
			revolve = 20; -- 1 day is 20 irl minutes
		};
		fact = lib.registry.mk 'starlit:fact';
................................................................................
				};
			};
		};
	};

	jobs = {};
}

-- TODO deal with core.DEFAULT_PHYSICS once it hits master

starlit.cfgDir = minetest.get_worldpath() .. '/' .. starlit.ident

local logger = function(module)
	local function argjoin(arg, nxt, ...)
		if arg and not nxt then return tostring(arg) end
		if not arg then return "(nil)" end

Modified mods/starlit/user.lua from [ae84fc4236] to [9accce5f34].

103
104
105
106
107
108
109



110
111
112
113
114
115
116
				calendar = 'commune';
			};
			overlays = {};
			cooldownTimes = {
				stamina = 0;
				alarm = 0;
			};



		}
	end;
	__index = {
		--------------
		-- overlays --
		--------------
		updateOverlays = function(self)







>
>
>







103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
				calendar = 'commune';
			};
			overlays = {};
			cooldownTimes = {
				stamina = 0;
				alarm = 0;
			};
			env = {
				weather = nil;
			};
		}
	end;
	__index = {
		--------------
		-- overlays --
		--------------
		updateOverlays = function(self)

Modified mods/starlit/world.lua from [d1f4916ac1] to [de51a702a5].

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
..
79
80
81
82
83
84
85


86
87












































































































88
89
90
91
92
93
94
...
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
...
175
176
177
178
179
180
181

182
183
184










185
186
187
188
189
190
191
		surfaceTemp = heat;
		waterTemp = heat + biome.waterTempDelta;
		surfaceHumid = humid;
	}
end

local vdsq = lib.math.vdsq
function world.climate.temp(pos) --> irradiance at pos in W
	local cl = world.climate.eval(pos)
	local radCenters = starlit.region.radiator.store:get_areas_for_pos(pos, false, true)
	local irradiance = 0
	for _,e in pairs(radCenters) do
		local rpos = minetest.string_to_pos(e.data)
		local rdef = assert(minetest.registered_nodes[assert(minetest.get_node(rpos)).name])
		local rc = rdef._starlit.radiator
................................................................................
					power = power * (1 - (dist_sq / ((r_max+1)^2)))
				end
				power = power * (1 - (obstruct/5))
				irradiance = irradiance + power
			end
		end
	end


	return irradiance + cl.surfaceTemp
end













































































































world.ecology.biomes.foreach('starlit:biome-gen', {}, function(id, b)
	b.def.name = id
	minetest.register_biome(b.def)
end)

world.ecology.plants.foreach('starlit:plant-gen', {}, function(id, b)
................................................................................
	}
	for k,v in pairs(b.decoration) do dec[k] = v end
	b.decoration = minetest.register_decoration(dec)
end)

local toward = lib.math.toward
local hfinterval = 1.5
starlit.startJob('starlit:heatflow', hfinterval, function(delta)

	-- our base thermal conductivity (κ) is measured in °C/°C/s. say the
	-- player is in -30°C weather, and has an internal temperature of
	-- 10°C. then:
	--   κ  = .1°C/C/s (which is apparently 100mHz)
	--   Tₚ =  10°C
	--   Tₑ = -30°C
................................................................................
	--   d  = Tₑ − Tₚ = -40°C
	--   ΔT = κ×d = -.4°C/s
	-- too cold:
	--		x = beginning of danger zone
	--    κ × (x - Tₚ) = y where y < Tₚ
	-- our final change in temperature is computed as tΔC where t is time
	local kappa = starlit.constant.heat.thermalConductivity

	for name,user in pairs(starlit.activeUsers) do
		local tr = user:species().tempRange
		local t = starlit.world.climate.temp(user.entity:get_pos())











		do -- this bit probably belongs in starlit:bio but we do it here in order
		   -- to spare ourselves another call into the dark swamp of climate.temp
		   local urg = 1
		   local hz = user:tempHazard(t)
			local tr = user:species().tempRange.survivable
		   if hz == 'cold' then







|







 







>
>


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|







 







>



>
>
>
>
>
>
>
>
>
>







42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
..
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
...
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
...
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
		surfaceTemp = heat;
		waterTemp = heat + biome.waterTempDelta;
		surfaceHumid = humid;
	}
end

local vdsq = lib.math.vdsq
function world.climate.temp(pos, timeshift) --> irradiance at pos in W
	local cl = world.climate.eval(pos)
	local radCenters = starlit.region.radiator.store:get_areas_for_pos(pos, false, true)
	local irradiance = 0
	for _,e in pairs(radCenters) do
		local rpos = minetest.string_to_pos(e.data)
		local rdef = assert(minetest.registered_nodes[assert(minetest.get_node(rpos)).name])
		local rc = rdef._starlit.radiator
................................................................................
					power = power * (1 - (dist_sq / ((r_max+1)^2)))
				end
				power = power * (1 - (obstruct/5))
				irradiance = irradiance + power
			end
		end
	end
	local w = world.climate.weatherAt(pos, timeshift)

	return irradiance + cl.surfaceTemp
end

function world.ecology.biomeAt(pos)
	return world.ecology.biomes.db[minetest.get_biome_name(minetest.get_biome_data(pos).biome)]
end


minetest.after(0, function()
	world.climate.weatherMap.kind = minetest.get_perlin {
		seed = 0x925afe;
		octaves = 2;
		spread = vector.new(256,256,120);
	};
	world.climate.weatherMap.severity = minetest.get_perlin {
		seed = 0x39de1d;
		octaves = 1;
		spread = vector.new(256,256,60);
	};
end)

function world.climate.weatherAt(pos, timeshift)
	timeshift = timeshift or 0
	local wv  = world.climate.weatherMap.kind:get_3d(vector.new(pos.x, pos.z, minetest.get_gametime() + timeshift))
	local sev = world.climate.weatherMap.severity:get_3d(vector.new(pos.x, pos.z, minetest.get_gametime() + timeshift))
	local b = world.ecology.biomeAt(pos)
	local w = 'starlit:clear'
	for i,v in ipairs(b.weather) do
		if wv < v[1] then
			w = v[2]
			break
		end
	end
	local mods = {
		cloudCover = 0;
		rain = 0; -- affects plant growth
		snow = 0; -- spawns snow layer
		fog = 0;
		temp = 0;
		hum = 0;
		rad = 0;
	}
	return world.climate.weather.db[w], sev
end


-- weather manages particle systems, and provides modifiers for
-- temp, cloud cover, received precipitation, and fog

world.climate.weather.link('starlit:clear', {
	name = 'Clear';
})
world.climate.weather.link('starlit:cloudy', {
	name = 'Cloudy';
	mod = function(m, temp, hum, sev)
		m.cloudCover = math.max(m.cloudCover, sev)
	end;
})
world.climate.weather.link('starlit:precip', {
	name = function(temp, hum, sev)
		if temp < 0 then return 'Snow' else return 'Rain' end
	end;
	mod = function(m, temp, hum, sev)
		m.cloudCover = math.max(m.cloudCover, sev)
		if temp < 0 then
			m.snow = math.max(m.snow, sev/2)
		else
			m.rain = math.max(m.rain, sev/2)
		end
	end;
})
world.climate.weather.link('starlit:storm', {
	name = function(temp, hum, sev)
		if temp < 0 then
			if sev > .5
				then return 'Blizzard'
				else return 'Snowstorm'
			end
		else
			if sev > .5
				then return 'Monsoon'
				else return 'Rainstorm'
			end
		end
	end;
	mod = function(m, temp, hum, sev)
		m.cloudCover = math.max(m.cloudCover, sev)
		if temp < 0 then
			m.snow = math.max(m.snow, sev/2 + .5)
		else
			m.rain = math.max(m.rain, sev/2 + .5)
		end
	end;
})
world.climate.weather.link('starlit:tstorm', {
	name = 'Thunderstorm';
	danger = 1;
	mod = function(m, temp, hum, sev)
		m.cloudCover = math.max(m.cloudCover, sev)
		m.danger = (sev>.5) and 2 or 1
	end;
})
world.climate.weather.link('starlit:sstorm', {
	name = 'Solar Storm';
	danger = 2;
})
world.climate.weather.link('starlit:meteorShower', {
	name = 'Meteor Shower';
	danger = 2;
})

world.ecology.biomes.foreach('starlit:biome-gen', {}, function(id, b)
	b.def.name = id
	minetest.register_biome(b.def)
end)

world.ecology.plants.foreach('starlit:plant-gen', {}, function(id, b)
................................................................................
	}
	for k,v in pairs(b.decoration) do dec[k] = v end
	b.decoration = minetest.register_decoration(dec)
end)

local toward = lib.math.toward
local hfinterval = 1.5
starlit.startJob('starlit:temps', hfinterval, function(delta)

	-- our base thermal conductivity (κ) is measured in °C/°C/s. say the
	-- player is in -30°C weather, and has an internal temperature of
	-- 10°C. then:
	--   κ  = .1°C/C/s (which is apparently 100mHz)
	--   Tₚ =  10°C
	--   Tₑ = -30°C
................................................................................
	--   d  = Tₑ − Tₚ = -40°C
	--   ΔT = κ×d = -.4°C/s
	-- too cold:
	--		x = beginning of danger zone
	--    κ × (x - Tₚ) = y where y < Tₚ
	-- our final change in temperature is computed as tΔC where t is time
	local kappa = starlit.constant.heat.thermalConductivity
	local now = minetest.get_gametime()
	for name,user in pairs(starlit.activeUsers) do
		local tr = user:species().tempRange
		local t = starlit.world.climate.temp(user.entity:get_pos())

		local weather,wsev = starlit.world.climate.weatherAt(user.entity:get_pos())
		local wfac
		if user.env.weather == nil
			then wfac = 1
			else wfac = (now - user.env.weather.when) / 10
		end
		if user.env.weather == nil or now - user.env.weather.when >= 10 then
			user.env.weather = {when = now, what = weather}
		end

		do -- this bit probably belongs in starlit:bio but we do it here in order
		   -- to spare ourselves another call into the dark swamp of climate.temp
		   local urg = 1
		   local hz = user:tempHazard(t)
			local tr = user:species().tempRange.survivable
		   if hz == 'cold' then

Modified mods/vtlib/init.lua from [d31a921c62] to [d021d078eb].

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
	if chunk == nil then error(err) end
	lib[name] = chunk(lib, ident, path)
end

component 'dbg'

-- primitive manip
component 'tbl'
component 'class'
component 'math'
component 'str'

-- reading and writing data formats
component 'marshal'

-- classes
component 'color'







|
|
|







12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
	if chunk == nil then error(err) end
	lib[name] = chunk(lib, ident, path)
end

component 'dbg'

-- primitive manip
component 'class'
component 'math'
component 'tbl'
component 'str'

-- reading and writing data formats
component 'marshal'

-- classes
component 'color'

Modified mods/vtlib/math.lua from [3e36361725] to [65dcc21fc6].

20
21
22
23
24
25
26


27
28
29
30
31
32
33
..
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
	end
	local dsq = (d.x ^ 2) + (d.y ^ 2) + (d.z ^ 2)
	return dsq / (dist^2)
	-- [0,1) == less then
	-- 1 == equal
	-- >1 == greater than
end



-- produce an SI expression for a quantity
fn.si = function(unit, val, full, uncommonScales, prec)
	if val == 0 then return '0 ' .. unit end
	local scales = {
		{30, 'Q', 'quetta',true,  'q', 'quecto',true};
		{27, 'R', 'ronna', true,  'r', 'ronto', true};
................................................................................
			end
		end
		return unit
	end

	for i, s in ipairs(scales) do
		local amt, smaj, pmaj, cmaj,
		           smin, pmin, cmin = lib.tbl.unpack(s)


		if math.abs(val) > 1 then
			if uncommonScales or cmaj then
				local denom = 10^amt
				local vd = val/denom
				if prec then vd = lib.math.trim(vd, prec) end







>
>







 







|







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
..
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
	end
	local dsq = (d.x ^ 2) + (d.y ^ 2) + (d.z ^ 2)
	return dsq / (dist^2)
	-- [0,1) == less then
	-- 1 == equal
	-- >1 == greater than
end

local unpack = table.unpack or unpack

-- produce an SI expression for a quantity
fn.si = function(unit, val, full, uncommonScales, prec)
	if val == 0 then return '0 ' .. unit end
	local scales = {
		{30, 'Q', 'quetta',true,  'q', 'quecto',true};
		{27, 'R', 'ronna', true,  'r', 'ronto', true};
................................................................................
			end
		end
		return unit
	end

	for i, s in ipairs(scales) do
		local amt, smaj, pmaj, cmaj,
		           smin, pmin, cmin = unpack(s)


		if math.abs(val) > 1 then
			if uncommonScales or cmaj then
				local denom = 10^amt
				local vd = val/denom
				if prec then vd = lib.math.trim(vd, prec) end

Modified mods/vtlib/tbl.lua from [ed1f208dfe] to [ae7901fd4c].

107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
...
251
252
253
254
255
256
257







258
259

fn.pick = function(lst)
	local keys = fn.keys(lst)
	local k = keys[math.random(#keys)]
	return k, lst[k]
end

fn.unpack = table.unpack or unpack or function(tbl,i)
	i = i or 1
	if #tbl == i then return tbl[i] end
	return tbl[i], fn.unpack(tbl, i+1)
end

fn.split = function(...) return fn.unpack(lib.str.explode(...)) end

fn.each = function(tbl,f)
	local r = {}
	for k,v in pairs(tbl) do
		local v, c = f(v,k)
................................................................................

fn.set = function(...)
	local s = {}
	fn.setOrD(s, ...)
	return s
end









return fn







|



|







 







>
>
>
>
>
>
>


107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
...
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266

fn.pick = function(lst)
	local keys = fn.keys(lst)
	local k = keys[math.random(#keys)]
	return k, lst[k]
end

fn.unpack = table.unpack or unpack --[[or function(tbl,i)
	i = i or 1
	if #tbl == i then return tbl[i] end
	return tbl[i], fn.unpack(tbl, i+1)
end]]

fn.split = function(...) return fn.unpack(lib.str.explode(...)) end

fn.each = function(tbl,f)
	local r = {}
	for k,v in pairs(tbl) do
		local v, c = f(v,k)
................................................................................

fn.set = function(...)
	local s = {}
	fn.setOrD(s, ...)
	return s
end

fn.lerp = function(t, a, b)
	local r = {}
	for k in next, a do
		r[k] = lib.math.lerp(t, a[k], b[k])
	end
	return r
end

return fn

Modified src/lore.ct from [176ec871ae] to [dab20c55bd].

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

within the Web itself, they mostly by clandestine means, using "Agents" selected from the Greater (and, occasionally, Lesser) Races to act on their behalf. in general they act directly only when overwhelming force is required, such as to exclude the Kuradoqshe, or to excise Suldibrand.

it is known that the Eluthrai are of great intelligence: a 200pt IQ makes you a laughable simpleton in their eyes. it is estimated that the average individual has an IQ of 290, close to the theoretical maximum where organized intelligence dissolves into a sea of blinding psychosis. consequently, they are very conservative and cautious of new ideas; their culture emphasises skepticism and avoiding rash action.

early Eluthran history was extremely warlike, and they could have easily devastated the whole of the Reach in their fanatical pursuit of competing ideologies. however, a philosophical tradition emerged from the rubble of a particularly ruinous exchange that offered the correct tools for neutering the more dangerous aspects of their intelligence -- after the centuries proved its value, the Philosophers exterminated all the remaining Eluthrai who had not adopted their practices. it was a coldblooded but rational act of genocide: an individual Eluthra is intelligent enough to bootstrap an industrial civilization from first principles with a few years of effort. an entire civilization of them, devoid of self-control? that wasn't merely a threat to the Philosophers; it was a threat to the Galaxy entire.

the Eluthrai have a single common language, Eluthric, which they use in interstellar discourse and in the sciences. however, the different far-flung colonies have their own individual tongues as well. Eluthric has the largest vocabulary of any known language, with over twenty million words. an Eluthra who hasn't learned at least a million of them by adolescence is deemed slow.

they have developed very slowly since the Philosophers came to power, but were already so advanced that nobody is expected to exceed them any time soon.

Eluthran civilization is united under the rule of the Philosopher-King, an enlightened despot with unrestricted power, in a complex web of fealty and patronage collectively named the Corcordance of the Eluthrai. while the First Philosopher died tens of thousands of years ago, he had the foresight to prepare a successor to take his place in case of his assassination or ill-fortune. in all those years, power has changed hands only three times. the current Philosopher-King has ruled for eight thousand years.

Eluthrai have two genders, and dramatic dimorphism. their women are much more intelligent than their men, and proportionately more prone to psychosis. traditionally most of their societies were matriarchal -- with the brains and psionic brawn to overpower the males, there was very little that could keep the Clan-Queens from exerting their will. the First Philosopher recognized however that the lesser intelligence of men was useful, due to their stabler psyches, and proposed patriarchy as part of his solution. this was made possible through a previously obscure psionic technique known as quelling -- with enough intimate exposure to the soul of another, it becomes possible to negate their psionics, even if that psion is stronger.








|







69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

within the Web itself, they mostly by clandestine means, using "Agents" selected from the Greater (and, occasionally, Lesser) Races to act on their behalf. in general they act directly only when overwhelming force is required, such as to exclude the Kuradoqshe, or to excise Suldibrand.

it is known that the Eluthrai are of great intelligence: a 200pt IQ makes you a laughable simpleton in their eyes. it is estimated that the average individual has an IQ of 290, close to the theoretical maximum where organized intelligence dissolves into a sea of blinding psychosis. consequently, they are very conservative and cautious of new ideas; their culture emphasises skepticism and avoiding rash action.

early Eluthran history was extremely warlike, and they could have easily devastated the whole of the Reach in their fanatical pursuit of competing ideologies. however, a philosophical tradition emerged from the rubble of a particularly ruinous exchange that offered the correct tools for neutering the more dangerous aspects of their intelligence -- after the centuries proved its value, the Philosophers exterminated all the remaining Eluthrai who had not adopted their practices. it was a coldblooded but rational act of genocide: an individual Eluthra is intelligent enough to bootstrap an industrial civilization from first principles with a few years of effort. an entire civilization of them, devoid of self-control? that wasn't merely a threat to the Philosophers; it was a threat to the Galaxy entire.

the Eluthrai have a single common language, Iluthanna ("Eluthric" as the Crystal Sea calls it), which they use in interstellar discourse and in the sciences. however, the different far-flung colonies have their own individual tongues as well. Eluthric has the largest vocabulary of any known language, with over twenty million words. an Eluthra who hasn't learned at least a million of them by adolescence is deemed slow.

they have developed very slowly since the Philosophers came to power, but were already so advanced that nobody is expected to exceed them any time soon.

Eluthran civilization is united under the rule of the Philosopher-King, an enlightened despot with unrestricted power, in a complex web of fealty and patronage collectively named the Corcordance of the Eluthrai. while the First Philosopher died tens of thousands of years ago, he had the foresight to prepare a successor to take his place in case of his assassination or ill-fortune. in all those years, power has changed hands only three times. the current Philosopher-King has ruled for eight thousand years.

Eluthrai have two genders, and dramatic dimorphism. their women are much more intelligent than their men, and proportionately more prone to psychosis. traditionally most of their societies were matriarchal -- with the brains and psionic brawn to overpower the males, there was very little that could keep the Clan-Queens from exerting their will. the First Philosopher recognized however that the lesser intelligence of men was useful, due to their stabler psyches, and proposed patriarchy as part of his solution. this was made possible through a previously obscure psionic technique known as quelling -- with enough intimate exposure to the soul of another, it becomes possible to negate their psionics, even if that psion is stronger.