starlit  Check-in [511814aace]

Overview
Comment:add primitive thermal hazard LEDs; further documentation
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 511814aace3b6e9dd57ec362b2f17c7092904b5e739530038bc6261790f214a9
User & Date: lexi on 2024-05-03 21:00:04
Other Links: manifest | tags
Context
2024-05-04
02:18
fix colors, add grass check-in: 9d4ddb7701 user: lexi tags: trunk
2024-05-03
21:00
add primitive thermal hazard LEDs; further documentation check-in: 511814aace user: lexi tags: trunk
15:31
genericize impacts check-in: 467a75e0dc user: lexi tags: trunk
Changes

Modified mods/starlit/init.lua from [435c654ad9] to [d6e7e6c59c].

28
29
30
31
32
33
34

35
36
37
38
39
40
41
			brightest = 14; -- only sun and growlights
		};
		heat = { -- celsius
			freezing = 0;
			safe = 4;
			overheat = 32;
			boiling = 100;

		};
		rad = {
		};
	};

	activeUsers = {
		-- map of username -> user object







>







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
			brightest = 14; -- only sun and growlights
		};
		heat = { -- celsius
			freezing = 0;
			safe = 4;
			overheat = 32;
			boiling = 100;
			thermalConductivity = 0.05; -- κ
		};
		rad = {
		};
	};

	activeUsers = {
		-- map of username -> user object

Modified mods/starlit/user.lua from [788077fd8e] to [7074af2318].

37
38
39
40
41
42
43

44
45
46
47
48
49
50
..
59
60
61
62
63
64
65

66
67
68
69
70
71
72
...
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
...
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
...
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
...
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
...
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
...
499
500
501
502
503
504
505
























506
507
508
509
510
511
512
...
872
873
874
875
876
877
878





































879
880
881
882
883
884
885
...
933
934
935
936
937
938
939






940
941
942
943
944
945
946
		end
		return {
			entity = luser;
			name = name;
			hud = {
				elt = {};
				bar = {};

			};
			tree = {};
			action = {
				bits = 0; -- for control deltas
				prog = {}; -- for recording action progress on a node; reset on refocus
				tgt = {type='nothing'};
				sfx = {};
................................................................................
			};
			pref = {
				calendar = 'commune';
			};
			overlays = {};
			cooldownTimes = {
				stamina = 0;

			};
		}
	end;
	__index = {
		--------------
		-- overlays --
		--------------
................................................................................
		end;

		---------
		-- HUD --
		---------
		attachImage = function(self, def)
			local user = self.entity
			local img = {}
			img.id = user:hud_add {
				type = 'image';
				text = def.tex;
				scale = def.scale;
				alignment = def.align;
				position = def.pos;
				offset = def.ofs;
................................................................................
					end, def)
				end
			end
			return img
		end;
		attachMeter = function(self, def)
			local luser = self.entity
			local m = {}
			local w = def.size or 80
			local szf = w / 80
			local h = szf * 260
			m.meter = luser:hud_add {
				type = 'image';
				scale = {x = szf, y = szf};
				alignment = def.align;
................................................................................
					luser:hud_change(m.readout, 'number', txtcolor:hex())
				end
			end
			return m
		end;
		attachTextBox = function(self, def)
			local luser = self.entity
			local box = {}
			box.id = luser:hud_add {
				type = 'text';
				text = '';
				alignment = def.align;
				number = def.color and def.color:int24() or 0xFFffFF;
				scale = def.bound;
				size = {x = def.size, y=0};
................................................................................
					luser:hud_change(box.id, 'number', color:int24())
				end
			end
			return box
		end;
		attachStatBar = function(self, def)
			local luser = self.entity
			local bar = {}
			local img = lib.image 'starlit-ui-bar.png'
			local colorized = img
			if type(def.color) ~= 'function' then
				colorized = colorized:shift(def.color)
			end

			bar.id = luser:hud_add {
................................................................................
					set('text', hudAdjustBacklight(hudCenterBG):render())
				end;
			};
			self:updateHUD()
		end;
		deleteHUD = function(self)
			for name, e in pairs(self.hud.elt) do
				self:hud_delete(e.id)
			end
		end;
		updateHUD = function(self)
			for name, e in pairs(self.hud.elt) do
				if e.update then e.update() end
			end
		end;
................................................................................
		-- intel-gathering --
		---------------------
		clientInfo = function(self)
			return minetest.get_player_information(self.name)
		end;
		species = function(self)
			return starlit.world.species.index[self.persona.species]
























		end;

		--------------------
		-- event handlers --
		--------------------
		onSignup = function(self)
			local meta = self.entity:get_meta()
................................................................................
			end
			if run then
				run(self, ctx)
				return true
			end
			return false
		end;






































		-------------
		-- weather --
		-------------
		updateWeather = function(self)
		end;

................................................................................

local clockInterval = 1.0
starlit.startJob('starlit:clock', clockInterval, function(delta)
	for id, u in pairs(starlit.activeUsers) do
		u.hud.elt.time:update()
	end
end)







local biointerval = 1.0
starlit.startJob('starlit:bio', biointerval, function(delta)
	for id, u in pairs(starlit.activeUsers) do
		if u:effectiveStat 'health' ~= 0 then
			local bmr = u:phenoTrait 'metabolism' * biointerval
			-- TODO apply modifiers







>







 







>







 







|







 







|







 







|







 







|







 







|







 







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







 







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







 







>
>
>
>
>
>







37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
..
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
...
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
...
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
...
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
...
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
...
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
...
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
...
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
...
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
		end
		return {
			entity = luser;
			name = name;
			hud = {
				elt = {};
				bar = {};
				alarm = {};
			};
			tree = {};
			action = {
				bits = 0; -- for control deltas
				prog = {}; -- for recording action progress on a node; reset on refocus
				tgt = {type='nothing'};
				sfx = {};
................................................................................
			};
			pref = {
				calendar = 'commune';
			};
			overlays = {};
			cooldownTimes = {
				stamina = 0;
				alarm = 0;
			};
		}
	end;
	__index = {
		--------------
		-- overlays --
		--------------
................................................................................
		end;

		---------
		-- HUD --
		---------
		attachImage = function(self, def)
			local user = self.entity
			local img = {def = def}
			img.id = user:hud_add {
				type = 'image';
				text = def.tex;
				scale = def.scale;
				alignment = def.align;
				position = def.pos;
				offset = def.ofs;
................................................................................
					end, def)
				end
			end
			return img
		end;
		attachMeter = function(self, def)
			local luser = self.entity
			local m = {def = def}
			local w = def.size or 80
			local szf = w / 80
			local h = szf * 260
			m.meter = luser:hud_add {
				type = 'image';
				scale = {x = szf, y = szf};
				alignment = def.align;
................................................................................
					luser:hud_change(m.readout, 'number', txtcolor:hex())
				end
			end
			return m
		end;
		attachTextBox = function(self, def)
			local luser = self.entity
			local box = {def = def}
			box.id = luser:hud_add {
				type = 'text';
				text = '';
				alignment = def.align;
				number = def.color and def.color:int24() or 0xFFffFF;
				scale = def.bound;
				size = {x = def.size, y=0};
................................................................................
					luser:hud_change(box.id, 'number', color:int24())
				end
			end
			return box
		end;
		attachStatBar = function(self, def)
			local luser = self.entity
			local bar = {def = def}
			local img = lib.image 'starlit-ui-bar.png'
			local colorized = img
			if type(def.color) ~= 'function' then
				colorized = colorized:shift(def.color)
			end

			bar.id = luser:hud_add {
................................................................................
					set('text', hudAdjustBacklight(hudCenterBG):render())
				end;
			};
			self:updateHUD()
		end;
		deleteHUD = function(self)
			for name, e in pairs(self.hud.elt) do
				self.entity:hud_remove(e.id)
			end
		end;
		updateHUD = function(self)
			for name, e in pairs(self.hud.elt) do
				if e.update then e.update() end
			end
		end;
................................................................................
		-- intel-gathering --
		---------------------
		clientInfo = function(self)
			return minetest.get_player_information(self.name)
		end;
		species = function(self)
			return starlit.world.species.index[self.persona.species]
		end;
		-- can the suit heater sustain its current internal temperature in an area of t°C
		tempCanSustain = function(self, t)
			if self:naked() then return false end
			local s = self:getSuit()
			if s:powerState() == 'off' then return false end
			local sd = s:def()
			local w = self:effectiveStat 'warmth'
			local kappa = starlit.constant.heat.thermalConductivity
			local insul = sd.temp.insulation
			local dt = (kappa * (1-insul)) * (t - w)
			print('dt', dt, dump(sd.temp))
			if (dt > 0 and          dt  > sd.temp.maxCool)
			or (dt < 0 and math.abs(dt) > sd.temp.maxHeat) then return false end
			return true
		end;
		-- will exposure to temperature t cause the player eventual harm
		tempHazard = function(self, t)
			local tr = self:species().tempRange.survivable
			if t >= tr[1] and t <= tr[2] then return nil end
			if self:tempCanSustain(t)    then return nil end

			if t < tr[1] then return 'cold' end
			return 'hot'
		end;

		--------------------
		-- event handlers --
		--------------------
		onSignup = function(self)
			local meta = self.entity:get_meta()
................................................................................
			end
			if run then
				run(self, ctx)
				return true
			end
			return false
		end;

		alarm = function(self, urgency, kind, freq, where)
			freq = freq or 3
			local urgencies = {
				[1] = {sound = 'starlit-alarm'};
				[2] = {sound = 'starlit-alarm-urgent'};
			}
		   local gt = minetest.get_gametime()
		   local urg = urgencies[urgency] or urgencies[#urgencies]

		   if gt - self.cooldownTimes.alarm < freq then return end

		   self.cooldownTimes.alarm = gt
		   self:suitSound(urg.sound)

		   if where then
			   local elt = {
				   tex = where.tex or 'starlit-ui-alert.png';
				   scale = {x=1, y=1};
				   align = table.copy(where.elt.def.align);
				   pos = table.copy(where.elt.def.pos);
				   ofs = table.copy(where.elt.def.ofs);
			   }
			   elt.ofs.x = elt.ofs.x + where.ofs.x
			   elt.ofs.y = elt.ofs.y + where.ofs.y
			   local attached = self:attachImage(elt)
				table.insert(self.hud.alarm, attached)

			   -- HATE. HATE. HAAAAAAAAAAATE
			   minetest.after(freq/2, function()
				   for k,v in pairs(self.hud.alarm) do
					   self.entity:hud_remove(v.id)
				   end
				   self.hud.alarm={}
			   end)
		   end
	   end;

		-------------
		-- weather --
		-------------
		updateWeather = function(self)
		end;

................................................................................

local clockInterval = 1.0
starlit.startJob('starlit:clock', clockInterval, function(delta)
	for id, u in pairs(starlit.activeUsers) do
		u.hud.elt.time:update()
	end
end)

-- performs a general HUD refresh, mainly to update the HUD backlight brightness
local hudInterval = 10
starlit.startJob('starlit:hud-refresh', hudInterval, function(delta)
	for id, u in pairs(starlit.activeUsers) do u:updateHUD() end
end)

local biointerval = 1.0
starlit.startJob('starlit:bio', biointerval, function(delta)
	for id, u in pairs(starlit.activeUsers) do
		if u:effectiveStat 'health' ~= 0 then
			local bmr = u:phenoTrait 'metabolism' * biointerval
			-- TODO apply modifiers

Modified mods/starlit/world.lua from [d79dc9efc2] to [a2ac450e1c].

176
177
178
179
180
181
182



183
184
185
186
187





















188
189
190
191
192
193
194
...
223
224
225
226
227
228
229

230
231
232
233
234
235
236
	-- 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



	-- our final change in temperature is computed as tΔC where t is time
	local kappa = .05
	for name,user in pairs(starlit.activeUsers) do
		local tr = user:species().tempRange
		local t = starlit.world.climate.temp(user.entity:get_pos())





















		local insul = 0
		local naked = user:naked()
		local suitDef
		if not naked then
			suitDef = user:suitStack():get_definition()
			insul = suitDef._starlit.suit.temp.insulation
		end
................................................................................
			end
		end

		user:statDelta('warmth', tgt - warm) -- dopey but w/e

		warm = tgt -- for the sake of readable code


		if warm < tSafeMin or warm > tSafeMax then
			local dv
			if warm < tSafeMin then
				dv = math.abs(warm - tSafeMin)
			else
				dv = math.abs(warm - tSafeMax)
			end







>
>
>

|



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







 







>







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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
...
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
	-- 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 function alarm(kind)
			   user:alarm(urg, kind, nil, {
				   elt = user.hud.elt.temp, ofs = {x=100,y=0};
				   tex = 'starlit-ui-alert-'..kind..'.png';
			   })
		   end
		   local hz = user:tempHazard(t)
			local tr = user:species().tempRange.survivable
		   if hz == 'cold' then
			   if tr[1] - t > 7 then urg = 2 end
			   alarm 'temp-cold'
		   elseif hz == 'hot' then
			   if t - tr[2] > 7 then urg = 2 end
			   alarm 'temp-hot'
		   end
		end

		local insul = 0
		local naked = user:naked()
		local suitDef
		if not naked then
			suitDef = user:suitStack():get_definition()
			insul = suitDef._starlit.suit.temp.insulation
		end
................................................................................
			end
		end

		user:statDelta('warmth', tgt - warm) -- dopey but w/e

		warm = tgt -- for the sake of readable code

		-- does this belong in starlit:bio? unsure tbh
		if warm < tSafeMin or warm > tSafeMax then
			local dv
			if warm < tSafeMin then
				dv = math.abs(warm - tSafeMin)
			else
				dv = math.abs(warm - tSafeMax)
			end

Modified starlit.ct from [415d01ad10] to [8d6b5074b6].

1
2
3
4
5
6
7
8
9
10
11
12
..
38
39
40
41
42
43
44


45
46
47
48
49
50























51
52
53
54
55
56
57
..
67
68
69
70
71
72
73




































74
75
76
77
78
79
80
# starlit
[*starlit] is a sci-fi survival game. you play the survivor of a disaster in space, stranded on a just-barely-habitable world with nothing but your escape pod, your survival suit, and some handy new psionic powers that the accident seems to have unlocked in you. scavenge resources, search alien ruins, and build a base where you can survive indefinitely, but be careful: winter approaches, and your starting suit heater is already struggling to keep you alive in the comparatively warm days of "summer".

## story
### "Imperial Expat" background
about a month ago, you woke up to unexpected good news. your application to join the new Commune colony at Thousand Petal, submitted in a moment of utter existential weariness and almost in jest, was actually accepted. your skillset was a "perfect match" for the budding colony's needs, claimed the Population Control Authority, and you'd earned yourself a free trip to your new home -- on a swanky state transport, no less.

it took a few discreet threats and bribes from Commune diplomats, but after a week of wrangling with the surly Crown Service for Comings & Goings -- whose bureaucrats seemed outright [!offended] that you actually managed to find a way off that hateful rock -- you secured grudging clearance to depart. you celebrated by telling your slackjawed boss exactly what you thought of him in a meeting room packed with his fellow parasites -- and left House Taladran with a new appreciation for the value of your labor, as the nobs found themselves desperately scrabbling for a replacement on short notice.

you almost couldn't believe it when the Commune ship -- a sleek, solid piece of engineering whose graceful descent onto the landing pad seemed to sneer at the lurching rattletraps arrayed all around it -- actually showed up. in a daze you handed over your worldly possessions -- all three of them -- to a valet with impeccable manners, and climbed up out of the wagie nightmare into high orbit around your homeworld. the mercenary psion aboard, a preening Usukwinti with her very own luxury suite, tore a bleeding hole in the spacetime metric, and five hundred hopeful souls dove through towards fancied salvation. "sure," you thought to yourself as you slipped into your sleek new nanotech environment suit, itself worth more than the sum total of your earnings on Flame of Unyielding Purification, "life won't be easy -- but damn it, it'll [!mean] something out there."

a free life on the wild frontier with a nation of comrades to have your back, with the best tech humans can make, fresh, clean water that isn't laced with compliance serum, and -- best of all -- never having to worry about paying rent again. it was too good to be true, you mused.
................................................................................

	p11143: https://github.com/minetest/minetest/pull/11143.diff

### shadows
i was delighted to see dynamic shadows land in minetest, and i hope the implementation will eventually mature. however, as it stands, there are severe issues with shadows that make them essentially incompatible with complex meshes like the Starlit player character meshes. for the sake of those who don't mind these glitches, Starlit does enable shadows, but i unfortunately have to recommend that you disable them until the minetest devs get their act together on this feature.

## gameplay


the most important thing to understand about starlit is that is is [*mean], by design.

* chance plays an important role. your escape pod might land in the midst of a lush, temperate forest with plenty of nearby shipwrecks to scavenge. or it might land in the exact geographic center of a vast, harsh desert that your suit's cooling systems can't protect you from, ten klicks from anything of value. "unfair", you say? tough. Farthest Shadow doesn't care about your feelings.
* death is much worse than a slap on the wrist. when you die, you drop your possessions and your suit, and respawn naked at your spawn point. this is a serious danger, as you might be kilometers away from your spawn point -- and there's no guarantee someone else won't take your suit before you can find your way back to it. good luck crossing long distances without climate control! if you haven't carefully prepared for this eventuality by keeping a spare suit by your spawn point, death can be devastating, to the point of making the game unsurvivable without another player's help. 

starlit is somewhat unusual in how it uses the minetest engine. it's a voxel game but not of the minecraft variety.
























### controls
summon your Suit Interface by pressing the [*E] / [*Inventory] key. this will allow you to move items around in your inventory, but more importantly, it also allows you select or configure your Interaction Mode.

the top three buttons can be used to select (or deactivate) an Interaction Mode. an Interaction Mode can be configured by pressing the button immediately below. the active Interaction Mode controls the behavior of the mouse buttons when no item is selected in the hotbar.

the modes are:
................................................................................

to use a tool, select it in the hotbar. even if an Interaction Mode is active, the tool will take priority. press [*Left Button] / [*Punch] to use the tool on a block; for instance, to break a stone with a jackhammer. to configure a tool or use its secondary functions, if any, press [*Right Button] / [*Place].

hold [*Aux1] to activate your selected Maneuver. by default this is Sprint, which will consume stamina to allow you to run much faster. certain suits offer the Flight ability, which allows slow, mid-range flight. you can also unlock the psionic ability Lift, which allows very rapid flight but consumes psi at a prodigious rate.

you can only have one Maneuver active at a time, whether this is a Psi Maneuver (consuming psi), a Suit Maneuver (consuming battery), or a Body Maneuver (consuming stamina). Maneuvers are activated in their respective panel.





































### psionics
there are four types of psionic abilities: Manual, Maneuver, Ritual, and Contextual.

you can assign two Manual abilities at any given time and access them with the mouse buttons in Psionics mode.

you can select a Psi Maneuver in the Psionics panel and activate it by holding [*Aux1].




|
<







 







>
>





|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







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







1
2
3
4

5
6
7
8
9
10
11
..
37
38
39
40
41
42
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
71
72
73
74
75
76
77
78
79
80
81
..
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
# starlit
[*starlit] is a sci-fi survival game. you play the survivor of a disaster in space, stranded on a just-barely-habitable world with nothing but your escape pod, your survival suit, and some handy new psionic powers that the accident seems to have unlocked in you. scavenge resources, search alien ruins, and build a base where you can survive indefinitely, but be careful: winter approaches, and your starting suit heater is already struggling to keep you alive in the comparatively warm days of "summer".

##story story

about a month ago, you woke up to unexpected good news. your application to join the new Commune colony at Thousand Petal, submitted in a moment of utter existential weariness and almost in jest, was actually accepted. your skillset was a "perfect match" for the budding colony's needs, claimed the Population Control Authority, and you'd earned yourself a free trip to your new home -- on a swanky state transport, no less.

it took a few discreet threats and bribes from Commune diplomats, but after a week of wrangling with the surly Crown Service for Comings & Goings -- whose bureaucrats seemed outright [!offended] that you actually managed to find a way off that hateful rock -- you secured grudging clearance to depart. you celebrated by telling your slackjawed boss exactly what you thought of him in a meeting room packed with his fellow parasites -- and left House Taladran with a new appreciation for the value of your labor, as the nobs found themselves desperately scrabbling for a replacement on short notice.

you almost couldn't believe it when the Commune ship -- a sleek, solid piece of engineering whose graceful descent onto the landing pad seemed to sneer at the lurching rattletraps arrayed all around it -- actually showed up. in a daze you handed over your worldly possessions -- all three of them -- to a valet with impeccable manners, and climbed up out of the wagie nightmare into high orbit around your homeworld. the mercenary psion aboard, a preening Usukwinti with her very own luxury suite, tore a bleeding hole in the spacetime metric, and five hundred hopeful souls dove through towards fancied salvation. "sure," you thought to yourself as you slipped into your sleek new nanotech environment suit, itself worth more than the sum total of your earnings on Flame of Unyielding Purification, "life won't be easy -- but damn it, it'll [!mean] something out there."

a free life on the wild frontier with a nation of comrades to have your back, with the best tech humans can make, fresh, clean water that isn't laced with compliance serum, and -- best of all -- never having to worry about paying rent again. it was too good to be true, you mused.
................................................................................

	p11143: https://github.com/minetest/minetest/pull/11143.diff

### shadows
i was delighted to see dynamic shadows land in minetest, and i hope the implementation will eventually mature. however, as it stands, there are severe issues with shadows that make them essentially incompatible with complex meshes like the Starlit player character meshes. for the sake of those who don't mind these glitches, Starlit does enable shadows, but i unfortunately have to recommend that you disable them until the minetest devs get their act together on this feature.

## gameplay
starlit is somewhat unusual in how it uses the minetest engine. it's a voxel game but not of the minecraft variety.

the most important thing to understand about starlit is that is is [*mean], by design.

* chance plays an important role. your escape pod might land in the midst of a lush, temperate forest with plenty of nearby shipwrecks to scavenge. or it might land in the exact geographic center of a vast, harsh desert that your suit's cooling systems can't protect you from, ten klicks from anything of value. "unfair", you say? tough. Farthest Shadow doesn't care about your feelings.
* death is much worse than a slap on the wrist. when you die, you drop your possessions and your suit, and respawn naked at your spawn point. this is a serious danger, as you might be kilometers away from your spawn point -- and there's no guarantee someone else won't take your suit before you can find your way back to it. good luck crossing long distances without climate control! if you haven't carefully prepared for this eventuality by keeping a spare suit by your spawn point, death can be devastating, to the point of making the game unsurvivable without another player's help. 

### scenarios
your starting character configuration depends on the scenario you select. (right now this is configured in minetest settings, which is intensely awkward, but i don't have a better solution). the scenario controls your species, sex, and starting inventory. [*neither species nor sex is cosmetic]; e.g. human females are physically weaker but psionically stronged than males. the current playable scenarios are:

#### Imperial Expat
[*phenotype]: human female
[*starting gear]: Commune survival kit
> Hoping to escape a miserable life deep in the grinding gears of the capitalist machine for the bracing freedom of the frontier, you sought entry as a colonist to the new Commune world of Thousand Petal. Fate -- which is to say, terrorists -- intervened, and you wound up stranded on Farthest Shadow with little more than the nanosuit on your back, ship blown to tatters and your soul thoroughly mauled by the explosion of a twisted alien artifact -- which SOMEONE neglected to inform you your ride would be carrying.
> At least you got some handy psionic powers out of this whole clusterfuck. Hopefully they're safe to use.

#### Gentleman Adventurer [!(unimplemented)]
[*phenotype]: human male
[*starting gear]: Imperial survival kit
> Tired of the same-old-same-old, sick of your idiot contemporaries, exasperated with the shallow soul-rotting luxury of life as landless lordling, and earnestly eager to enrage your father, you resolved to see the Reach in all her splendor. Deftly evading the usual tourist traps, you finagled your way into the confidence of the Commune ambassador with a few modest infusions of Father's money -- now [!that] should pop his monocle -- and secured yourself a seat on a ride to their brand-new colony at Thousand Petal. How exciting -- a genuine frontier outing!

#### Terrorist Tagalong
[*phenotype]: human female
[*starting gear]: star merc combat kit
> It turns out there's a *reason* Crown jobs pay so well.

#### Tradebird Bodyguard [!(unimplemented)]
[*phenotype]: male Usukwinti
[*starting gear]: Usuk survival kit
> You've never understood why astropaths of all people [!insist] on bodyguards. This one could probably make hash of a good-sized human batallion, if her feathers were sufficiently ruffled. Perhaps it's a status thing. Whatever the case, it's easy money.
> At least, it was supposed to be.

### controls
summon your Suit Interface by pressing the [*E] / [*Inventory] key. this will allow you to move items around in your inventory, but more importantly, it also allows you select or configure your Interaction Mode.

the top three buttons can be used to select (or deactivate) an Interaction Mode. an Interaction Mode can be configured by pressing the button immediately below. the active Interaction Mode controls the behavior of the mouse buttons when no item is selected in the hotbar.

the modes are:
................................................................................

to use a tool, select it in the hotbar. even if an Interaction Mode is active, the tool will take priority. press [*Left Button] / [*Punch] to use the tool on a block; for instance, to break a stone with a jackhammer. to configure a tool or use its secondary functions, if any, press [*Right Button] / [*Place].

hold [*Aux1] to activate your selected Maneuver. by default this is Sprint, which will consume stamina to allow you to run much faster. certain suits offer the Flight ability, which allows slow, mid-range flight. you can also unlock the psionic ability Lift, which allows very rapid flight but consumes psi at a prodigious rate.

you can only have one Maneuver active at a time, whether this is a Psi Maneuver (consuming psi), a Suit Maneuver (consuming battery), or a Body Maneuver (consuming stamina). Maneuvers are activated in their respective panel.

### stats
to survive and thrive on Farthest Shadow, you need to manage your stats carefully. stats impact gameplay and other stats in various ways.
* [*health]: self-explanatory. it hits zero, you die. measured in hit points (hp)
 ** regenerates very slowly, though nanomedical suites improve this considerably
* [*stamina]: how long you can sustain intense physical activity. measured in sprinting meters (m)
** regenerates quickly, depending on [*fatigue]
** consumed by biological abilities such as sprinting or Usuk flight.
** affects [*fatigue]: the lower your stamina bar, the faster your fatigue builds
* [*energy]: the amount of electrical power available to your environment suit. measured in joules (J)
** does not regenerate, but empty batteries can be swapped out to replenish energy in the field
** consumed by all suit abilities, including nanotech and weapons systems
* [*numina]: a measure of your available psionic power. measured in psi (ψ)
** accumulates slowly, depending on [*morale]
* [*nutrition]: how much energy your body has stored. measured in kCal. basal metabolic rate depends on species and sex
** affects [*morale]: if you're starving, you bleed morale more quickly
** affects [*health]: you will die if you do not eat enough
* [*hydration]: stay hydrated uwu. measured in liters (L)
** affects [*morale]: if you're parched, you bleed morale much more quickly
** affects [*health]: you will die very quickly if you do not drink enough
* [*warmth]: maintaining a healthy body temperature is crucially important on Farthest Shadow, which does not have many places a human can be comfortable without a powered environment suit. if you're outside your species' comfort range, your stamina regen and morale expenditure will be impacted. if you're outside your species' [*survival] range, you will start dying of hypo/hyperthermia. most environment suits have built-in thermoelectrics that will try to keep you at a comfortable (or merely survivable, in power save mode) temperature, but this can drain your suit batteries very quickly at extreme temperatures.
** affects [*morale], [*stamina], [*health]
* [*fatigue]: how long you've gone without sleep.
** affects [*stamina] regen: the higher your [*fatigue], the more slowly you regnerate stamina. if you are fully fatigued (exhausted), you do not regenerate stamina
* [*morale]: how happy you are. measured in the number of hours you can continue functioning emotionally
** diminishes over time. can be replenished by various activities, such as eating tasty food
** affects [*numina] regen: the lower your morale, the more slowly you accumulate numina. if you are completely out of morale (depressed), you do not accumulate any numina
* [*irradation]: how much ionizing radiation you've soaked up. measured in grays (Gy)
** your environment suit provides some protection against environmental radiation. how much depends on your model of suit.
** affects [*health]: slows natural healing. at values over 2Gy, you will start to hemorrhage health, though a capable nanomedical system can compensate up to a point
** affects [*numina]: you accumulate numina more quickly in high-rad areas
** naturally diminishes, but very slowly
* [*illness]: not everything on Farthest Shadow's ecosystem is, shall we say, biocompatible. measured in percent
** affects [*morale]: the higher your illness, the more quickly you bleed morale

your primary stats are shown on your HUD. ancillary stats can be viewed in the "body" panel.

### psionics
there are four types of psionic abilities: Manual, Maneuver, Ritual, and Contextual.

you can assign two Manual abilities at any given time and access them with the mouse buttons in Psionics mode.

you can select a Psi Maneuver in the Psionics panel and activate it by holding [*Aux1].