starlit  Check-in [f1e68eb97e]

Overview
Comment:ui latency/performance improvements, bugfixes
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: f1e68eb97e0b1041ad71b87fc27e985e790d18f5bc43ecfb74cedbb35fd6be95
User & Date: lexi on 2024-05-03 13:40:20
Other Links: manifest | tags
Context
2024-05-03
15:31
genericize impacts check-in: 467a75e0dc user: lexi tags: trunk
13:40
ui latency/performance improvements, bugfixes check-in: f1e68eb97e user: lexi tags: trunk
00:10
add sprint, improve bio job, rebalance stamina regen, various fixes check-in: cade6683f7 user: lexi tags: trunk
Changes

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

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
..
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
..
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
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
	--               W    Sp   Su    Au   W
	seasonalTemp = {-50, -10, 5, 5, -20, -50};
	def = {
		node_top      = 'starlit:greengraze', depth_top = 1;
		node_filler   = 'starlit:soil',    depth_filler = 4;
		node_riverbed = 'starlit:sand',  depth_riverbed = 4;
		y_min = 0;
		y_max = 512;
		heat_point = 20;
		humidity_point = 35;
	};
})

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

world.ecology.biomes.link('starlit:desert', {
	nightTempDelta = -40;
	waterTempDelta = 0;
	--               W    Sp  Su    Au   W
................................................................................
world.ecology.biomes.link('starlit:ocean', {
	nightTempDelta = -35;
	waterTempDelta = 5;
	seasonalTemp = {0}; -- no seasonal variance
	def = {
		y_max = 3;
		y_min = -512;
		heat_point = 50;
		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};
	def = {
		y_max = 70;
		y_min = 1;
		heat_point = 0;
		humidity_point = 5;
		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};
	def = {
		y_max = 30;
		y_min = 0;
		heat_point = 15;
		humidity_point = 35;
-- 		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 = 0;
		heat_point = 0;
		humidity_point = 0;
	};
})
minetest.register_craftitem('starlit_eco:fiber', {
	description = "Plant Fiber";
	groups = {fiber = 1};







|
|
|







 







|
|







 







|













|
|
|







 







|
|












|







7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
..
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
..
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
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
	--               W    Sp   Su    Au   W
	seasonalTemp = {-50, -10, 5, 5, -20, -50};
	def = {
		node_top      = 'starlit:greengraze', depth_top = 1;
		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};
	def = {
		node_top      = 'starlit:greengraze', depth_top = 1;
		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
................................................................................
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};
	def = {
		y_max = 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};
	def = {
		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};

Modified mods/starlit/species.lua from [7816834c67] to [47cb5f4a38].

44
45
46
47
48
49
50

51
52
53

54
55
56
57
58
59
60
61
..
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
				end
			elseif ctx.how.state == 'prog' then
				local d = ctx.how.delta
				local p = user.action.prog.sprint
				-- is the player currently holding any of WASD
				local isMoving = bit.band(0x0f, user.entity:get_player_control_bits()) ~= 0
				if p and isMoving then

					p.cb = p.cb + cost*d
					if p.cb >= 10 then
						user:statDelta('stamina', -10)

						if user:effectiveStat 'stamina' < 10 then halt() end
					end
				end
			elseif ctx.how.state == 'halt' then
				halt()
			end
		end;
	};
................................................................................
					local invis = lib.image '[fill:1x1:0,0:#00000000'
					local plate = adorn.suit and adorn.suit.plate or invis
					local lining = adorn.suit and adorn.suit.lining or invis

					return {lining, plate, skin, skin, eye, hair}
				end;
				stats = {
					psiRegen = 1.3;
					psiPower = 1.2;
					psi = 1.2;
					nutrition = .8; -- women have smaller stomachs
					hydration = .8;
					morale = 0.8; -- you are not She-Bear Grylls

				};
				traits = {
					health = 400;
					lungCapacity = .6;
					irradiation = 0.8; -- you are smaller, so it takes less rads to kill ya
					sturdiness = 0; -- women are more fragile and thus susceptible to blunt force trauma
					metabolism = .150; -- kCal/s
					painTolerance = 0.4;
					dehydration = 10e-4; -- L/s
					speed = 1.1;
					staminaRegen = 30.0;


				};
			};
			male = {
				name = 'Human Male';
				eyeHeight = 1.6;
				stats = {
					psiRegen = 1.0;
					psiPower = 1.0;
					psi = 1.0;
					nutrition = 1.0;
					hydration = 1.0;
					staminaRegen = 20; -- men are strong but have inferior endurance
				};
				traits = {
					health = 500;
					painTolerance = 1.0;
					lungCapacity = 1.0;
					sturdiness = 0.3;
					metabolism = .150; -- kCal/s
					dehydration = 15e-4; -- L/s
					speed = 1.0;


				};
			};
		};
		traits = {};
		abilities = {bioAbilities.sprint};
	};
}







>

|
|
>
|







 







<
<




>




<





|
>
>






<
<



|









>
>







44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
..
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
				end
			elseif ctx.how.state == 'prog' then
				local d = ctx.how.delta
				local p = user.action.prog.sprint
				-- is the player currently holding any of WASD
				local isMoving = bit.band(0x0f, user.entity:get_player_control_bits()) ~= 0
				if p and isMoving then
					user.cooldownTimes.stamina = minetest.get_gametime()
					p.cb = p.cb + cost*d
					if p.cb >= 5 then
						user:statDelta('stamina', -p.cb)
						p.cb = 0
						if user:effectiveStat 'stamina' < cost then halt() end
					end
				end
			elseif ctx.how.state == 'halt' then
				halt()
			end
		end;
	};
................................................................................
					local invis = lib.image '[fill:1x1:0,0:#00000000'
					local plate = adorn.suit and adorn.suit.plate or invis
					local lining = adorn.suit and adorn.suit.lining or invis

					return {lining, plate, skin, skin, eye, hair}
				end;
				stats = {


					psi = 1.2;
					nutrition = .8; -- women have smaller stomachs
					hydration = .8;
					morale = 0.8; -- you are not She-Bear Grylls
					irradiation = 0.8; -- you are smaller, so it takes less rads to kill ya
				};
				traits = {
					health = 400;
					lungCapacity = .6;

					sturdiness = 0; -- women are more fragile and thus susceptible to blunt force trauma
					metabolism = .150; -- kCal/s
					painTolerance = 0.4;
					dehydration = 10e-4; -- L/s
					speed = 1.1;
					staminaRegen = 10.0;
					psiRegen = 1.3;
					psiPower = 1.2;
				};
			};
			male = {
				name = 'Human Male';
				eyeHeight = 1.6;
				stats = {


					psi = 1.0;
					nutrition = 1.0;
					hydration = 1.0;
					staminaRegen = 7; -- men are strong but have inferior endurance
				};
				traits = {
					health = 500;
					painTolerance = 1.0;
					lungCapacity = 1.0;
					sturdiness = 0.3;
					metabolism = .150; -- kCal/s
					dehydration = 15e-4; -- L/s
					speed = 1.0;
					psiRegen = 1.0;
					psiPower = 1.0;
				};
			};
		};
		traits = {};
		abilities = {bioAbilities.sprint};
	};
}

Modified mods/starlit/stats.lua from [0892085f88] to [255a217522].

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
starlit.world.stats = {
	psi        = {min = 0, max = 500, base = 0, desc = U('ψ', 10), color = C(320), name = 'Numina'};
	-- numina is measured in daψ
	warmth     = {min = -1000, max = 1000, base = 0, desc = U('°C', 10, true), color = C(5), name = 'Warmth'};
	-- warmth in measured in d°C
	fatigue    = {min = 0, max = 76 * 60, base = 0, desc = U('hr', 60, true), color = C(288,.3,.5), name = 'Fatigue', srzType = T.decimal};
	-- fatigue is measured in minutes one needs to sleep to cure it
	stamina    = {min = 0, max = 40 * 100, base = true, desc = U('m', 100), color = C(88), name = 'Stamina'};
	-- stamina is measured in how many 10th-nodes (== cm) one can sprint
	nutrition  = {min = 0, max = 8000, base = 0, desc = U('kCal', 1, true), color = C(43,.5,.4), name = 'Nutrition', srzType = T.decimal};
	-- hunger is measured in kcalories one must consume to cure it. at 0, you start dying
	hydration  = {min = 0, max = 4, base = 0, desc = U('L', 1), color = C(217, .25,.4), name = 'Hydration', srzType = T.decimal};
	-- thirst is measured in L of H²O required to cure it
	morale     = {min = 0, max = 10 * 24 * 60, base = true, desc = U('hr', 60, true), color = C(0,0,.8), name = 'Morale', srzType = T.decimal};
	-- morale is measured in minutes. e.g. at base rate morale degrades by







|







24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
starlit.world.stats = {
	psi        = {min = 0, max = 500, base = 0, desc = U('ψ', 10), color = C(320), name = 'Numina'};
	-- numina is measured in daψ
	warmth     = {min = -1000, max = 1000, base = 0, desc = U('°C', 10, true), color = C(5), name = 'Warmth'};
	-- warmth in measured in d°C
	fatigue    = {min = 0, max = 76 * 60, base = 0, desc = U('hr', 60, true), color = C(288,.3,.5), name = 'Fatigue', srzType = T.decimal};
	-- fatigue is measured in minutes one needs to sleep to cure it
	stamina    = {min = 0, max = 10 * 20, base = true, desc = U('m', 100), color = C(88), name = 'Stamina'};
	-- stamina is measured in how many 10th-nodes (== cm) one can sprint
	nutrition  = {min = 0, max = 8000, base = 0, desc = U('kCal', 1, true), color = C(43,.5,.4), name = 'Nutrition', srzType = T.decimal};
	-- hunger is measured in kcalories one must consume to cure it. at 0, you start dying
	hydration  = {min = 0, max = 4, base = 0, desc = U('L', 1), color = C(217, .25,.4), name = 'Hydration', srzType = T.decimal};
	-- thirst is measured in L of H²O required to cure it
	morale     = {min = 0, max = 10 * 24 * 60, base = true, desc = U('hr', 60, true), color = C(0,0,.8), name = 'Morale', srzType = T.decimal};
	-- morale is measured in minutes. e.g. at base rate morale degrades by

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

36
37
38
39
40
41
42

43
44
45
46
47
48
49
..
56
57
58
59
60
61
62



63
64
65
66
67
68
69
...
131
132
133
134
135
136
137


138
139
140
141
142
143
144
145
...
315
316
317
318
319
320
321







322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
...
394
395
396
397
398
399
400





401
402
403
404
405
406
407
...
424
425
426
427
428
429
430

431
432
433
434
435
436
437
...
726
727
728
729
730
731
732
733

734
735
736
737
738
739
740
...
906
907
908
909
910
911
912







913
914
915
916
917
918
919
...
942
943
944
945
946
947
948

949

950
951
952
953
954
955
956
...
967
968
969
970
971
972
973
974
975
976
977




978
979
980
981
982
983
984
			name = luser:get_player_name()
		end
		return {
			entity = luser;
			name = name;
			hud = {
				elt = {};

			};
			tree = {};
			action = {
				bits = 0; -- for control deltas
				prog = {}; -- for recording action progress on a node; reset on refocus
				tgt = {type='nothing'};
				sfx = {};
................................................................................
				psi = {primary = nil, secondary = nil};
				maneuver = nil;
			};
			pref = {
				calendar = 'commune';
			};
			overlays = {};



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

				if     dt[stat]+base > max then dt[stat] = max-base
				elseif dt[stat]+base < min then dt[stat] = min-base end
				self:pushPersona()
			end




			self:updateHUD()
			-- TODO trigger relevant animations?
		end;
		statRange = function(self, stat) --> min, max, base
			return starlit.world.species.statRange(
				self.persona.species, self.persona.speciesVariant, stat)
		end;
		effectiveStat = function(self, stat)
................................................................................
			return bar, {x=3 * def.size, y=16} -- x*2??? what
		end;
		createHUD = function(self)
			local function basicStat(statName)
				return function(user, bar)
					return self:effectiveStat(statName)
				end







			end
			local function batteryLookup(user)
				local max = user:suitPowerCapacity()
				if max == 0 then return 0, 0 end
				local ch = user:suitCharge()
				return (ch/max)*100, ch/max
			end
			local function C(h,s,l) return {hue=h,sat=s,lum=l} end
			local hbofs = (1+self.entity:hud_get_hotbar_itemcount()) * 25
			local bpad = 8
			self.hud.elt.health = self:attachStatBar {
				name = 'health', stat = basicStat 'health';
				color = C(340,0,.3), size = 100;
				pos = {x=0.5, y=1}, ofs = {x = -hbofs, y=-48 - bpad};
				dir = 1;
				align = {x=-1, y=-1};
			}
			self.hud.elt.stamina = self:attachStatBar {
				name = 'stamina', stat = basicStat 'stamina';
				color = C(60,0,.2), size = 100;
				pos = {x=0.5, y=1}, ofs = {x = -hbofs, y=-24 - bpad};
				dir = 1;
				align = {x=-1, y=-1};
			}
			self.hud.elt.bat = self:attachStatBar {
				name = 'battery', stat = batteryLookup;
				color = C(190,0,.2), size = 100;
				pos = {x=0.5, y=1}, ofs = {x = hbofs - 4, y=-48 - bpad};
				dir = 0;
				align = {x=1, y=-1};
			}
			self.hud.elt.psi = self:attachStatBar {
				name = 'psi', stat = basicStat 'psi';
				color = C(320,0,.2), size = 100;
				pos = {x=0.5, y=1}, ofs = {x = hbofs - 4, y=-24 - bpad};
				dir = 0;
				align = {x=1, y=-1};
			}
			self.hud.elt.time = self:attachTextBox {
				name = 'time';
................................................................................
				measure = function(user)
					local hot = self:effectiveStat 'irradiation'
					local color = self:uiColor():lerp(lib.color(0.3, 1, 0), math.min(1, hot/5))
					local txt = string.format("%sGy", math.floor(hot))
					return (hot/5), txt, color
				end;
			}





			self.hud.elt.crosshair = self:attachImage {
				name = 'crosshair';
				tex = '';
				pos = {x=.5, y=.5};
				scale = {x=1,y=1};
				ofs = {x=0, y=0};
				align = {x=0, y=0};
................................................................................
				ofs = {x=0, y=0};
				align = {x=0, y=-1};
				z = -1;
				update = function(user, set)
					set('text', hudAdjustBacklight(hudCenterBG):render())
				end;
			};

		end;
		deleteHUD = function(self)
			for name, e in pairs(self.hud.elt) do
				self:hud_delete(e.id)
			end
		end;
		updateHUD = function(self)
................................................................................
				local suit = self:getSuit()
				suit:establishInventories(self.entity)

				if self:suitCharge() <= 0 then
					self:suitPowerStateSet 'off'
				end
			end
			self:updateHUD()

		end;
		reconfigureSuit = function(self)
			-- and here's where things get ugly
			-- you can't have an inventory inside another item. to hack around this,
			-- we use the player as the location of the suit inventories, and whenever
			-- there's a change in the content of these inventories, this function is
			-- called to serialize those inventories out to the suit stack
................................................................................
			local fd = stack:take_item(n)
			local stats = starlit.world.food.effectiveStats(fd)

			return stack
		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
................................................................................
				local tempPenalty = tempDiff/3
				moralePenalty = moralePenalty + tempPenalty
				heatPenalty = heatPenalty + tempPenalty
			end

			-- penalize heavy phys. activity
			local stamina, sp = u:effectiveStat 'stamina'

			fatiguePenalty = fatiguePenalty * (1 + 9*(1-sp))


			local food = u:effectiveStat 'nutrition'
			local water = u:effectiveStat 'hydration'
			local rads = u:effectiveStat 'irradiation'
			if food < 1000 then moralePenalty = moralePenalty + (1 - (food/1000)) * 5 end
			if water < 1   then moralePenalty = moralePenalty + (1 - (water/1)) * 10 end

................................................................................
				u:statDelta('health', -5*biointerval)
			end

			if water == 0 then -- dying of thirst
				u:statDelta('health', -20*biointerval)
			end

			if sp < 1.0 then
				u:statDelta('stamina', u:phenoTrait('staminaRegen',1) / heatPenalty)
-- 				print('stam', u:effectiveStat 'stamina', u:phenoTrait('staminaRegen',1) / heatPenalty, heatPenalty)
			end




		end
	end
end)

local cbit = {
	up   = 0x001;
	down = 0x002;







>







 







>
>
>







 







>
>
|







 







>
>
>
>
>
>
>










|
|
|




|
|












|
|







 







>
>
>
>
>







 







>







 







|
>







 







>
>
>
>
>
>
>







 







>

>







 







|
|


>
>
>
>







36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
..
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
...
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
...
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
...
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
...
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
...
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
...
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
...
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
...
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
			name = luser:get_player_name()
		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 = {};
................................................................................
				psi = {primary = nil, secondary = nil};
				maneuver = nil;
			};
			pref = {
				calendar = 'commune';
			};
			overlays = {};
			cooldownTimes = {
				stamina = 0;
			};
		}
	end;
	__index = {
		--------------
		-- overlays --
		--------------
		updateOverlays = function(self)
................................................................................

				if     dt[stat]+base > max then dt[stat] = max-base
				elseif dt[stat]+base < min then dt[stat] = min-base end
				self:pushPersona()
			end


			local sb = self.hud.bar[stat]
			if sb then sb:update() end
-- 			self:updateHUD()
			-- TODO trigger relevant animations?
		end;
		statRange = function(self, stat) --> min, max, base
			return starlit.world.species.statRange(
				self.persona.species, self.persona.speciesVariant, stat)
		end;
		effectiveStat = function(self, stat)
................................................................................
			return bar, {x=3 * def.size, y=16} -- x*2??? what
		end;
		createHUD = function(self)
			local function basicStat(statName)
				return function(user, bar)
					return self:effectiveStat(statName)
				end
			end
			local function attachBasicStat(def)
				local statName = def.stat
				def.stat = basicStat(def.stat)
				local b = self:attachStatBar(def)
				self.hud.bar[statName] = b
				return b
			end
			local function batteryLookup(user)
				local max = user:suitPowerCapacity()
				if max == 0 then return 0, 0 end
				local ch = user:suitCharge()
				return (ch/max)*100, ch/max
			end
			local function C(h,s,l) return {hue=h,sat=s,lum=l} end
			local hbofs = (1+self.entity:hud_get_hotbar_itemcount()) * 25
			local bpad = 8
			self.hud.elt.health = attachBasicStat {
				name = 'health', stat = 'health';
				color = C(10,0,.3), size = 100;
				pos = {x=0.5, y=1}, ofs = {x = -hbofs, y=-48 - bpad};
				dir = 1;
				align = {x=-1, y=-1};
			}
			self.hud.elt.stamina = attachBasicStat {
				name = 'stamina', stat = 'stamina';
				color = C(60,0,.2), size = 100;
				pos = {x=0.5, y=1}, ofs = {x = -hbofs, y=-24 - bpad};
				dir = 1;
				align = {x=-1, y=-1};
			}
			self.hud.elt.bat = self:attachStatBar {
				name = 'battery', stat = batteryLookup;
				color = C(190,0,.2), size = 100;
				pos = {x=0.5, y=1}, ofs = {x = hbofs - 4, y=-48 - bpad};
				dir = 0;
				align = {x=1, y=-1};
			}
			self.hud.elt.psi = attachBasicStat {
				name = 'psi', stat = 'psi';
				color = C(320,0,.2), size = 100;
				pos = {x=0.5, y=1}, ofs = {x = hbofs - 4, y=-24 - bpad};
				dir = 0;
				align = {x=1, y=-1};
			}
			self.hud.elt.time = self:attachTextBox {
				name = 'time';
................................................................................
				measure = function(user)
					local hot = self:effectiveStat 'irradiation'
					local color = self:uiColor():lerp(lib.color(0.3, 1, 0), math.min(1, hot/5))
					local txt = string.format("%sGy", math.floor(hot))
					return (hot/5), txt, color
				end;
			}

			-- special-case the meters
			self.hud.bar.irradiation = self.hud.elt.geiger
			self.hud.bar.warmth = self.hud.elt.temp

			self.hud.elt.crosshair = self:attachImage {
				name = 'crosshair';
				tex = '';
				pos = {x=.5, y=.5};
				scale = {x=1,y=1};
				ofs = {x=0, y=0};
				align = {x=0, y=0};
................................................................................
				ofs = {x=0, y=0};
				align = {x=0, y=-1};
				z = -1;
				update = function(user, set)
					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)
................................................................................
				local suit = self:getSuit()
				suit:establishInventories(self.entity)

				if self:suitCharge() <= 0 then
					self:suitPowerStateSet 'off'
				end
			end
-- 			self:updateHUD()
			self.hud.elt.bat:update()
		end;
		reconfigureSuit = function(self)
			-- and here's where things get ugly
			-- you can't have an inventory inside another item. to hack around this,
			-- we use the player as the location of the suit inventories, and whenever
			-- there's a change in the content of these inventories, this function is
			-- called to serialize those inventories out to the suit stack
................................................................................
			local fd = stack:take_item(n)
			local stats = starlit.world.food.effectiveStats(fd)

			return stack
		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
................................................................................
				local tempPenalty = tempDiff/3
				moralePenalty = moralePenalty + tempPenalty
				heatPenalty = heatPenalty + tempPenalty
			end

			-- penalize heavy phys. activity
			local stamina, sp = u:effectiveStat 'stamina'
			local fatigue, fp = u:effectiveStat 'fatigue'
			fatiguePenalty = fatiguePenalty * (1 + 9*(1-sp))
			local penaltyFromFatigue = 1 - fp

			local food = u:effectiveStat 'nutrition'
			local water = u:effectiveStat 'hydration'
			local rads = u:effectiveStat 'irradiation'
			if food < 1000 then moralePenalty = moralePenalty + (1 - (food/1000)) * 5 end
			if water < 1   then moralePenalty = moralePenalty + (1 - (water/1)) * 10 end

................................................................................
				u:statDelta('health', -5*biointerval)
			end

			if water == 0 then -- dying of thirst
				u:statDelta('health', -20*biointerval)
			end

			if sp < 1.0 and minetest.get_gametime() - u.cooldownTimes.stamina > 5.0 then
				u:statDelta('stamina', (u:phenoTrait('staminaRegen',1) * penaltyFromFatigue) / heatPenalty)
-- 				print('stam', u:effectiveStat 'stamina', u:phenoTrait('staminaRegen',1) / heatPenalty, heatPenalty)
			end

			local morale, mp = u:effectiveStat 'morale'
			local pr = u:phenoTrait 'psiRegen'
			u:statDelta('psi', pr * penaltyFromFatigue * mp)
		end
	end
end)

local cbit = {
	up   = 0x001;
	down = 0x002;