starlit  Check-in [953151446f]

Overview
Comment:better alarm LEDs, continue work on matter compiler UI, hack around gravitational horrorscape (i.e. stop shitting all over the server's `minetest.conf`), better stat interface, tweak some compute stats, be more generous with starting battery loadout, mercilessly squash numberless bugs beneath my jackbooted heel
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 953151446f688fc9771fdc5fe3b2278a6a3981b9ac6e05dc595388f31d0ca482
User & Date: lexi on 2024-05-05 19:31:39
Other Links: manifest | tags
Context
2024-05-06
16:20
complete (-ish) matter compiler UI (power drain still missing), add printable chemical light check-in: 3df08bd5ac user: lexi tags: trunk
2024-05-05
19:31
better alarm LEDs, continue work on matter compiler UI, hack around gravitational horrorscape (i.e. stop shitting all over the server's `minetest.conf`), better stat interface, tweak some compute stats, be more generous with starting battery loadout, mercilessly squash numberless bugs beneath my jackbooted heel check-in: 953151446f user: lexi tags: trunk
2024-05-04
22:41
add beginnings of matter compiler UI, check in missing files check-in: 0e7832a24c user: lexi tags: trunk
Changes

Modified mods/starlit-electronics/init.lua from [fff344db38] to [d82ba893dd].

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
313
314

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
362
363
364
365
366
367
368
369
370
371
...
373
374
375
376
377
378
379

380
381
382
383
384
385
386
387
388
389
390
391
...
393
394
395
396
397
398
399

400
401
402
403
404
405
406
407
408
409
410
411
412
413
...
416
417
418
419
420
421
422

423
424
425
426
427
428
429
430
431
432
433
434
435
436
437

438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
...
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
...
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
-- firstborn ("god-tier"): exceptional

local batteryTiers = {
	makeshift = {
		name = 'Makeshift'; capacity = .5, decay = 3, leak = 2, dischargeRate = 1,
		fab = starlit.type.fab {
			element = {copper=10};


		};
		desc = "Every attosecond this electrical abomination doesn't explode in your face is but the unearned grace of the Wild Gods.";
		complexity = 1;
		sw = {rarity = 1};
	};
	imperial  = {
		name = 'Imperial'; capacity = 2, decay = 2, leak = 2, dischargeRate = 2; 
		fab = starlit.type.fab {
			element = {copper=15, iron = 20};
			size = { print = 0.1 };


		};
		desc = "The Empire's native technology is a lumbering titan: bulky, inefficient, unreliable, ugly, and awesomely powerful. Their batteries are no exception, with raw capacity and throughput that exceed even Usukinwya designs.";
		drm = 1;
		complexity = 2;
		sw = {rarity = 2};
	};
	commune   = {
		name = 'Commune'; capacity = 1, decay = .5, leak = .2, dischargeRate = 1; 
		fab = starlit.type.fab {
			element = {vanadium = 50};
			metal = {steel=10};
			size = { print = 0.05 };

		};
		desc = "The Commune's proprietary battery designs prioritize reliability, compactness, and maintenance concerns above raw throughput, with an elegance of engineering and design that would make a Su'ikuri cry.";
		complexity = 5;
		sw = {rarity = 3};
	};
	usukwinya = {
		name = 'Usukwinya'; capacity = 2, decay = 1, leak = 1, dischargeRate = 1.5,
		fab = starlit.type.fab {
			element = {argon=10};
			metal = {vanadium=30};
			size = { print = 0.07 };

		};
		desc = "A race of consummate value engineers, the Usukwinya have spent thousands of years refining their tech to be as cheap to build as possible, without compromising much on quality. The Tradebirds drive an infamously hard bargain, but their batteries are more than worth their meagre cost.";
		drm = 2;
		sw = {rarity = 10};
		complexity = 15;
	};
	eluthrai  = {
		name = 'Eluthrai'; capacity = 3, decay = .4, leak = .1, dischargeRate = 1.5,
		fab = starlit.type.fab {
			element = {beryllium=20, platinum=20, technetium = 1};
			metal = {cinderstone = 10};
			size = { print = 0.03 };


		};
		desc = "The uncompromising Eluthrai are never satisfied until every quantifiable characteristic of their tech is maximally optimised down to the picoscale. Their batteries are some of the best in the Reach, and unquestionably the most expensive -- especially for those lesser races trying to copy the designs without the benefit of the sublime autofabricator ecosystem of the Eluthrai themselves.";
		complexity = 200;
		sw = {rarity = 0}; -- you think you're gonna buy eluthran schematics on SuperDiscountNanoWare.space??
	};
	firstborn = {
		name = 'Firstborn'; capacity = 5, decay = 0.1, leak = 0, dischargeRate = 3;
		fab = starlit.type.fab {
			element = {neodymium=20, xenon=150, technetium=5};
			metal = {sunsteel = 10};
			crystal = {astrite = 1};
			size = { print = 0.05 };


		};
		desc = "Firstborn engineering seamlessly merges psionic effects with a mastery of the physical universe unattained by even the greatest of the living Starsouls. Their batteries reach levels of performance that strongly imply Quantum Gravity Theory -- and several major holy books -- need to be rewritten. From the ground up.";
		complexity = 1000;
		sw = {rarity = 0}; -- lol no
	};
}

local batterySizes = {
	small = {name = 'Small', capacity = .5, dischargeRate =  .5, complexity = 1, matMult = .5, fab = starlit.type.fab {size={print=0.1}}};

	mid   = {                capacity =  1, dischargeRate =   1, complexity = 1, matMult = 1, fab = starlit.type.fab {size={print=0.3}}};

	large = {name = 'Large', capacity =  2, dischargeRate = 1.5, complexity = 1, matMult = 1.5, fab = starlit.type.fab {size={print=0.5}}};

	huge  = {name = 'Huge',  capacity =  3, dischargeRate =   2, complexity = 1, matMult = 2, fab = starlit.type.fab {size={print=0.8}}};

}

local batteryTypes = {
	supercapacitor = {
		name = 'Supercapacitor';
		desc = 'Room-temperature superconductors make for very reliable, high-dischargeRate, but low-capacity batteries.';
		fab = starlit.type.fab {
			metal = { enodium = 5 };
			size = {print=0.8};

		};
		sw = {
			cost = {
				cycles = 5e9; -- 5 bil cycles
				ram = 10e9; -- 10GB
			};
			pgmSize = 2e9; -- 2GB
			rarity = 5;
		};
		capacity = 50e3, dischargeRate = 1000;
		leak = 0, decay = 1e-6;

................................................................................
	};
	chemical = {
		name = 'Chemical';
		desc = '';
		fab = starlit.type.fab {
			element = { lithium = 3 };
			size = {print=1.0};

		};
		sw = {
			cost = {
				cycles = 1e9; -- 1 bil cycles
				ram = 2e9; -- 2GB
			};
			pgmSize = 512e6; -- 512MB
			rarity = 2;
		};
		capacity = 200e3, dischargeRate = 200;
		leak = 0.2, decay = 1e-2;
		complexity = 1;
................................................................................
	carbon = {
		name = 'Carbon';
		desc = 'Carbon nanotubes form the basis of many important metamaterials, chief among them power-polymer.';
		capacity = 1;
		fab = starlit.type.fab {
			element = { carbon = 40 };
			size = {print=0.5};

		};
		sw = {
			cost = {
				cycles = 50e9; -- 50 bil cycles
				ram = 64e9; -- 64GB
			};
			pgmSize = 1e9; -- 1GB
			rarity = 10;
		};
		capacity = 100e3, dischargeRate = 500;
		leak = 0.1, decay = 1e-3;
		complexity = 10;
	};
	hybrid = {
................................................................................
		capacity = 1;
		fab = starlit.type.fab {
			element = {
				lithium = 10;
				carbon = 20;
			};
			size = {print=1.5};

		};
		sw = {
			cost = {
				cycles = 65e9; -- 65 bil cycles
				ram = 96e9; -- 96GB
			};
			pgmSize = 5e9; -- 5GB
			rarity = 15;
		};
		capacity = 300e3, dischargeRate = 350;
		leak = 0.3, decay = 1e-5;
		complexity = 30;
	};
}


local function elemath(dest, src, mult)
	dest = dest or {}
	for k,v in pairs(src) do
		if not dest[k] then dest[k] = 0 end
		dest[k] = dest[k] + v*mult
	end
	return dest
end

for bTypeName, bType in pairs(batteryTypes) do
for bTierName, bTier in pairs(batteryTiers) do
for bSizeName, bSize in pairs(batterySizes) do
	-- elemath(elementCost, bType.fab.element or {}, bSize.matMult)
	-- elemath(elementCost, bTier.fab.element or {}, bSize.matMult)
	-- elemath(metalCost, bType.fab.metal or {}, bSize.matMult)
................................................................................
	})
end)

-- in case other mods want to define their own tiers
E.chip.tiers = lib.registry.mk 'starlit_electronics:chipTiers'
E.chip.tiers.meld {
	-- GP chips
	tiny    = {name = 'Tiny Chip', clockRate = 512e3, flash = 4096, ram = 1024, powerEfficiency = 1e9, size = 1};
	small   = {name = 'Small Chip', clockRate = 128e6, flash = 512e6, ram = 512e6, powerEfficiency = 1e8, size = 3};
	med     = {name = 'Chip', clockRate = 1e9, flash = 4e9, ram = 4e9, powerEfficiency = 1e7, size = 6};
	large   = {name = 'Large Chip', clockRate = 2e9, flash = 8e9, ram = 8e9, powerEfficiency = 1e6, size = 8};
	-- specialized chips
	compute = {name = 'Compute Chip', clockRate = 4e9, flash = 24e6, ram = 64e9, powerEfficiency = 1e8, size = 4};
	data    = {name = 'Data Chip', clockRate = 128e3, flash = 2e12, ram = 32e3, powerEfficiency = 1e5, size = 4};
	lp      = {name = 'Low-Power Chip', clockRate = 128e6, flash = 64e6, ram = 1e9, powerEfficiency = 1e10, size = 4};
	carbon  = {name = 'Carbon Chip', clockRate = 64e6, flash = 32e6, ram = 2e6, powerEfficiency = 2e9, size = 2, circ='carbon'};
}

E.chip.tiers.foreach('starlit_electronics:genChips', {}, function(id, t)
	id = t.id or string.format('%s:chip_%s', minetest.get_current_modname(), id)
	local circMat = t.circ or 'silicon';
	starlit.item.chip.link(id, {
		name = t.name;
................................................................................
			flag = {
				silicompile = true;
			};
			time = {
				silicompile = t.size * 24*60;
			};
			cost = {
				energy = 50e3 + t.size * 15e2;
			};
			element = {
				[circMat] = 50 * t.size;
				copper = 30;
				gold = 15;
			};
		};







>
>










>
>












>











>












>
>












>
>








|
>
|
>
|
>
|
>









>



|
|







 







>



|
|







 







>



|
|

|







 







>



|
|

|








>







|







 







|
|
|
|

|
|
|
|







 







|







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
313
314
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
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
...
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
...
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
...
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
...
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
...
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
-- firstborn ("god-tier"): exceptional

local batteryTiers = {
	makeshift = {
		name = 'Makeshift'; capacity = .5, decay = 3, leak = 2, dischargeRate = 1,
		fab = starlit.type.fab {
			element = {copper=10};
			cost = {power = 0.3};
			time = {print = .25};
		};
		desc = "Every attosecond this electrical abomination doesn't explode in your face is but the unearned grace of the Wild Gods.";
		complexity = 1;
		sw = {rarity = 1};
	};
	imperial  = {
		name = 'Imperial'; capacity = 2, decay = 2, leak = 2, dischargeRate = 2; 
		fab = starlit.type.fab {
			element = {copper=15, iron = 20};
			size = { print = 0.1 };
			cost = {power = 2};
			time = {print = .5};
		};
		desc = "The Empire's native technology is a lumbering titan: bulky, inefficient, unreliable, ugly, and awesomely powerful. Their batteries are no exception, with raw capacity and throughput that exceed even Usukinwya designs.";
		drm = 1;
		complexity = 2;
		sw = {rarity = 2};
	};
	commune   = {
		name = 'Commune'; capacity = 1, decay = .5, leak = .2, dischargeRate = 1; 
		fab = starlit.type.fab {
			element = {vanadium = 50};
			metal = {steel=10};
			size = { print = 0.05 };
			cost = {power = 1};
		};
		desc = "The Commune's proprietary battery designs prioritize reliability, compactness, and maintenance concerns above raw throughput, with an elegance of engineering and design that would make a Su'ikuri cry.";
		complexity = 5;
		sw = {rarity = 3};
	};
	usukwinya = {
		name = 'Usukwinya'; capacity = 2, decay = 1, leak = 1, dischargeRate = 1.5,
		fab = starlit.type.fab {
			element = {argon=10};
			metal = {vanadium=30};
			size = { print = 0.07 };
			cost = {power = .8};
		};
		desc = "A race of consummate value engineers, the Usukwinya have spent thousands of years refining their tech to be as cheap to build as possible, without compromising much on quality. The Tradebirds drive an infamously hard bargain, but their batteries are more than worth their meagre cost.";
		drm = 2;
		sw = {rarity = 10};
		complexity = 15;
	};
	eluthrai  = {
		name = 'Eluthrai'; capacity = 3, decay = .4, leak = .1, dischargeRate = 1.5,
		fab = starlit.type.fab {
			element = {beryllium=20, platinum=20, technetium = 1};
			metal = {cinderstone = 10};
			size = { print = 0.03 };
			cost = {power = 10};
			time = {print = 2};
		};
		desc = "The uncompromising Eluthrai are never satisfied until every quantifiable characteristic of their tech is maximally optimised down to the picoscale. Their batteries are some of the best in the Reach, and unquestionably the most expensive -- especially for those lesser races trying to copy the designs without the benefit of the sublime autofabricator ecosystem of the Eluthrai themselves.";
		complexity = 200;
		sw = {rarity = 0}; -- you think you're gonna buy eluthran schematics on SuperDiscountNanoWare.space??
	};
	firstborn = {
		name = 'Firstborn'; capacity = 5, decay = 0.1, leak = 0, dischargeRate = 3;
		fab = starlit.type.fab {
			element = {neodymium=20, xenon=150, technetium=5};
			metal = {sunsteel = 10};
			crystal = {astrite = 1};
			size = { print = 0.05 };
			cost = {power = 50};
			time = {print = 4};
		};
		desc = "Firstborn engineering seamlessly merges psionic effects with a mastery of the physical universe unattained by even the greatest of the living Starsouls. Their batteries reach levels of performance that strongly imply Quantum Gravity Theory -- and several major holy books -- need to be rewritten. From the ground up.";
		complexity = 1000;
		sw = {rarity = 0}; -- lol no
	};
}

local batterySizes = {
	small = {name = 'Small', capacity = .5, dischargeRate =  .5, complexity = 1, matMult = .5,
		fab = starlit.type.fab {size={print=0.1},cost={power=.5},time={print=25}}};
	mid   = {                capacity =  1, dischargeRate =   1, complexity = 1, matMult = 1,
		fab = starlit.type.fab {size={print=0.3},cost={power=1},time={print=40}}};
	large = {name = 'Large', capacity =  2, dischargeRate = 1.5, complexity = 1, matMult = 1.5,
		fab = starlit.type.fab {size={print=0.5},cost={power=2},time={print=60}}};
	huge  = {name = 'Huge',  capacity =  3, dischargeRate =   2, complexity = 1, matMult = 2,
		fab = starlit.type.fab {size={print=0.8},cost={power=8},time={print=120}}};
}

local batteryTypes = {
	supercapacitor = {
		name = 'Supercapacitor';
		desc = 'Room-temperature superconductors make for very reliable, high-dischargeRate, but low-capacity batteries.';
		fab = starlit.type.fab {
			metal = { enodium = 5 };
			size = {print=0.8};
			cost = {power = 1e3};
		};
		sw = {
			cost = {
				cycles = 48e9; -- 48 bil cycles
				ram = 4e9; -- 10GB
			};
			pgmSize = 2e9; -- 2GB
			rarity = 5;
		};
		capacity = 50e3, dischargeRate = 1000;
		leak = 0, decay = 1e-6;

................................................................................
	};
	chemical = {
		name = 'Chemical';
		desc = '';
		fab = starlit.type.fab {
			element = { lithium = 3 };
			size = {print=1.0};
			cost = {power = .5e3};
		};
		sw = {
			cost = {
				cycles = 16e9; -- 16 bil cycles
				ram = 1e9; -- 1GB
			};
			pgmSize = 512e6; -- 512MB
			rarity = 2;
		};
		capacity = 200e3, dischargeRate = 200;
		leak = 0.2, decay = 1e-2;
		complexity = 1;
................................................................................
	carbon = {
		name = 'Carbon';
		desc = 'Carbon nanotubes form the basis of many important metamaterials, chief among them power-polymer.';
		capacity = 1;
		fab = starlit.type.fab {
			element = { carbon = 40 };
			size = {print=0.5};
			cost = {power = 2.5e3};
		};
		sw = {
			cost = {
				cycles = 256e9; -- 256 bil cycles
				ram = 16e9; -- 64GB
			};
			pgmSize = 4e9; -- 4GB
			rarity = 10;
		};
		capacity = 100e3, dischargeRate = 500;
		leak = 0.1, decay = 1e-3;
		complexity = 10;
	};
	hybrid = {
................................................................................
		capacity = 1;
		fab = starlit.type.fab {
			element = {
				lithium = 10;
				carbon = 20;
			};
			size = {print=1.5};
			cost = {power = 10e3};
		};
		sw = {
			cost = {
				cycles = 512e9; -- 512 bil cycles
				ram = 24e9; -- 96GB
			};
			pgmSize = 7e9; -- 7GB
			rarity = 15;
		};
		capacity = 300e3, dischargeRate = 350;
		leak = 0.3, decay = 1e-5;
		complexity = 30;
	};
}

--[[
local function elemath(dest, src, mult)
	dest = dest or {}
	for k,v in pairs(src) do
		if not dest[k] then dest[k] = 0 end
		dest[k] = dest[k] + v*mult
	end
	return dest
end]]

for bTypeName, bType in pairs(batteryTypes) do
for bTierName, bTier in pairs(batteryTiers) do
for bSizeName, bSize in pairs(batterySizes) do
	-- elemath(elementCost, bType.fab.element or {}, bSize.matMult)
	-- elemath(elementCost, bTier.fab.element or {}, bSize.matMult)
	-- elemath(metalCost, bType.fab.metal or {}, bSize.matMult)
................................................................................
	})
end)

-- in case other mods want to define their own tiers
E.chip.tiers = lib.registry.mk 'starlit_electronics:chipTiers'
E.chip.tiers.meld {
	-- GP chips
	tiny    = {name = 'Tiny Chip', clockRate = 512e3, flash = 4096, ram = 1024, powerEfficiency = 1e10, size = 1};
	small   = {name = 'Small Chip', clockRate = 128e6, flash = 512e6, ram = 512e6, powerEfficiency = 1e9, size = 3};
	med     = {name = 'Chip', clockRate = 1e9, flash = 4e9, ram = 4e9, powerEfficiency = 1e8, size = 6};
	large   = {name = 'Large Chip', clockRate = 2e9, flash = 8e9, ram = 8e9, powerEfficiency = 1e7, size = 8};
	-- specialized chips
	compute = {name = 'Compute Chip', clockRate = 4e9, flash = 24e6, ram = 64e9, powerEfficiency = 1e9, size = 4};
	data    = {name = 'Data Chip', clockRate = 128e3, flash = 2e12, ram = 32e3, powerEfficiency = 1e6, size = 4};
	lp      = {name = 'Low-Power Chip', clockRate = 128e6, flash = 64e6, ram = 1e9, powerEfficiency = 1e11, size = 4};
	carbon  = {name = 'Carbon Chip', clockRate = 64e6, flash = 32e6, ram = 2e6, powerEfficiency = 2e10, size = 2, circ='carbon'};
}

E.chip.tiers.foreach('starlit_electronics:genChips', {}, function(id, t)
	id = t.id or string.format('%s:chip_%s', minetest.get_current_modname(), id)
	local circMat = t.circ or 'silicon';
	starlit.item.chip.link(id, {
		name = t.name;
................................................................................
			flag = {
				silicompile = true;
			};
			time = {
				silicompile = t.size * 24*60;
			};
			cost = {
				power = 50e3 + t.size * 15e2;
			};
			element = {
				[circMat] = 50 * t.size;
				copper = 30;
				gold = 15;
			};
		};

Modified mods/starlit-electronics/sw.lua from [430c447d0b] to [da2672ba3c].

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
	};
	run = shredder{range=3, powerDraw=200};
})

starlit.item.sw.link('starlit_electronics:compile_commune', {
	name = 'Compile Matter';
	kind = 'suitPower', powerKind = 'direct';
	desc = "A basic suit matter compiler program, rather slow but ruthlessly optimized for power- and memory-efficiency by some of the Commune's most fanatic coders.";
	size = 700e3;
	cost = {
		cycles = 300e6;
		ram = 2e9;
	};
	ui = 'starlit:compile-matter-component';
	run = function(user, ctx)
	end;
})

starlit.item.sw.link('starlit_electronics:compile_block_commune', {
	name = 'Compile Block';
	kind = 'suitPower', powerKind = 'active';
	desc = "An advanced suit matter compiler program, capable of printing complete devices and structure parts directly into the world.";
	size = 5e6;
	cost = {
		cycles = 700e6;
		ram = 4e9;
	};
	ui = 'starlit:compile-matter-block';
	run = function(user, ctx)
	end;
})















do local J = starlit.store.compilerJob
	starlit.item.sw.link('starlit_electronics:driver_compiler_commune', {
		name = 'Matter Compiler';
		kind = 'driver';
		desc = "A driver for a standalone matter compiler, suitable for building larger components than your suit alone can handle.";
		size = 850e3;
		cost = {
			cycles = 400e6;
			ram = 2e9;
		};
		ui = 'starlit:device-compile-matter-component';
		run = function(user, ctx)
		end;
		bgProc = function(user, ctx, interval, runState)
			if runState.flags.compiled == true then return false end
			-- only so many nanides to go around







|


|
|












|
|





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









|







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
	};
	run = shredder{range=3, powerDraw=200};
})

starlit.item.sw.link('starlit_electronics:compile_commune', {
	name = 'Compile Matter';
	kind = 'suitPower', powerKind = 'direct';
	desc = "A basic suit matter compiler program. It's rather slow, but it's been ruthlessly optimized for size- and memory-efficiency by some of the Commune's most fanatic coders, to the point where every Commune nanosuit can come with the program preinstalled.";
	size = 700e3;
	cost = {
		cycles = 4e9;
		ram = .3e9;
	};
	ui = 'starlit:compile-matter-component';
	run = function(user, ctx)
	end;
})

starlit.item.sw.link('starlit_electronics:compile_block_commune', {
	name = 'Compile Block';
	kind = 'suitPower', powerKind = 'active';
	desc = "An advanced suit matter compiler program, capable of printing complete devices and structure parts directly into the world.";
	size = 5e6;
	cost = {
		cycles = 8e9;
		ram = 1e9;
	};
	ui = 'starlit:compile-matter-block';
	run = function(user, ctx)
	end;
})

starlit.item.sw.link('starlit_electronics:compile_imperial', {
	name = 'Genesis Deluxe';
	kind = 'suitPower', powerKind = 'direct';
	desc = "House Bascundir has long dominated the matter compiler market in the Crystal Sea. Their firmware is excessively complex due to mountains of specialized edge-case handling, but the end result is certainly speedier than the competitors'.";
	size = 2e4;
	cost = {
		cycles = 100e6;
		ram = 1.5e9;
	};
	ui = 'starlit:compile-matter-component';
	run = function(user, ctx)
	end;
})

do local J = starlit.store.compilerJob
	starlit.item.sw.link('starlit_electronics:driver_compiler_commune', {
		name = 'Matter Compiler';
		kind = 'driver';
		desc = "A driver for a standalone matter compiler, suitable for building larger components than your suit alone can handle.";
		size = 850e3;
		cost = {
			cycles = 400e6;
			ram = .2e9;
		};
		ui = 'starlit:device-compile-matter-component';
		run = function(user, ctx)
		end;
		bgProc = function(user, ctx, interval, runState)
			if runState.flags.compiled == true then return false end
			-- only so many nanides to go around

Modified mods/starlit-scenario/init.lua from [a141e7b70f] to [a805932fc9].

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
	social = {
		empire = 'workingClass';
		commune = 'metic';
	};

	startingItems = {
		suit = ItemStack('starlit_suit:suit_survival_commune');
		suitBatteries = {battery 'starlit_electronics:battery_carbon_commune_small'};
		suitChips = {
			chipLibrary.survivalware;
			-- you didn't notice it earlier, but your Commune environment suit
			-- came with this chip already plugged in. it's apparently true
			-- what they say: the Commune is always prepared for everything.
			-- E V E R Y T H I N G.
		};
................................................................................
		suitGuns = {};
		suitAmmo = {};
		suitCans = {
-- 			ItemStack('starlit_material:canister_small');
			volume('liquid', 'water', 5);
		};
		carry = {

			chipLibrary.compendium;
			-- you bought this on a whim before you left the Empire, and
			-- just happened to still have it on your person when everything
			-- went straight to the Wild Gods' privy
		};
	};
})







|







 







>







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
	social = {
		empire = 'workingClass';
		commune = 'metic';
	};

	startingItems = {
		suit = ItemStack('starlit_suit:suit_survival_commune');
		suitBatteries = {battery 'starlit_electronics:battery_carbon_commune_mid'};
		suitChips = {
			chipLibrary.survivalware;
			-- you didn't notice it earlier, but your Commune environment suit
			-- came with this chip already plugged in. it's apparently true
			-- what they say: the Commune is always prepared for everything.
			-- E V E R Y T H I N G.
		};
................................................................................
		suitGuns = {};
		suitAmmo = {};
		suitCans = {
-- 			ItemStack('starlit_material:canister_small');
			volume('liquid', 'water', 5);
		};
		carry = {
			battery 'starlit_electronics:battery_carbon_commune_small';
			chipLibrary.compendium;
			-- you bought this on a whim before you left the Empire, and
			-- just happened to still have it on your person when everything
			-- went straight to the Wild Gods' privy
		};
	};
})

Modified mods/starlit/element.lua from [25b10aa9d6] to [f0333e36f4].

3
4
5
6
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
..
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
..
90
91
92
93
94
95
96

97
98
99
100
101
102
103
...
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
...
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
local M = W.material

M.element.foreach('starlit:sort', {}, function(id, m)
	if m.metal then
		M.metal.link(id, {
			name = m.name;
			composition = starlit.type.fab{element = {[id] = 1}};
			color = m.color;
			-- n.b. this is a RATIO: it will be appropriately multiplied
			-- for the object in question; e.g a normal chunk will be

			-- 100 $element, an ingot will be 1000 $element
		})
	elseif m.gas then
		M.gas.link(id, {
			name = m.name;
			composition = starlit.type.fab{element = {[id] = 1}};
			density = m.density;

		})
	elseif m.liquid then
		M.liquid.link(id, {
			name = m.name;
			composition = starlit.type.fab{element = {[id] = 1}};
			density = m.density;

		})
	end
end)

local F = string.format

local function mkEltIndicator(composition)
................................................................................
			indsz, indsz,
			indicator);
	end
end

M.element.foreach('starlit:gen-forms', {}, function(id, m)
	local eltID = F('%s:element_%s', minetest.get_current_modname(), id)
	local eltName = F('Elemental %s', lib.str.capitalize(m.name))
	local tt = function(t, d, g)
		return starlit.ui.tooltip {
			title = t, desc = d;
			color = lib.color(0.1,0.2,0.1);
			props = {
				{title = 'Mass', desc = lib.math.si('g', g), affinity='info'}
			}
................................................................................
			_starlit = {
				mass = 1;
				material = {
					kind = 'element';
					element = id;
				};
				fab = starlit.type.fab {

					element = comp;
				};
			};
		});
	end

	--[[
................................................................................
		};
	});
	]]
end)


M.metal.foreach('starlit:gen-forms', {}, function(id, m)



	local baseID = F('%s:metal_%s_', minetest.get_current_modname(), id)
	local ingotID = baseID .. 'ingot'
	local ingotName = F('%s Ingot', lib.str.capitalize(m.name))
	m.form = m.form or {}
	m.form.ingot = ingotID

	local tt = function(t, d, g)
		return starlit.ui.tooltip {
			title = t, desc = d;
			color = lib.color(0.1,0.1,0.1);
			props = {
				{title = 'Mass', desc = lib.math.si('g', g), affinity='info'}
			}
		}
	end
	local mcomp = m.composition:elementalize().element
	local function comp(n)
		local t = {}
		for id, amt in pairs(mcomp) do
			t[id] = amt * n
		end
		return t
	end
	local iblit = mkEltIndicator(mcomp)
	local function img(s)
		return iblit(s:colorize(m.color):render())
	end


	minetest.register_craftitem(ingotID, {
		short_description = ingotName;
		description = tt(ingotName, F('A solid ingot of %s, ready to be worked by a large matter compiler', m.name), 1e3);

		inventory_image = img(lib.image('starlit-item-ingot.png'));
		wield_image = lib.image 'starlit-item-ingot.png':colorize(m.color):render();
		groups = {metal = 1, ingot = 1};
		stack_max = 5;
		_starlit = {
			mass = 1e3;
			material = {
				kind = 'metal';
				metal = id;
			};
			fab = starlit.type.fab {
				flag = {smelt=true};
				element = comp(1e3);
			};
		};
	});



end)

local canisterMeta = lib.marshal.metaStore {
	contents = {key = 'starlit:canister_contents', type = starlit.store.volume};
}

................................................................................
		short_description = c.name;
		description = canisterDesc(nil, c);
		inventory_image = c.image or 'starlit-item-element-canister.png';
		groups = {canister = 1};
		stack_max = 1;
		_starlit = {
			canister = c;
			container = {
				handle = function(stack, oldstack)
					stack:get_meta():set_string('description', canisterDesc(stack))
					return stack
				end;
				list = {
					elem = {
						key = 'starlit:canister_elem';
						accept = 'powder';
						sz = c.slots;
					};
				};
			};
		};
	})
end)

function starlit.item.canister.contents(st)
	local m = canisterMeta(st)
	return m.read 'contents'







<

|
>
|






>






>







 







|







 







>







 







>
>
>
|
|
|
|
<
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

>
|
|
<
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
>







 







<
<
<
<
<
<
<
<
<
<
<
<
<







3
4
5
6
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
..
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
..
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
...
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
...
234
235
236
237
238
239
240













241
242
243
244
245
246
247
local M = W.material

M.element.foreach('starlit:sort', {}, function(id, m)
	if m.metal then
		M.metal.link(id, {
			name = m.name;
			composition = starlit.type.fab{element = {[id] = 1}};

			-- n.b. this is a RATIO: it will be appropriately multiplied
			-- for the object in question
			color = m.color;
			elemental = m.name;
		})
	elseif m.gas then
		M.gas.link(id, {
			name = m.name;
			composition = starlit.type.fab{element = {[id] = 1}};
			density = m.density;
			elemental = m.name;
		})
	elseif m.liquid then
		M.liquid.link(id, {
			name = m.name;
			composition = starlit.type.fab{element = {[id] = 1}};
			density = m.density;
			elemental = m.name;
		})
	end
end)

local F = string.format

local function mkEltIndicator(composition)
................................................................................
			indsz, indsz,
			indicator);
	end
end

M.element.foreach('starlit:gen-forms', {}, function(id, m)
	local eltID = F('%s:element_%s', minetest.get_current_modname(), id)
-- 	local eltName = F('Elemental %s', lib.str.capitalize(m.name))
	local tt = function(t, d, g)
		return starlit.ui.tooltip {
			title = t, desc = d;
			color = lib.color(0.1,0.2,0.1);
			props = {
				{title = 'Mass', desc = lib.math.si('g', g), affinity='info'}
			}
................................................................................
			_starlit = {
				mass = 1;
				material = {
					kind = 'element';
					element = id;
				};
				fab = starlit.type.fab {
					flag = {smelt = m.metal and true or nil};
					element = comp;
				};
			};
		});
	end

	--[[
................................................................................
		};
	});
	]]
end)


M.metal.foreach('starlit:gen-forms', {}, function(id, m)
	if m.elemental then -- avoid multiple forms for same material
		m.form = M.element.db[m.elemental].form;
	else
		local baseID = F('%s:metal_%s_', minetest.get_current_modname(), id)
		local brickID = baseID .. 'brick'
		local brickName = F('%s Brick', lib.str.capitalize(m.name))
		m.form = m.form or {}

		m.form.brick = brickID
		local tt = function(t, d, g)
			return starlit.ui.tooltip {
				title = t, desc = d;
				color = lib.color(0.1,0.1,0.1);
				props = {
					{title = 'Mass', desc = lib.math.si('g', g), affinity='info'}
				}
			}
		end
		local mcomp = m.composition:elementalize().element
		local function comp(n)
			local t = {}
			for id, amt in pairs(mcomp) do
				t[id] = amt * n
			end
			return t
		end
		local iblit = mkEltIndicator(mcomp)
		local function img(s)
			return iblit(s:colorize(m.color):render())
		end

		local mass = 1
		minetest.register_craftitem(brickID, {
			short_description = brickName;

			description = tt(brickName, F('A small brick of %s, ready to be worked by a matter compiler', m.name), mass);
			inventory_image = img(lib.image('starlit-item-brick.png'));
			wield_image = lib.image 'starlit-item-brick.png':colorize(m.color):render();
			groups = {metal = 1, brick = 1};
			stack_max = 500;
			_starlit = {
				mass = mass;
				material = {
					kind = 'metal';
					metal = id;
				};
				fab = starlit.type.fab {
					flag = {smelt=true};
					element = comp(1e3);
				};
			};
		});

	end

end)

local canisterMeta = lib.marshal.metaStore {
	contents = {key = 'starlit:canister_contents', type = starlit.store.volume};
}

................................................................................
		short_description = c.name;
		description = canisterDesc(nil, c);
		inventory_image = c.image or 'starlit-item-element-canister.png';
		groups = {canister = 1};
		stack_max = 1;
		_starlit = {
			canister = c;













		};
	})
end)

function starlit.item.canister.contents(st)
	local m = canisterMeta(st)
	return m.read 'contents'

Modified mods/starlit/fab.lua from [bdd1907d1f] to [a2443e45a4].

12
13
14
15
16
17
18


19
20
21
22
23
24
25
..
29
30
31
32
33
34
35





















































36
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
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
...
204
205
206
207
208
209
210

































































211
212
213
214
215
216
217
--    * used for determining quantities. that is,
--			f*x = spec to make x instances of f
--
--    new fab fields must be defined in starlit.type.fab.fields.
--    this maps a name to fn(a,b,n) -> quant, where a is the first
--    argument, b is a compounding amount, and n is a quantity of
--    items to produce. fields that are unnamed will be underwritten



local function fQuant(a,b,n) return ((a or 0)+(b or 0))*n end
local function fFac  (a,b,n)
	if a == nil and b == nil then return nil end
	local f if a == nil or b == nil then
		f = a or b
	else
................................................................................
end
local function fReq  (a,b,n) return a or b         end
local function fFlag (a,b,n) return a and b        end
local function fSize (a,b,n) return math.max(a,b)  end

local F = string.format
local lib = starlit.mod.lib






















































local fields = {
	-- fabrication eligibility will be determined by which kinds
	-- of input a particular fabricator can introduce. e.g. a
	-- printer with a  but no cache can only print items whose
	-- recipe only names elements as ingredients
	element = {
		name = {"element", "elements"};
		string = function(x, n, long)
			local el = starlit.world.material.element.db[x]
			return lib.math.si('g', n) .. ' ' .. ((not long and el.sym) or el.name)
		end;
		image = function(x, n)
			return string.format('starlit-element-%s.png', x)
		end;

		op = fQuant;
	};
	metal ={
		name = {"metal", "metals"};
		string = function(x, n)
			local met = starlit.world.material.metal.db[x]
			return lib.math.si('g', n) .. ' ' .. met.name
		end;
		image = function(x, n)
			local met = starlit.world.material.metal.db[x]
			return ItemStack(met.form.ingot):get_definition().inventory_image
		end;

		op = fQuant;
	};
	liquid = {
		name = {"liquid", "liquids"};
		string = function(x, n)
			local liq = starlit.world.material.liquid.db[x]
			return lib.math.si('L', n) .. ' ' .. liq.name
		end;

		op = fQuant;
	};
	gas = {
		name = {"gas", "gasses"};
		string = function(x, n)
			local gas = starlit.world.material.gas.db[x]
			return lib.math.si('g', n) .. ' ' .. gas.name
		end;

		op = fQuant;
	};
-- 	crystal = {
-- 		op = fQuant;
-- 	};
	item = {
		name = {"item", "items"};
		string = function(x, n)
			local i = minetest.registered_items[x]
			return tostring(n) .. 'x ' .. i.short_description
		end;









	};

	-- factors



	cost = {op=fFac}; -- units vary






















	time = {op=fFac}; -- (s)
		-- print: base printing time
	size = {op=fSize};
		-- printBay: size of the printer bay necessary to produce the item
	req  = {op=fReq};
	flag = {op=fFlag}; -- means that can be used to produce the item & misc flags
		-- print: allow production with a printer
		-- smelt: allow production with a smelter
	-- all else defaults to underwrite
}

local order = {
	'element', 'metal', 'liquid', 'gas', 'item'

}

local lib = starlit.mod.lib

local fab fab = lib.class {
	__name = 'starlit:fab';
	
	fields = fields;
	order = order;
	construct = function(q) return q end;
	__index = {
		elementalize = function(self)
			local e = fab {element = self.element or {}}
			for _, kind in pairs {'metal', 'gas', 'liquid'} do
				for m,mass in pairs(self[kind] or {}) do
					local mc = starlit.world.material[kind][m].composition
					e = e + mc:elementalize()*mass
				end
			end
			return e
		end;

		elementSeq = function(self)
................................................................................
				if next(t) then table.insert(all, {
					id=o, list=t;
					header=fields[o].name[t[2] and 2 or 1];
				}) end
			end
			return all
		end;

































































	};

	__tostring = function(self)
		local t = {}
		for i,o in ipairs(order) do
			if self[o] and fields[o].string then
				for mat,amt in pairs(self[o]) do







>
>







 







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










|




>






|



|

>






|

>






|

>











>
>
>
>
>
>
>
>
>




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












|
>




|










|







 







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







12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
..
31
32
33
34
35
36
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
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
...
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
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
362
363
364
365
366
367
368
369
370
371
372
373
374
375
--    * used for determining quantities. that is,
--			f*x = spec to make x instances of f
--
--    new fab fields must be defined in starlit.type.fab.fields.
--    this maps a name to fn(a,b,n) -> quant, where a is the first
--    argument, b is a compounding amount, and n is a quantity of
--    items to produce. fields that are unnamed will be underwritten

local fab

local function fQuant(a,b,n) return ((a or 0)+(b or 0))*n end
local function fFac  (a,b,n)
	if a == nil and b == nil then return nil end
	local f if a == nil or b == nil then
		f = a or b
	else
................................................................................
end
local function fReq  (a,b,n) return a or b         end
local function fFlag (a,b,n) return a and b        end
local function fSize (a,b,n) return math.max(a,b)  end

local F = string.format
local lib = starlit.mod.lib

local function fRawMat(class)
	return function(x,n,stack)
		local def = stack:get_definition()._starlit
		if not def.material then return 0 end
		local mf = fab {[def.material.kind] = {[def.material[def.material.kind]] = def.mass}}

--		this is bugged: the same item can satisfy both e.g. metal.steel and element.fe
-- 		if not (mf[class] and mf[class][x]) then
-- 			mf = mf:elementalize()
			if not (mf[class] and mf[class][x]) then return 0 end
-- 		end

		local perItem = mf[class][x]
		local wholeStack = perItem * stack:get_count()

		local deduct = ItemStack()
		local taken = 0 repeat
			taken = taken + perItem
			deduct:add_item(stack:take_item(1))
		until taken >= n or stack:is_empty()
		return taken, deduct

		--[[  outsmarted myself with this one :/
		local fab = def.recover or def.fab
		-- we ignore recover_vary bc this needs to be deterministic
		local function tryFab(fab)
			if not fab then return 0 end
			if fab[class] and fab[class][x] then
				local perItem = fab[class][x]
				local wholeStack = perItem * stack:get_count()
				print('fab has substance', n, perItem, wholeStack)
				local deduct = ItemStack()
				local taken = 0 repeat
					taken = taken + perItem
					deduct:add_item(stack:take_item(1))
				until taken >= n
				return taken, deduct
			end
			return 0
		end
		local z,c = tryFab(fab)
		if z == 0 then -- does it work if we break down the constituent compounds?
			z,c = tryFab(fab:elementalize())
		end]]
	end
end
local function fCanister(class)
	return function(x, n, stack)
		local amt, deduct = 0
		return amt, deduct
	end
end

local fields = {
	-- fabrication eligibility will be determined by which kinds
	-- of input a particular fabricator can introduce. e.g. a
	-- printer with a  but no cache can only print items whose
	-- recipe only names elements as ingredients
	element = {
		name = {"element", "elements"};
		string = function(x, n, long)
			local el = starlit.world.material.element.db[x]
			return lib.math.siUI('g', n) .. ' ' .. ((not long and el.sym) or el.name)
		end;
		image = function(x, n)
			return string.format('starlit-element-%s.png', x)
		end;
		inventory = fRawMat 'element';
		op = fQuant;
	};
	metal ={
		name = {"metal", "metals"};
		string = function(x, n)
			local met = starlit.world.material.metal.db[x]
			return lib.math.siUI('g', n) .. ' ' .. met.name
		end;
		image = function(x, n)
			local met = starlit.world.material.metal.db[x]
			return ItemStack(met.form.brick):get_definition().inventory_image
		end;
		inventory = fRawMat 'metal';
		op = fQuant;
	};
	liquid = {
		name = {"liquid", "liquids"};
		string = function(x, n)
			local liq = starlit.world.material.liquid.db[x]
			return lib.math.siUI('L', n) .. ' ' .. liq.name
		end;
		inventory = fCanister 'liquid';
		op = fQuant;
	};
	gas = {
		name = {"gas", "gasses"};
		string = function(x, n)
			local gas = starlit.world.material.gas.db[x]
			return lib.math.siUI('g', n) .. ' ' .. gas.name
		end;
		inventory = fCanister 'gas';
		op = fQuant;
	};
-- 	crystal = {
-- 		op = fQuant;
-- 	};
	item = {
		name = {"item", "items"};
		string = function(x, n)
			local i = minetest.registered_items[x]
			return tostring(n) .. 'x ' .. i.short_description
		end;
		image = function(x, n)
			return ItemStack(x):get_definition().inventory_image
		end;
		inventory = function(x, n, stack)
			x = ItemStack(x)
			if not x:equals(stack) then return nil end
			local deduct = stack:take_item(x:get_count() * n)
			return deduct:get_count(), deduct
		end;
	};

	-- factors

	cost = {
		name = {"cost", "costs"};
		op=fFac; -- units vary
		string = function(x,n)
			local units = {
				power = 'J';
			}
			local s
			if units[x] then
				s = lib.math.siUI(units[x], n)
			elseif starlit.world.stats[x] then
				s = starlit.world.stats[x].desc(n)
			else
				s = tostring(n)
			end
			return string.format('%s: %s',x,s)
		end;
		image = function(x,n)
			local icons = {
				power = 'starlit-ui-icon-stat-power.png';
				numina = 'starlit-ui-icon-stat-numina.png'
			}
			return icons[x]
		end;
	};
	time = {op=fFac}; -- (s)
		-- print: base printing time
	size = {op=fSize};
		-- printBay: size of the printer bay necessary to produce the item
	req  = {op=fReq};
	flag = {op=fFlag}; -- means that can be used to produce the item & misc flags
		-- print: allow production with a printer
		-- smelt: allow production with a smelter
	-- all else defaults to underwrite
}

local order = {
	'element', 'metal', 'liquid', 'gas', 'item',
	'cost'
}

local lib = starlit.mod.lib

fab = lib.class {
	__name = 'starlit:fab';
	
	fields = fields;
	order = order;
	construct = function(q) return q end;
	__index = {
		elementalize = function(self)
			local e = fab {element = self.element or {}}
			for _, kind in pairs {'metal', 'gas', 'liquid'} do
				for m,mass in pairs(self[kind] or {}) do
					local mc = starlit.world.material[kind].db[m].composition
					e = e + mc:elementalize()*mass
				end
			end
			return e
		end;

		elementSeq = function(self)
................................................................................
				if next(t) then table.insert(all, {
					id=o, list=t;
					header=fields[o].name[t[2] and 2 or 1];
				}) end
			end
			return all
		end;
		seek = function(self, invs)
			local consumed = {}
			local spec = fab{item={}} -- used to generate a convenient visualization
			local unsatisfied = fab{}
			local cache = {}
			local leftover = fab{}
			local function alreadyGot(inv,slot)
				local already = cache[inv] and cache[inv][slot] and true
				if cache[inv] == nil then cache[inv] = {} end
				cache[inv][slot] = true
				return already
			end
			for ci, cat in ipairs(order) do
				local scan = fields[cat].inventory
				if scan and self[cat] then
					for substance, amt in pairs(self[cat]) do
-- 						print('check substance', substance, amt, dump(self[cat]))
						local amtFound = 0
						local stacks = {}
						for ii, inv in ipairs(invs) do
-- 							print('  - check inventory',ii,inv,'for',cat,substance,amt)
							for oi, o in ipairs(inv) do
-- 								print('    - check stack', oi, o)
								local st = ItemStack(o)
								if not st:is_empty() then
									local avail, deduct = scan(substance,amt,st)
									if avail > 0 then
										amtFound = amtFound + avail
-- 										print('       - found amt', amtFound,ii,oi)
										if not alreadyGot(ii,oi) then
											local sv = {
												inv=ii, slot=oi;
												consume=deduct, remain=st;
												satisfy=fab{[cat]={[substance]=avail}}
											}
											table.insert(stacks, sv)
										end
										if amtFound >= amt then goto suffice end
									end
								end
							end
						end

						::insufficient:: do -- record the failure and move on
							if unsatisfied[cat] == nil then unsatisfied[cat] = {} end
							unsatisfied[cat][substance] = amt-amtFound
						end

						::suffice:: -- commit the stack diff
						for si,sv in ipairs(stacks) do
-- 							table.insert(consumed, sv)
							local di = ItemStack(sv.consume)
							local din = ItemStack(sv.consume):get_name()
							if not spec.item[din] then spec.item[din] = 0 end
							spec.item[din] = spec.item[din] + di:get_count()
							local lo = amtFound-amt if lo > 0 then
								leftover = leftover + fab{[cat]={[substance]=lo}}
							end
						end

					end
				end
			end
			return (next(unsatisfied) == nil), consumed, unsatisfied, leftover, spec
		end;
	};

	__tostring = function(self)
		local t = {}
		for i,o in ipairs(order) do
			if self[o] and fields[o].string then
				for mat,amt in pairs(self[o]) do

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

32
33
34
35
36
37
38





39
40
41
42
43
44
45
...
272
273
274
275
276
277
278
279

280
281
282
283
284
285
286
			safe = 4;
			overheat = 32;
			boiling = 100;
			thermalConductivity = 0.05; -- κ
		};
		rad = {
		};





	};

	activeUsers = {
		-- map of username -> user object
	};
	activeUI = {
		-- map of username -> UI context
................................................................................

starlit.include 'element'

starlit.include 'terrain'
starlit.include 'interfaces'
starlit.include 'suit'

minetest.settings:set('movement_gravity', starlit.world.planet.gravity) -- ??? seriously???


---------------
-- callbacks --
---------------
-- here we connect our types up to the minetest API

local function userCB(fn)







>
>
>
>
>







 







|
>







32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
...
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
			safe = 4;
			overheat = 32;
			boiling = 100;
			thermalConductivity = 0.05; -- κ
		};
		rad = {
		};

		phys = {
			--- HACK HACK HAAAAAAAAAAACK
			engineGravity = minetest.settings:get('movement_gravity') or 9.81
		};
	};

	activeUsers = {
		-- map of username -> user object
	};
	activeUI = {
		-- map of username -> UI context
................................................................................

starlit.include 'element'

starlit.include 'terrain'
starlit.include 'interfaces'
starlit.include 'suit'

-- minetest.settings:set('movement_gravity', starlit.world.planet.gravity) -- ??? seriously???
-- THIS OVERRIDES THE GLOBAL SETTING *AND PERSISTS IT* WHAT IN THE SATANIC FUCK

---------------
-- callbacks --
---------------
-- here we connect our types up to the minetest API

local function userCB(fn)

Modified mods/starlit/interfaces.lua from [e3ed80cb5c] to [c1a1690c3b].

168
169
170
171
172
173
174









175
176
177
178
179
180
181
...
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
...
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
...
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
...
458
459
460
461
462
463
464



























465
466
467
468
469
470
471
...
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496

497
498




499



500
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




539
540
541



542
543










544


545
546
547
548














549
550



551
552
553
554
555


556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
...
591
592
593
594
595
596
597






598
599
600
601
602
603
604
605
606
607
608

609
610
611



612
613
614
615
616
617
618
619
620
621
622
623



























624
625
626
627
628
629
630
631
632
633







634
635
636
637




638
639






640
641
642











643














644
645
646
647
648
649
650
651
652
653
654
655
					end
				end
				if not pgm then return false end -- HAX

				-- kind=active programs must be assigned to a command slot
				-- kind=direct programs must open their UI
				-- kind=passive programs must toggle on and off









				if pgm.sw.powerKind == 'active' then
					if cfg then
						user:openUI(pgm.sw.ui, 'index', {
							context = 'suit';
							program = pgm;
						})
						return false
................................................................................
					elseif pptrMatch(ptr, pnan.secondary) then
						pnan.secondary = nil
					else
						pnan.secondary = ptr
					end
					user:suitSound 'starlit-configure'
				elseif pgm.sw.powerKind == 'direct' then
					local ctx = {
						context = 'suit';
						program = pgm;
					}
					if pgm.sw.ui then
						user:openUI(pgm.sw.ui, 'index', ctx)
						return false
					else
						pgm.sw.run(user, ctx)
					end
				elseif pgm.sw.powerKind == 'passive' then
					if cfg then
						user:openUI(pgm.sw.ui, 'index', {
							context = 'suit';
							program = pgm;
						})
						return false
					end

					local addDisableRec = true
					for i, e in ipairs(pgm.file.body.conf) do
						if e.key == 'disable' and e.value == 'yes' then
							addDisableRec = false
................................................................................
						w=2, h=2;
					})
					menu.padding = 1;
				end
				return starlit.ui.build(menu)
			end;
		};
		compilerListRecipes = {
		};
		psi = {
			render = function(state, user)
				return starlit.ui.build {
					kind = 'vert', mode = 'sw';
					padding = 0.5;
				}
			end;
................................................................................
				local tb = {
					kind = 'vert', mode = 'sw';
					padding = 0.5, 
					{kind = 'hztl', padding = 0.25;
						{kind = 'label', text = 'Name', w = 2, h = barh};
						{kind = 'label', text = user.persona.name, w = 4, h = barh}};
				}
				local statBars = {'nutrition', 'hydration', 'fatigue', 'morale', 'irradiation', 'illness'}
				for idx, id in ipairs(statBars) do
					local s = starlit.world.stats[id]
					local amt, sv = user:effectiveStat(id)
					local min, max = starlit.world.species.statRange(user.persona.species, user.persona.speciesVariant, id)

















					local st = string.format('%s / %s', s.desc(amt, true), s.desc(max))
					table.insert(tb, {kind = 'hztl', padding = 0.25;
						{kind = 'label', w=2, h=barh, text = lib.str.capitalize(s.name)};
						{kind = 'hbar',  w=4, h=barh, fac = sv, text = st, color=s.color};
					})
				end























				local abilities = {
					maneuver = {};
					direct = {};
					passive = {};
				}
				state.abilityMap = {}
				for i, a in pairs(user:species().abilities) do
................................................................................
					user:suitPowerStateSet(suitMode)
					return true
				end
			end;
		};
	};
})




























-- TODO destroy suit interfaces when power runs out or suit/chip is otherwise disabled
starlit.interface.install(starlit.type.ui {
	id = 'starlit:compile-matter-component';
	sub = {
		suit = function(state, user, evt)
			if evt.kind == 'disrobe' then state:close()
................................................................................
			setupState = function(state, user, ctx)
				state.pgm = ctx.program
				state.select = {}
				local E = starlit.mod.electronics
				if ctx.context == 'suit' then
					state.fetch = function()
						local cst = user.entity:get_inventory():get_list 'starlit_suit_chips'
						local cl = {order={}, map={}}
						for i, c in ipairs(cst) do
							if not c:is_empty() then
								local d = E.chip.read(c)
								local co = {
									stack = c;
									data = d;
								}
								table.insert(cl.order, co)
								cl.map[d.uuid] = co

							end
						end




						if state.select.chip and not cl.map[state.select.chip.data.uuid] then



							-- chip no longer available
							user:suitSound 'starlit-error'
							state.select = {}
						end
						state.select.chips = cl

						state.select.scms = {}
						if state.select.chip then
							state.select.scms = E.chip.usableSoftware({state.select.chip.stack},nil,





								function(s) return s.sw.kind == 'schematic' end)
						end


					end
				end
			end;

			onClose = function(state, user)
				user:suitSound 'starlit-quit'
			end;
................................................................................
			handle = function(state, user, q)
				local sel = state.select
				state.fetch()
				local chips = state.select.chips
				local function chirp()
					user:suitSound 'starlit-nav'
				end

				local function onPickChip(chip)







					chirp()
					sel.chip = chip

					return true
				end
				local function onPickScm(scm)
					chirp()
					sel.scm = scm
					return true
				end




				if sel.chip == nil then
					for k in next, q do
						local id = k:match "^chip_(%d+)$"




						if id then
							local cm = chips.map[tonumber(id)]
							if cm then return onPickChip(cm) end



						end
					end










				elseif sel.scm == nil then


					if q.back then chirp() sel.chip = nil return true end
					for k in next, q do
						local id = k:match "^scm_(%d+)$"
						if id then














							local cm = state.select.scms[tonumber(id)]
							if cm then return onPickScm(cm) end



						end
					end
				else
					if q.back then chirp() sel.scm = nil return true end
				end


			end;

			render = function(state, user)
				local sel, pgmSelector = state.select, {}
				state.fetch()

				local function pushSelector(id, item, label, desc, req)
					local rh = .5
					local label = {kind = 'text', w = 10-1.5, h=1.5;
							text = '<global valign=middle>'..label }
					if req then
						label.h = label.h - rh - .2

						local imgs = {}
						for ci,c in ipairs(req) do
							for ei, e in ipairs(c.list) do
								table.insert(imgs, {kind = 'img', w=rh, h=rh,  img=e.img})
................................................................................
				if sel.chips == nil then
					table.insert(pgmSelector, {kind = 'img', img = 'starlit-ui-alert.png', w=2, h=2})
				elseif sel.chip == nil then
					for i, c in ipairs(sel.chips.order) do
					-- TODO filter out chips without schematics?
						pushSelector('chip_' .. c.data.uuid, c.stack, c.data.label)
					end






				else
					if sel.scm == nil then
						for idx, ent in ipairs(sel.scms) do
							local fab = ItemStack(ent.sw.output):get_definition()._starlit.fab
							if fab.flag.print then
								local req = fab:visualize()
								pushSelector('scm_' .. idx, ent.sw.output, ent.sw.name, nil, req)
							end
						end
						table.insert(pgmSelector, back)
					else

						local output = ItemStack(sel.scm.sw.output):get_definition()
						local fab = output._starlit.fab
						local sw = sel.scm.sw



						table.insert(pgmSelector, {kind = 'hztl', w=10, h=1.2;
							{kind = 'img', item = sw.output, w=1.2, h=1.2, desc=output.description};
							{kind = 'text', text = string.format('<global valign=middle><b>%s</b>', sw.name), w=10-1.2,h=1.2};
						})
						local inputTbl = {kind = 'vert', w=5,h=0;
							{kind = 'hbar', w=5, h=.5, text='Input'}};
						local costTbl = {kind = 'vert', w=5,h=0; spacing=.25;
							{kind = 'hbar', w=5, h=.5, text='Process'}};
						local reqPane = {kind = 'pane', id='reqPane', w=10, h=7;
							{kind = 'hztl', w=10,h=0; inputTbl, costTbl}
						}
						local req = fab:visualize()



























						for ci,c in ipairs(req) do
							table.insert(inputTbl, {kind = 'label', w=4.5, h=1, x=.5;
								text=lib.str.capitalize(c.header)});
							for ei,e in ipairs(c.list) do
								table.insert(inputTbl, {kind = 'hztl', w=4, h=.5, x=1;
									{kind='img',   w=.5,h=.5, img=e.img};
									{kind='label', w=3.3,h=.5,x=.2, text=e.label};
								});
							end
						end







						if sw.cost then
							local function pushCost(t, val)
								table.insert(costTbl, {kind='text', w=4.5,h=.5,x=.5;
									text=string.format('<b>%s</b>: %s',t,val);




								})
							end






							if sw.cost.cycles then
								pushCost('Energy', lib.math.siUI('J', sel.scm.powerCost))
								pushCost('Compute', lib.math.siUI({'cycle','cycles'}, sw.cost.cycles, true))











							end














						end
						table.insert(pgmSelector, reqPane)
						table.insert(pgmSelector, {kind = 'hztl', w=10,h=1.2;
							{kind = 'button', id='back', label = '<- Back', w=5,h=1.2};
							{kind = 'button', id='print', label = 'Print ->', w=5,h=1.2, color={hue=120,sat=0,lum=0}};
						})
					end
				end

				return starlit.ui.build {
					kind = 'hztl', padding = 0.5; w = 20, h = 10, mode = 'sw';
					{kind = 'vert', w = 5, h = 5;







>
>
>
>
>
>
>
>
>







 







|
<
<
<








|
<
<
<







 







<
<







 







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

|




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







 







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







 







|









>


>
>
>
>
|
>
>
>
|







|
>
>
>
>
>
|
|
>
>







 







>
|
>
>
>
>
>
>
>
|
<
>
|
|
<
<
<
<
|
>
>
>


<
<
>
>
>
>
|
<
<
>
>
>
|
|
>
>
>
>
>
>
>
>
>
>
|
>
>
|
<
<
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
<
>
>
>
|
|
<
<
|
>
>









|







 







>
>
>
>
>
>











>
|

|
>
>
>


|


|
|
|



<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
|
<
<
<
>
>
>
>
|
|
>
>
>
>
>
>
|
<
<
>
>
>
>
>
>
>
>
>
>
>

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



|
|







168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
...
198
199
200
201
202
203
204
205



206
207
208
209
210
211
212
213
214



215
216
217
218
219
220
221
...
329
330
331
332
333
334
335


336
337
338
339
340
341
342
...
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
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
...
499
500
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
539
...
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
...
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617

618
619
620




621
622
623
624
625
626


627
628
629
630
631


632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650



651
652
653
654
655
656
657
658
659
660
661
662
663
664
665

666
667
668
669
670


671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
...
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750

751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795



796
797
798
799
800
801
802
803
804
805
806
807
808


809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
					end
				end
				if not pgm then return false end -- HAX

				-- kind=active programs must be assigned to a command slot
				-- kind=direct programs must open their UI
				-- kind=passive programs must toggle on and off
				local function suitCtx(pgm)
					local chips = user.entity:get_inventory():get_list 'starlit_suit_chips'
					local pgmctx = starlit.mod.electronics.chip.usableSoftware(chips, {pgm})[1]
					return {
						context = 'suit';
						program = pgmctx;
					}
				end

				if pgm.sw.powerKind == 'active' then
					if cfg then
						user:openUI(pgm.sw.ui, 'index', {
							context = 'suit';
							program = pgm;
						})
						return false
................................................................................
					elseif pptrMatch(ptr, pnan.secondary) then
						pnan.secondary = nil
					else
						pnan.secondary = ptr
					end
					user:suitSound 'starlit-configure'
				elseif pgm.sw.powerKind == 'direct' then
					local ctx = suitCtx(pgm)



					if pgm.sw.ui then
						user:openUI(pgm.sw.ui, 'index', ctx)
						return false
					else
						pgm.sw.run(user, ctx)
					end
				elseif pgm.sw.powerKind == 'passive' then
					if cfg then
						user:openUI(pgm.sw.ui, 'index', suitCtx(pgm))



						return false
					end

					local addDisableRec = true
					for i, e in ipairs(pgm.file.body.conf) do
						if e.key == 'disable' and e.value == 'yes' then
							addDisableRec = false
................................................................................
						w=2, h=2;
					})
					menu.padding = 1;
				end
				return starlit.ui.build(menu)
			end;
		};


		psi = {
			render = function(state, user)
				return starlit.ui.build {
					kind = 'vert', mode = 'sw';
					padding = 0.5;
				}
			end;
................................................................................
				local tb = {
					kind = 'vert', mode = 'sw';
					padding = 0.5, 
					{kind = 'hztl', padding = 0.25;
						{kind = 'label', text = 'Name', w = 2, h = barh};
						{kind = 'label', text = user.persona.name, w = 4, h = barh}};
				}
				local statBars = {'stamina', 'numina', 'nutrition', 'hydration', 'fatigue', 'morale', 'irradiation', 'illness'}
				local function wrapElts(n, l)
					local all = {kind='vert'}
					local ct, row
					local function flush()
						if row then
							table.insert(all, row)
						end
						row = {kind='hztl', spacing = 0.2}
						ct = 0
					end
					flush()
					for i, e in ipairs(l) do
						ct = ct + 1
						table.insert(row, e)
						if ct >= n then flush() end
					end
					flush()
					return all
				end
				local bars = {}
				local function pushBar(s, amt, sv, min, max)
					local st = string.format('%s / %s', s.desc(amt, true), s.desc(max))
					table.insert(bars, {kind = 'hztl', padding = 0.25;
						{kind = 'label', w=2, h=barh, text = lib.str.capitalize(s.name)};
						{kind = 'hbar',  w=4, h=barh, fac = sv, text = st, color=s.color};
					})
				end
				do local hp, hpf = user:effectiveStat 'health'
					local desc = {
						name='health';
						desc = function(hp) return tostring(hp) end;
						color = {hue=10,sat=1,lum=.5};
					}
					pushBar(desc, hp, hpf, starlit.world.species.statRange(user.persona.species, user.persona.speciesVariant, 'health'))
				end
				do local ep, ex = user:suitCharge(), user:suitPowerCapacity()
					local desc = {
						name = 'power';
						desc = function(j) return lib.math.siUI('J', j) end;
						color = {hue=190,sat=1,lum=.5};
					}
					pushBar(desc, ep, ep/ex, 0, ex)
				end
				for idx, id in ipairs(statBars) do
					local s = starlit.world.stats[id]
					local amt, sv = user:effectiveStat(id)
					local min, max = starlit.world.species.statRange(user.persona.species, user.persona.speciesVariant, id)
					pushBar(s, amt, sv, min, max)
				end
				table.insert(tb, wrapElts(2, bars))
				local abilities = {
					maneuver = {};
					direct = {};
					passive = {};
				}
				state.abilityMap = {}
				for i, a in pairs(user:species().abilities) do
................................................................................
					user:suitPowerStateSet(suitMode)
					return true
				end
			end;
		};
	};
})

local function compilerCanPrint(user, cpl, scm)
	local output = ItemStack(scm.sw.output):get_definition()
	local fab = output._starlit.fab
	local sw = scm.sw
	local ok, consume, unsat, leftover, itemSpec = fab:seek {
		user.entity:get_inventory():get_list 'main';
	}

	local cost = {
		consume = consume, unsat = unsat, leftover = leftover, itemSpec = itemSpec;
		runtimeEstimate = scm.speed + cpl.speed + (fab.time and fab.time.print or 0);
		power = cpl.powerCost + scm.powerCost;
		ram = (cpl.cost and cpl.cost.ram or 0)
		    + (scm.cost and scm.cost.ram or 0);
		cycles = (cpl.cost and cpl.cost.cycles or 0)
		       + (scm.cost and scm.cost.cycles or 0);
	}

	local userComp = starlit.mod.electronics.chip.sumCompute(
		user.entity:get_inventory():get_list 'starlit_suit_chips'
	)

	if ok and cost.power <= user:suitCharge() and cost.ram <= userComp.ram then
		return true, cost
	else return false, cost end
end

-- TODO destroy suit interfaces when power runs out or suit/chip is otherwise disabled
starlit.interface.install(starlit.type.ui {
	id = 'starlit:compile-matter-component';
	sub = {
		suit = function(state, user, evt)
			if evt.kind == 'disrobe' then state:close()
................................................................................
			setupState = function(state, user, ctx)
				state.pgm = ctx.program
				state.select = {}
				local E = starlit.mod.electronics
				if ctx.context == 'suit' then
					state.fetch = function()
						local cst = user.entity:get_inventory():get_list 'starlit_suit_chips'
						local cl = {order={}, map={}, slot={}}
						for i, c in ipairs(cst) do
							if not c:is_empty() then
								local d = E.chip.read(c)
								local co = {
									stack = c;
									data = d;
								}
								table.insert(cl.order, co)
								cl.map[d.uuid] = co
								cl.slot[i] = co
							end
						end

						-- kill me fam
						if (   state.select.chip
							and state.select.chip ~= true
							and not cl.map[state.select.chip])
						or (state.select.scm
						   and not state.select.scms[state.select.scm])
					   then
							-- chip or pgm no longer available
							user:suitSound 'starlit-error'
							state.select = {}
						end
						state.select.chips = cl

						state.select.scms = {}
						if state.select.chip then
							state.select.scms = E.chip.usableSoftware(cst,nil, function(s)
								if state.select.chip ~= true then
									if cl.slot[s.chipSlot].data.uuid ~= state.select.chip then
										return false
									end
								end
								return s.sw.kind == 'schematic'
							end)
						end

					end
				end
			end;

			onClose = function(state, user)
				user:suitSound 'starlit-quit'
			end;
................................................................................
			handle = function(state, user, q)
				local sel = state.select
				state.fetch()
				local chips = state.select.chips
				local function chirp()
					user:suitSound 'starlit-nav'
				end

				local function trySelection(id)
					if sel[id] == nil then
						for k in next, q do
							local pat = "^"..id.."_(%d+)$" -- ew
							local idx = k:match(pat)
							if idx then
								local cm = tonumber(idx)
								if cm then
									chirp()

									sel[id] = cm
									return true
								end




							end
						end
					end
				end

				if sel.chip == nil then


					if q.showAll then
						chirp()
						sel.chip = true
						return true
					elseif q.find then


						chirp()
						-- TODO
						return true
					end
				end

				if trySelection('chip') then
					return true
				elseif trySelection('scm') then
					return true
				else
					if q.back then
						chirp()
						if sel.input then
							sel.input = nil
						elseif sel.scm then
							sel.scm = nil
						elseif sel.chip then
							sel.chip = nil



						end
						return true
					elseif q.commit then
						if not sel.input then
							chirp()
							sel.input = true
							return true
						else
							local scm = sel.scms[sel.scm]
							local ok, cost = compilerCanPrint(user, state.pgm, scm)
							if ok then
								user:suitSound 'starlit-configure'
								-- consume consumables
								-- add print job
								state.select = {}

								return true
							else
								user:suitSound 'starlit-error'
							end
						end


					end
				end

			end;

			render = function(state, user)
				local sel, pgmSelector = state.select, {}
				state.fetch()

				local function pushSelector(id, item, label, desc, req)
					local rh = .5
					local label = {kind = 'text', w = 10-1.5, h=1.5;
							text = '<global valign=middle>'..lib.str.htsan(label) }
					if req then
						label.h = label.h - rh - .2

						local imgs = {}
						for ci,c in ipairs(req) do
							for ei, e in ipairs(c.list) do
								table.insert(imgs, {kind = 'img', w=rh, h=rh,  img=e.img})
................................................................................
				if sel.chips == nil then
					table.insert(pgmSelector, {kind = 'img', img = 'starlit-ui-alert.png', w=2, h=2})
				elseif sel.chip == nil then
					for i, c in ipairs(sel.chips.order) do
					-- TODO filter out chips without schematics?
						pushSelector('chip_' .. c.data.uuid, c.stack, c.data.label)
					end
					if next(sel.chips.order) then
						table.insert(pgmSelector, {kind = 'hztl', w=10,h=1.5;
							{kind = 'button', w=5,h=1.5; id='showAll', label='Show All'};
							{kind = 'button', w=5,h=1.5; id='find', label='Find'};
						})
					end
				else
					if sel.scm == nil then
						for idx, ent in ipairs(sel.scms) do
							local fab = ItemStack(ent.sw.output):get_definition()._starlit.fab
							if fab.flag.print then
								local req = fab:visualize()
								pushSelector('scm_' .. idx, ent.sw.output, ent.sw.name, nil, req)
							end
						end
						table.insert(pgmSelector, back)
					else
						local scm = sel.scms[sel.scm]
						local output = ItemStack(scm.sw.output):get_definition()
						local fab = output._starlit.fab
						local sw = scm.sw
						local function unmet(str)
							return lib.color(1,.3,.3):fmt(str)
						end
						table.insert(pgmSelector, {kind = 'hztl', w=10, h=1.2;
							{kind = 'img', item = sw.output, w=1.2, h=1.2, desc=output.description};
							{kind = 'text', text = string.format('<global valign=middle><b>%s</b>', lib.str.htsan(sw.name)), w=10-1.2,h=1.2};
						})
						local inputTbl = {kind = 'vert', w=5,h=0;
							{kind = 'hbar', w=5, h=.5, text=sel.input and 'Input Plan' or 'Input'}};
						local costTbl = {kind = 'vert', w=5,h=0;
							{kind = 'hbar', w=5, h=.5, text=sel.input and 'Process Plan' or 'Process'}};
						local reqPane = {kind = 'pane', id='reqPane', w=10, h=7;
							{kind = 'hztl', w=10,h=0; inputTbl, costTbl}
						}

						local function pushCost(x, t, val)
							table.insert(costTbl, {kind='label', w=4.5,h=.5,x=x;
								text=string.format('%s: %s',t,val);
							})
						end
						local function pushComputeCosts(header, p)
							if p then
								table.insert(costTbl, {kind = 'label', w=5, h=.5, x=0; text=header});
								if p.cycles then
									pushCost(.5, 'Compute', lib.math.siUI({'cycle','cycles'}, p.cycles, true))
								end
								if p.power then
									local str = lib.math.siUI('J', p.power)
									if p.power > user:suitCharge() then str = unmet(str) end
									pushCost(.5, 'Power', str)
								end
								if p.ram then
									local str = string.format("%s / %s",
										lib.math.siUI('B', p.ram),
										lib.math.siUI('B', state.pgm.comp.ram))
									if p.ram > state.pgm.comp.ram then str = unmet(str) end
									pushCost(.5, 'Memory', str)
								end
							end
						end

						local function fabToUI(x, inputTbl, req)
							for ci,c in ipairs(req) do
								table.insert(inputTbl, {kind = 'label', w=5-x, h=.5, x=x;
									text=lib.str.capitalize(c.header)});
								for ei,e in ipairs(c.list) do
									table.insert(inputTbl, {kind = 'hztl', w=4.5-x, h=.5, x=x+.5;
										{kind='img',   w=.5,h=.5, img=e.img};
										{kind='label', w=3.3,h=.5,x=.2, text=lib.str.capitalize(e.label)};
									});
								end
							end
						end

						local commitHue=120, commitLabel
						if not sel.input then
							commitLabel = 'Plan'
							fabToUI(0, inputTbl, fab:visualize())
							local function pushComputeCostsSw(header, p)
								if p.sw.cost then



									pushComputeCosts(header, {
										cycles = p.sw.cost.cycles;
										power = p.powerCost;
										ram = p.sw.cost.ram;
									})
								end
							end
							pushComputeCostsSw('Schematic', scm)
							pushComputeCostsSw('Compiler', state.pgm)
						else
							commitLabel = 'Commit'
							pushComputeCosts('Total', {
								cycles = (scm.sw.cost and scm.sw.cost.cycles or 0)


								       + (state.pgm.sw.cost and state.pgm.sw.cost.cycles or 0);
								power = (scm.powerCost or 0)
								      + (state.pgm.powerCost or 0)
								      + (fab.cost and fab.cost.power or 0);
								ram = (scm.sw.cost and scm.sw.cost.ram or 0)
								    + (state.pgm.sw.cost and state.pgm.sw.cost.ram or 0);
							})
							if fab.time and fab.time.print then
								pushCost(0, 'Job Runtime', lib.math.timespec(fab.time.print + scm.speed))
								pushCost(.5, 'Print Time', lib.math.timespec(fab.time.print))
								pushCost(.5, 'CPU Time', lib.math.timespec(scm.speed + state.pgm.speed))
							end
							local ok, compileCost = compilerCanPrint(user, state.pgm, scm)
							fabToUI(0, inputTbl, compileCost.itemSpec:visualize())

							if next(compileCost.unsat) then
								local vis = compileCost.unsat:visualize()
								for si, s in ipairs(vis) do
									s.header = 'Missing ' .. s.header
									for ei, e in ipairs(s.list) do
										e.label = lib.color(1,.2,.2):fmt(e.label)
									end
								end
								fabToUI(0, inputTbl, vis)
							end
							if not ok then commitHue = 0 end
						end
						table.insert(pgmSelector, reqPane)
						table.insert(pgmSelector, {kind = 'hztl', w=10,h=1.2;
							{kind = 'button', id='back', label = ' Back', w=5,h=1.2};
							{kind = 'button', id='commit', label = commitLabel .. ' →', w=5,h=1.2, color={hue=commitHue,sat=0,lum=0}};
						})
					end
				end

				return starlit.ui.build {
					kind = 'hztl', padding = 0.5; w = 20, h = 10, mode = 'sw';
					{kind = 'vert', w = 5, h = 5;

Modified mods/starlit/species.lua from [34f8558850] to [e0959a01e2].

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
...
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
					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 = 0.05; -- ψ/s
					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 = 0.025;
					psiPower = 1.0;
				};
			};
		};
		traits = {};
		abilities = {bioAbilities.sprint};
	};
}
................................................................................
		local min, max = starlit.world.species.statRange(pSpecies, pVariant, st)
		local delta = max - min
		return min + delta*p
	end
	local ps = starlit.world.species.mkPersonaFor(pSpecies,pVariant)
	local startingHP = pct('health', 1.0)
	if circumstances.injured    then startingHP = pct('health', circumstances.injured) end
	if circumstances.psiCharged then ps.statDeltas.psi = pct('psi', circumstances.psiCharged) end
	for k,v in pairs(starlit.world.stats) do ps.statDeltas[k] = 0 end
	ps.statDeltas.warmth = 20 -- don't instantly start dying of frostbite
	ps.statDeltas.nutrition = 2000 -- shoulda packed more MRE :c
	ps.statDeltas.hydration = 3 -- stay hydrated uwu

	entity:set_properties{hp_max = var.traits.health or sp.traits.health}
	entity:set_hp(startingHP, 'initial hp')







|







 







|
|






|












|
|







 







|







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
...
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
					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 = {
					numina = 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;
					numinaRegen = 0.05; -- ψ/s
					psi = 1.2;
				};
			};
			male = {
				name = 'Human Male';
				eyeHeight = 1.6;
				stats = {
					numina = 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;
					numinaRegen = 0.025;
					psi = 1.0;
				};
			};
		};
		traits = {};
		abilities = {bioAbilities.sprint};
	};
}
................................................................................
		local min, max = starlit.world.species.statRange(pSpecies, pVariant, st)
		local delta = max - min
		return min + delta*p
	end
	local ps = starlit.world.species.mkPersonaFor(pSpecies,pVariant)
	local startingHP = pct('health', 1.0)
	if circumstances.injured    then startingHP = pct('health', circumstances.injured) end
	if circumstances.numinaCharged then ps.statDeltas.numina = pct('numina', circumstances.numinaCharged) end
	for k,v in pairs(starlit.world.stats) do ps.statDeltas[k] = 0 end
	ps.statDeltas.warmth = 20 -- don't instantly start dying of frostbite
	ps.statDeltas.nutrition = 2000 -- shoulda packed more MRE :c
	ps.statDeltas.hydration = 3 -- stay hydrated uwu

	entity:set_properties{hp_max = var.traits.health or sp.traits.health}
	entity:set_hp(startingHP, 'initial hp')

Modified mods/starlit/stats.lua from [0688acc2a6] to [9558da3e0c].

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
..
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
	end
end

local function C(h, s, l)
	return lib.color {hue = h, sat = s or 1, lum = l or .7}
end
starlit.world.stats = {
	psi        = {min = 0, max = 500, base = 0, desc = U('ψ', 1), color = C(320), name = 'numina', srzType = T.decimal};
	-- numina is measured in ψ
	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', harm=true, 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, color = C(0,0,.8), name = 'morale', srzType = T.decimal;
		desc = function(amt, excU) return lib.math.timespec(amt) end};
	-- morale is measured in minutes. e.g. at base rate morale degrades by
	-- 60 points every hour. morale can last up to 10 earthdays
	irradiation = {min = 0, max = 10, base = 0, desc = U('Gy', 1), color = C(141,1,.5), name = 'irradiation', harm=true, srzType = T.decimal};
	-- irrad is measured is milligreys
................................................................................
	-- illness is increased by certain conditions, and decreases on its own as your
	-- body heals when those conditions wear off. some drugs can lower accumulated illness
	-- but illness-causing conditions require specific cures
	-- illness also causes thirst and fatigue to increase proportionately
}

starlit.world.statOrder = {
	'health', 'stamina', 'psi', 'warmth';

	'nutrition', 'hydration', 'irradiation';
	'illness', 'morale', 'fatigue';
}

local impactStruct = G.struct {
	base = G.array(8, G.struct {id = T.str, val = T.decimal});







|



|

|

|

|







 







|







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
..
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
	end
end

local function C(h, s, l)
	return lib.color {hue = h, sat = s or 1, lum = l or .7}
end
starlit.world.stats = {
	numina     = {min = 0, max = 500, base = 0, desc = U('ψ', 1), color = C(320), name = 'numina', srzType = T.decimal};
	-- numina is measured in ψ
	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,1,.8), name = 'fatigue', harm=true, 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', 10), 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,1,.8), 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), 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, color = C(0,0,.8), name = 'morale', srzType = T.decimal;
		desc = function(amt, excU) return lib.math.timespec(amt) end};
	-- morale is measured in minutes. e.g. at base rate morale degrades by
	-- 60 points every hour. morale can last up to 10 earthdays
	irradiation = {min = 0, max = 10, base = 0, desc = U('Gy', 1), color = C(141,1,.5), name = 'irradiation', harm=true, srzType = T.decimal};
	-- irrad is measured is milligreys
................................................................................
	-- illness is increased by certain conditions, and decreases on its own as your
	-- body heals when those conditions wear off. some drugs can lower accumulated illness
	-- but illness-causing conditions require specific cures
	-- illness also causes thirst and fatigue to increase proportionately
}

starlit.world.statOrder = {
	'health', 'stamina', 'numina', 'warmth';

	'nutrition', 'hydration', 'irradiation';
	'illness', 'morale', 'fatigue';
}

local impactStruct = G.struct {
	base = G.array(8, G.struct {id = T.str, val = T.decimal});

Modified mods/starlit/suit.lua from [7f11ba51a8] to [8324bad06e].

341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
		if rst.itemClass and not grp(item, rst.itemClass) then
			return false
		end
		if rst.maintenanceNode then return false end
		-- FIXME figure out best way to identify when the player is using a maintenance node

		if grp(item, 'specialInventory') then
			if grp(item, 'powder') and list ~= 'starlit_suit_elem' then return false end
			-- FIXME handle containers
			if grp(item, 'psi') and list ~= 'starlit_psi' then return false end
		end

		return true
	end
	local function itemCanLeave(item, list)
		local rst, ok = checkBaseRestrictions(list)
		if not ok then return false end







<
<
<







341
342
343
344
345
346
347



348
349
350
351
352
353
354
		if rst.itemClass and not grp(item, rst.itemClass) then
			return false
		end
		if rst.maintenanceNode then return false end
		-- FIXME figure out best way to identify when the player is using a maintenance node

		if grp(item, 'specialInventory') then



		end

		return true
	end
	local function itemCanLeave(item, list)
		local rst, ok = checkBaseRestrictions(list)
		if not ok then return false end

Modified mods/starlit/ui.lua from [81aedb85b1] to [08cb8bbbbd].

249
250
251
252
253
254
255

256
257

258
259
260
261
262
263
264
		if def.kind == 'hbar'
			then wfac = wfac * clamp
			else hfac = hfac * clamp
		end
		local x,y, w,h = state.x, state.y, def.w, def.h
		widget('box[%s,%s;%s,%s;%s]',
			x,y, w,h, cl:brighten(0.2):hex())

		widget('box[%s,%s;%s,%s;%s]',
			x, y + (h*(1-hfac)), w * wfac, h * hfac, cl:hex())

		if def.text then
			widget('hypertext[%s,%s;%s,%s;;%s]',
				state.x, state.y, def.w, def.h,
				string.format('<global halign=center valign=middle color=%s>%s', fg:hex(), E(def.text)))
		end
	end








>
|
|
>







249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
		if def.kind == 'hbar'
			then wfac = wfac * clamp
			else hfac = hfac * clamp
		end
		local x,y, w,h = state.x, state.y, def.w, def.h
		widget('box[%s,%s;%s,%s;%s]',
			x,y, w,h, cl:brighten(0.2):hex())
		if clamp > 0 then
			widget('box[%s,%s;%s,%s;%s]',
				x, y + (h*(1-hfac)), w * wfac, h * hfac, cl:hex())
		end
		if def.text then
			widget('hypertext[%s,%s;%s,%s;;%s]',
				state.x, state.y, def.w, def.h,
				string.format('<global halign=center valign=middle color=%s>%s', fg:hex(), E(def.text)))
		end
	end

Modified mods/starlit/user.lua from [b2784a81d4] to [c928629a64].

19
20
21
22
23
24
25
26


































27
28

29
30
31
32
33
34
35
..
38
39
40
41
42
43
44

45
46
47
48
49
50
51
..
69
70
71
72
73
74
75



76
77
78
79
80
81
82
83
84
85
86
...
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
...
477
478
479
480
481
482
483







































484
485
486
487
488
489
490
...
920
921
922
923
924
925
926
927




































928
929
930
931
932
933
934
...
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
....
1015
1016
1017
1018
1019
1020
1021

1022
1023
1024
1025
1026
1027
1028

1029
1030
1031
1032
1033
1034
1035
....
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
	persona = {
		key  = 'starlit:persona';
		type = starlit.store.persona;
	};
}

local suitStore = starlit.store.suitMeta



































starlit.type.user = lib.class {
	name = 'starlit:user';

	construct = function(ident)
		local name, luser
		if type(ident) == 'string' then
			name = ident
			luser = minetest.get_player_by_name(name)
		else
			luser = ident
................................................................................
		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 = {};
................................................................................
		}
	end;
	__index = {
		--------------
		-- overlays --
		--------------
		updateOverlays = function(self)



			local phys = {
				speed = self.pheno:trait('speed',1);
				jump = self.pheno:trait('jump',1);
				gravity = 1;
				speed_climb = 1;
				speed_crouch = 1;
				speed_walk = 1;
				acceleration_default = 1;
				acceleration_air = 1;
			}
			for i, o in ipairs(self.overlays) do o(phys) end
................................................................................
			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';
................................................................................
				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;

		---------------------
		-- actions & modes --
		---------------------
		onModeChange = function(self, oldMode, silent)
			self.hud.elt.crosshair.update()
................................................................................
			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]
................................................................................
			   -- 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
................................................................................

			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;








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


>







 







>







 







>
>
>



|







 







|
|







 







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







 







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







 







|







 







>






|
>







 







|
|







19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
..
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
...
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
...
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
...
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
...
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
....
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
....
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
....
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
	persona = {
		key  = 'starlit:persona';
		type = starlit.store.persona;
	};
}

local suitStore = starlit.store.suitMeta

local leds = {
	freeze = {
		icon = lib.image('starlit-ui-alert-temp-cold.png');
		bg = lib.image('starlit-ui-alert-bg-temp-cold.png');
		side = 'left';
	};
	overheat = {
		icon = lib.image('starlit-ui-alert-temp-hot.png');
		bg = lib.image('starlit-ui-alert-bg-temp-hot.png');
		side = 'left';
	};
	hydration = {
		icon = lib.image('starlit-ui-alert-hydration.png');
		bg = lib.image('starlit-ui-alert-bg-hydration.png');
		side = 'left';
	};
	nutrition = {
		icon = lib.image('starlit-ui-alert-nutrition.png');
		bg = lib.image('starlit-ui-alert-bg-nutrition.png');
		side = 'left';
	};

	radiation = {
		icon = lib.image('starlit-ui-alert-rad.png');
		bg = lib.image('starlit-ui-alert-bg-rad.png');
		side = 'right';
	};
	fatigue = {
		icon = lib.image('starlit-ui-alert-fatigue.png');
		bg = lib.image('starlit-ui-alert-bg-fatigue.png');
		side = 'right';
	};
}

starlit.type.user = lib.class {
	name = 'starlit:user';
	leds = leds;
	construct = function(ident)
		local name, luser
		if type(ident) == 'string' then
			name = ident
			luser = minetest.get_player_by_name(name)
		else
			luser = ident
................................................................................
		return {
			entity = luser;
			name = name;
			hud = {
				elt = {};
				bar = {};
				alarm = {};
				led = { left={}, right={}, map={} };
			};
			tree = {};
			action = {
				bits = 0; -- for control deltas
				prog = {}; -- for recording action progress on a node; reset on refocus
				tgt = {type='nothing'};
				sfx = {};
................................................................................
		}
	end;
	__index = {
		--------------
		-- overlays --
		--------------
		updateOverlays = function(self)
			-- minetest: because fuck you, that's why
			local engineGravity = starlit.constant.phys.engineGravity
			local targetGravity = starlit.world.planet.gravity
			local phys = {
				speed = self.pheno:trait('speed',1);
				jump = self.pheno:trait('jump',1);
				gravity = targetGravity / engineGravity;
				speed_climb = 1;
				speed_crouch = 1;
				speed_walk = 1;
				acceleration_default = 1;
				acceleration_air = 1;
			}
			for i, o in ipairs(self.overlays) do o(phys) end
................................................................................
			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.numina = attachBasicStat {
				name = 'numina', stat = 'numina';
				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';
................................................................................
				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
			self:updateLEDs()
		end;
		updateLEDs = function(self)
			local time = minetest.get_gametime()
			local function updateSide(name, ofs, tx)
				local del = {}
				for i, l in ipairs(self.hud.led[name]) do
					local idx = 0
					if time - l.origin > 3 then
						if l.elt then self.entity:hud_remove(l.elt.id) end
						self.hud.led.map[l.kind] = nil
						table.insert(del, i)
					else
						local xc = (idx*48 + 400)*ofs
						if l.elt and next(del) then
							l.elt:update('offset', {x=xc, y=1})
						else
							local tex = leds[l.kind].icon:blit(hudAdjustBacklight(leds[l.kind].bg))
							if tx then tex = tex:transform(tx) end
							if not l.elt then
								l.elt = self:attachImage {
									tex = tex:render();
									align = {x=ofs, y=-1};
									pos = {x=.5, y=1};
									scale = {x=1,y=1};
									ofs = {x=xc, y=0};
								}
							end
						end
						idx = idx + 1
					end
				end
				for _, i in ipairs(del) do
					table.remove(self.hud.led[name], i)
				end

			end
			updateSide('left', -1)
			updateSide('right', 1, 'FX')
		end;

		---------------------
		-- actions & modes --
		---------------------
		onModeChange = function(self, oldMode, silent)
			self.hud.elt.crosshair.update()
................................................................................
			if run then
				run(self, ctx)
				return true
			end
			return false
		end;

		alarm = function(self, urgency, kind, minFreq)
			minFreq = minFreq or 1.5
			local time = minetest.get_gametime()
			local led = leds[kind]

			local ul = self.hud.led.map[kind]
			if ul then
				if time - ul.origin > minFreq then
					ul.origin = time
				else return end
			end

			if urgency > 0 then
				local urgencies = {
					[1] = {sound = 'starlit-alarm'};
					[2] = {sound = 'starlit-alarm-urgent'};
				}
			   local urg = urgencies[urgency] or urgencies[#urgencies]

			   if time - self.cooldownTimes.alarm > 1.5 then
				   self.cooldownTimes.alarm = time
				   self:suitSound(urg.sound)
			   end
		   end


			local newLed = {
				kind = kind;
				origin = time;
			}
			self.hud.led.map[kind] = newLed
			table.insert(self.hud.led[led.side], newLed)


		   self:updateLEDs()

		--[[
			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]
................................................................................
			   -- 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()
		u:updateLEDs()
	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
................................................................................

			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 'numinaRegen'
			u:statDelta('numina', pr * penaltyFromFatigue * mp)
		end
	end
end)

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

Modified mods/starlit/world.lua from [5368f81f41] to [822a964373].

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
	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







<
<
<
<
<
<




|


|







181
182
183
184
185
186
187






188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
	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
			   if tr[1] - t > 7 then urg = 2 end
			   user:alarm(urg, 'freeze', 3)
		   elseif hz == 'hot' then
			   if t - tr[2] > 7 then urg = 2 end
			   user:alarm(urg, 'overheat', 3)
		   end
		end

		local insul = 0
		local naked = user:naked()
		local suitDef
		if not naked then

Modified mods/vtlib/math.lua from [43eaf4251e] to [2d47a7e2f1].

156
157
158
159
160
161
162

163
164
165
166

167
168
169
170
-- function fn.vlerp

function fn.timespec(n)
	if n == 0 then return '0s' end
	if n < 0 then return '-' .. fn.timespec(n*-1) end

	local sec = math.floor(n % 60)

	local hr = math.floor(n / 60)
	local spec = {}

	if hr  ~= 0 then table.insert(spec, string.format("%shr", hr))  end

	if sec ~= 0 then table.insert(spec, string.format("%ss",  sec)) end
	return table.concat(spec, ' ')
end
return fn







>
|



>




156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
-- function fn.vlerp

function fn.timespec(n)
	if n == 0 then return '0s' end
	if n < 0 then return '-' .. fn.timespec(n*-1) end

	local sec = math.floor(n % 60)
	local min = math.floor(n / 60)
	local hr = math.floor(min / 60)
	local spec = {}

	if hr  ~= 0 then table.insert(spec, string.format("%shr", hr))  end
	if min  ~= 0 then table.insert(spec, string.format("%sm", min))  end
	if sec ~= 0 then table.insert(spec, string.format("%ss",  sec)) end
	return table.concat(spec, ' ')
end
return fn

Modified mods/vtlib/str.lua from [01c1839f00] to [ded7121fc6].

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
			else
				tbl[#tbl+1] = string.sub(ss,1,d-1)
			end
		until i > string.len(str)
		return tbl
	end;

	rand = function(min,max)
		if not min then min = 16  end
		if not max then max = min end
		local str = ''
		local r_int   =            0x39 - 0x30
		local r_upper = r_int   + (0x5a - 0x41)
		local r_lower = r_upper + (0x7a - 0x61)
		for i = 1,math.random(max - min) + min do
			-- 0x30 -- 0x39
			-- 0x41 -- 0x5A
			-- 0x61 -- 0x71
			local codepoint = math.random(r_lower)
			if codepoint > r_upper then
				codepoint = (codepoint - r_upper) + 0x61
			elseif codepoint > r_int then
				codepoint = (codepoint - r_int) + 0x41
			else
				codepoint = codepoint + 0x30
			end
			str = str .. string.char(codepoint)
		end
		return str
	end;





	chop = function(str)
		if string.sub(str, 1,1) == ' ' then
			str = string.sub(str, 2)
		end
		if string.sub(str, #str,#str) == ' ' then
			str = string.sub(str, 1, #str - 1)







|






|



|











>
>
>
>







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
			else
				tbl[#tbl+1] = string.sub(ss,1,d-1)
			end
		until i > string.len(str)
		return tbl
	end;

	rand = function(rng, min,max)
		if not min then min = 16  end
		if not max then max = min end
		local str = ''
		local r_int   =            0x39 - 0x30
		local r_upper = r_int   + (0x5a - 0x41)
		local r_lower = r_upper + (0x7a - 0x61)
		for i = 1,rng:int(min,max) do
			-- 0x30 -- 0x39
			-- 0x41 -- 0x5A
			-- 0x61 -- 0x71
			local codepoint = rng:int(r_lower)
			if codepoint > r_upper then
				codepoint = (codepoint - r_upper) + 0x61
			elseif codepoint > r_int then
				codepoint = (codepoint - r_int) + 0x41
			else
				codepoint = codepoint + 0x30
			end
			str = str .. string.char(codepoint)
		end
		return str
	end;

	htsan = function(str)
		return str:gsub('([<\\])', '\\%1')
	end;

	chop = function(str)
		if string.sub(str, 1,1) == ' ' then
			str = string.sub(str, 2)
		end
		if string.sub(str, #str,#str) == ' ' then
			str = string.sub(str, 1, #str - 1)

Modified starlit.ct from [8d6b5074b6] to [7cb4085b3b].

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

	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)]







|

|





|










|
>







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

	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. you do have some control over your environment, but it's limited and exerting it is much more expensive than you might be used to -- the focus of the game is figuring out how to work with nature, not against it. Farthest Shadow has little patience for those who do not show her the respect a living world is due, and she is unconcerned with human virtues like "mercy" or "fairness" or "proportionate retribution".

this is to say, starlit 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 stronger 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)]