starlit  Check-in [0e7832a24c]

Overview
Comment:add beginnings of matter compiler UI, check in missing files
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 0e7832a24c7daa4240f152a584e31bbecbda228f2bffe1dec3816a5817c66f85
User & Date: lexi on 2024-05-04 22:41:29
Other Links: manifest | tags
Context
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
02:18
fix colors, add grass check-in: 9d4ddb7701 user: lexi tags: trunk
Changes

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

    39     39   -- crafting, while programs require a certain amount of memory.
    40     40   -- chips have a variable number of program slots and a single bootloader slot
    41     41   --
    42     42   starlit.item.chip = lib.registry.mk 'starlit_electronics:chip'
    43     43   
    44     44   -- software is of one of the following types:
    45     45   --   schematic: program for your matter compiler that enables crafting a given item.
    46         ---       output: the result
           46  +--       output (convertible to ItemStack): the result
    47     47   --   driver: inserted into a Core to control attached hardware
    48     48   --     suitPower: provides suit functionality like nanoshredding or healing
    49     49   --                passive powers are iterated on suit application/configuration and upon fst-tick
    50     50   --   cost: what the software needs to run. some fields are fab-specific
    51     51   --		   energy: for fab, total energy cost of process in joules
    52     52   --               for suitPassive, added suit power consumption in watts
    53     53   starlit.item.sw = lib.registry.mk 'starlit_electronics:sw'
................................................................................
   214    214   		inventory_image = def.img or 'starlit-item-battery.png';
   215    215   		description = starlit.ui.tooltip {
   216    216   			title = def.name;
   217    217   			desc = def.desc;
   218    218   			color = lib.color(0,.2,1);
   219    219   			props = {
   220    220   				{ title = 'Optimal Capacity', affinity = 'info';
   221         -					desc = lib.math.si('J', def.capacity) };
          221  +					desc = lib.math.siUI('J', def.capacity) };
   222    222   				{ title = 'Discharge Rate', affinity = 'info';
   223         -					desc = lib.math.si('W', def.dischargeRate) };
          223  +					desc = lib.math.siUI('W', def.dischargeRate) };
   224    224   				{ title = 'Charge Efficiency', affinity = 'info';
   225    225   					desc = string.format('%s%%', (1-def.leak) * 100) };
   226    226   				{ title = 'Size', affinity = 'info';
   227         -					desc = lib.math.si('m', def.fab.size.print) };
          227  +					desc = lib.math.siUI('m', def.fab.size.print) };
   228    228   			};
   229    229   		};
   230    230   		_starlit = {
   231    231   			event = {
   232    232   				create = function(st, how)
   233    233   					--[[if not how.gift then -- cheap hack to make starting batteries fully charged
   234    234   						E.battery.setCharge(st, 0)
................................................................................
   274    274   -- eluthrai ("uncompromising"): high power, high quality, wildly expensive
   275    275   -- firstborn ("god-tier"): exceptional
   276    276   
   277    277   local batteryTiers = {
   278    278   	makeshift = {
   279    279   		name = 'Makeshift'; capacity = .5, decay = 3, leak = 2, dischargeRate = 1,
   280    280   		fab = starlit.type.fab {
   281         -			metal = {copper=10};
          281  +			element = {copper=10};
   282    282   		};
   283    283   		desc = "Every attosecond this electrical abomination doesn't explode in your face is but the unearned grace of the Wild Gods.";
   284    284   		complexity = 1;
   285    285   		sw = {rarity = 1};
   286    286   	};
   287    287   	imperial  = {
   288    288   		name = 'Imperial'; capacity = 2, decay = 2, leak = 2, dischargeRate = 2; 
   289    289   		fab = starlit.type.fab {
   290         -			metal = {copper=15, iron = 20};
          290  +			element = {copper=15, iron = 20};
   291    291   			size = { print = 0.1 };
   292    292   		};
   293    293   		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.";
   294    294   		drm = 1;
   295    295   		complexity = 2;
   296    296   		sw = {rarity = 2};
   297    297   	};
   298    298   	commune   = {
   299    299   		name = 'Commune'; capacity = 1, decay = .5, leak = .2, dischargeRate = 1; 
   300    300   		fab = starlit.type.fab {
   301         -			metal = {vanadium=50, steel=10};
          301  +			element = {vanadium = 50};
          302  +			metal = {steel=10};
   302    303   			size = { print = 0.05 };
   303    304   		};
   304    305   		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.";
   305    306   		complexity = 5;
   306    307   		sw = {rarity = 3};
   307    308   	};
   308    309   	usukwinya = {
   309    310   		name = 'Usukwinya'; capacity = 2, decay = 1, leak = 1, dischargeRate = 1.5,
   310    311   		fab = starlit.type.fab {
   311         -			metal = {vanadium=30, argon=10};
          312  +			element = {argon=10};
          313  +			metal = {vanadium=30};
   312    314   			size = { print = 0.07 };
   313    315   		};
   314    316   		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.";
   315    317   		drm = 2;
   316    318   		sw = {rarity = 10};
   317    319   		complexity = 15;
   318    320   	};
   319    321   	eluthrai  = {
   320    322   		name = 'Eluthrai'; capacity = 3, decay = .4, leak = .1, dischargeRate = 1.5,
   321    323   		fab = starlit.type.fab {
   322         -			metal = {beryllium=20, platinum=20, technetium = 1, cinderstone = 10 };
          324  +			element = {beryllium=20, platinum=20, technetium = 1};
          325  +			metal = {cinderstone = 10};
   323    326   			size = { print = 0.03 };
   324    327   		};
   325    328   		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.";
   326    329   		complexity = 200;
   327    330   		sw = {rarity = 0}; -- you think you're gonna buy eluthran schematics on SuperDiscountNanoWare.space??
   328    331   	};
   329    332   	firstborn = {
   330    333   		name = 'Firstborn'; capacity = 5, decay = 0.1, leak = 0, dischargeRate = 3;
   331    334   		fab = starlit.type.fab {
   332         -			metal = {neodymium=20, xenon=150, technetium=5, sunsteel = 10 };
          335  +			element = {neodymium=20, xenon=150, technetium=5};
          336  +			metal = {sunsteel = 10};
   333    337   			crystal = {astrite = 1};
   334    338   			size = { print = 0.05 };
   335    339   		};
   336    340   		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.";
   337    341   		complexity = 1000;
   338    342   		sw = {rarity = 0}; -- lol no
   339    343   	};
................................................................................
   367    371   
   368    372   		complexity = 3;
   369    373   	};
   370    374   	chemical = {
   371    375   		name = 'Chemical';
   372    376   		desc = '';
   373    377   		fab = starlit.type.fab {
   374         -			element = { lithium = 3};
   375         -			metal = {iron = 5};
          378  +			element = { lithium = 3 };
   376    379   			size = {print=1.0};
   377    380   		};
   378    381   		sw = {
   379    382   			cost = {
   380    383   				cycles = 1e9; -- 1 bil cycles
   381    384   				ram = 2e9; -- 2GB
   382    385   			};
................................................................................
   409    412   	};
   410    413   	hybrid = {
   411    414   		name = 'Hybrid';
   412    415   		desc = '';
   413    416   		capacity = 1;
   414    417   		fab = starlit.type.fab {
   415    418   			element = {
   416         -				lithium = 3;
   417         -			};
   418         -			metal = {
   419         -				iron = 5;
          419  +				lithium = 10;
          420  +				carbon = 20;
   420    421   			};
   421    422   			size = {print=1.5};
   422    423   		};
   423    424   		sw = {
   424    425   			cost = {
   425    426   				cycles = 65e9; -- 65 bil cycles
   426    427   				ram = 96e9; -- 96GB
................................................................................
   500    501   	else
   501    502   		rare = bType.sw.rarity + bTier.sw.rarity
   502    503   	end
   503    504   
   504    505   	starlit.item.sw.link(swID, {
   505    506   		kind = 'schematic';
   506    507   		name = name .. ' Schematic';
          508  +		input = fab;
   507    509   		output = id;
   508    510   		size = bType.sw.pgmSize;
   509    511   		cost = bType.sw.cost;
   510    512   		rarity = rare;
   511    513   	})
   512    514   
   513    515   	E.schematicGroupLink('starlit_electronics:battery', swID)
................................................................................
   686    688   			data = {}
   687    689   			defOnly = true
   688    690   		end
   689    691   		def = assert(def._starlit.chip)
   690    692   	end
   691    693   	local props = {
   692    694   		{title = 'Clock Rate', affinity = 'info';
   693         -		 desc  = lib.math.si('Hz', def.clockRate)};
          695  +		 desc  = lib.math.siUI('Hz', def.clockRate)};
   694    696   		{title = 'RAM', affinity = 'info';
   695         -		 desc  = lib.math.si('B', def.ram)};
          697  +		 desc  = lib.math.siUI('B', def.ram)};
   696    698   	}
   697    699   	if not defOnly then
   698    700   		table.insert(props, {
   699    701   			title = 'Free Storage', affinity = 'info';
   700         -			 desc = lib.math.si('B', E.chip.freeSpace(ch, data)) .. ' / '
   701         -			     .. lib.math.si('B', def.flash);
          702  +			 desc = lib.math.siUI('B', E.chip.freeSpace(ch, data)) .. ' / '
          703  +			     .. lib.math.siUI('B', def.flash);
   702    704   		})
   703    705   		local swAffMap = {
   704    706   			schematic = 'schematic';
   705    707   			suitPower = 'ability';
   706    708   			driver = 'driver';
   707    709   		}
   708    710   		for i, e in ipairs(data.files) do
................................................................................
   730    732   				affinity = aff;
   731    733   				desc = name;
   732    734   			})
   733    735   		end
   734    736   	else
   735    737   		table.insert(props, {
   736    738   			title = 'Flash Storage', affinity = 'info';
   737         -			 desc = lib.math.si('B', def.flash);
          739  +			 desc = lib.math.siUI('B', def.flash);
   738    740   		 })
   739    741   	end
   740    742   	return starlit.ui.tooltip {
   741    743   		title = data.label and data.label~='' and string.format('<%s>', data.label) or def.name;
   742    744   		color = lib.color(.6,.6,.6);
   743    745   		desc = def.desc;
   744    746   		props = props;
................................................................................
   782    784   	local circMat = t.circ or 'silicon';
   783    785   	starlit.item.chip.link(id, {
   784    786   		name = t.name;
   785    787   		clockRate = t.clockRate;
   786    788   		flash = t.flash;
   787    789   		ram = t.ram;
   788    790   		powerEfficiency = t.powerEfficiency; -- cycles per joule
   789         -		fab = {
          791  +		fab = starlit.type.fab {
   790    792   			flag = {
   791    793   				silicompile = true;
   792    794   			};
   793    795   			time = {
   794    796   				silicompile = t.size * 24*60;
   795    797   			};
   796    798   			cost = {
................................................................................
   866    868   		end;
   867    869   		open = function(self,fn)
   868    870   			return E.chip.fileOpen(self.chip, self.inode, fn)
   869    871   		end;
   870    872   	};
   871    873   }
   872    874   
   873         -function E.chip.usableSoftware(chips,pgm)
          875  +function E.chip.usableSoftware(chips,pgm,pred)
          876  +	pred = pred or function() return true end
   874    877   	local comp = E.chip.sumCompute(chips)
   875    878   	local r = {}
   876    879   	local unusable = {}
   877    880   	local sw if pgm then
   878    881   		if type(pgm) == 'string' then
   879    882   			pgm = {starlit.item.sw.db[pgm]}
   880    883   		end
................................................................................
   895    898   					end
   896    899   				end
   897    900   			end
   898    901   		end
   899    902   	end
   900    903   
   901    904   	for _, s in pairs(sw) do
   902         -		if s.sw.cost.ram <= comp.ram then
          905  +		if s.sw.cost.ram <= comp.ram and pred(s) then
   903    906   			table.insert(r, {
   904    907   				sw = s.sw;
   905    908   				chip = s.chip, chipSlot = s.chipSlot;
   906    909   				file = s.file;
   907    910   				fd = E.chip.fileHandle(s.chip, s.inode);
   908    911   				speed = s.sw.cost.cycles / comp.cycles;
   909    912   				powerCost = s.sw.cost.cycles / comp.powerEfficiency;

Modified mods/starlit-electronics/mod.conf from [c7d10b71e3] to [8ba7140456].

     1      1   name = starlit_electronics
     2      2   title = starlit electronics
     3      3   description = basic electronic components and logic
     4         -depends = starlit
            4  +depends = starlit, starlit_material

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

    99     99   				user.action.fx.shred = starlit.fx.nano.shred(user, what, prop, shredTime, node)
   100    100   			else
   101    101   				user.action.prog.shred = user.action.prog.shred + ctx.how.delta or 0
   102    102   			end
   103    103   			--print('shred progress: ', user.action.prog.shred)
   104    104   			if user.action.prog.shred >= shredTime then
   105    105   				minetest.remove_node(what)
          106  +				minetest.check_for_falling(what)
   106    107   				--print('shred complete')
   107    108   				user:suitSound 'starlit-success'
   108    109   				if fab then
   109    110   					local vf = fab
   110    111   					if vary then
   111    112   						local rng = (starlit.world.seedbank+0xa891f62)[minetest.hash_node_position(what)]
   112    113   						vf = vf + vary(rng, {})

Modified mods/starlit-material/elements.lua from [2a0859e47b] to [fb5dcf0ef1].

    23     23   		color = lib.color(.7,.2,.1);
    24     24   	};
    25     25   	silicon = {
    26     26   		name = 'silicon', sym = 'Si', n = 14, density = 2.329;
    27     27   		metal = true; -- can be forged into an ingot
    28     28   		color = lib.color(.6,.6,.4);
    29     29   	};
           30  +	neodymium = {
           31  +		name = 'neodymium', sym = 'Nd', n= 60, density = 7.01;
           32  +		metal = true;
           33  +		color = lib.color(1,1,1);
           34  +	};
    30     35   	potassium = {
    31     36   		name = 'potassium', sym = 'K', n = 19, density = 0.862;
    32     37   		-- potassium is technically a metal but it's so soft
    33     38   		-- it can be easily nanoworked without high temps, so
    34     39   		-- ingots make no sense
    35     40   		color = lib.color(1,.8,0.1);
    36     41   	};

Modified mods/starlit-material/init.lua from [b9ae2a76fd] to [158d4b1720].

    18     18   	storage = {name = 'Storage Canister', vol = 16.0};
    19     19   }
    20     20   
    21     21   starlit.mod.material = M
    22     22   
    23     23   starlit.include 'elements'
    24     24   starlit.include 'liquids'
           25  +starlit.include 'metals'
    25     26   

Added mods/starlit-material/liquids.lua version [2d90e78ef4].

            1  +local lib = starlit.mod.lib
            2  +local M = starlit.mod.material
            3  +
            4  +starlit.world.material.liquid.meld {
            5  +	water = {
            6  +		name = 'water';
            7  +		composition = starlit.type.fab {
            8  +			element = {hydrogen = 2.0, oxygen = 1.0};
            9  +		};
           10  +		density = 1.0;
           11  +		desc = "the sine qua non of biological life";
           12  +		color = lib.color(.1,.2,1);
           13  +	};
           14  +}

Added mods/starlit-material/metals.lua version [3454f586c9].

            1  +local lib = starlit.mod.lib
            2  +local M = starlit.mod.material
            3  +
            4  +starlit.world.material.metal.meld {
            5  +	steel = {
            6  +		name = 'steel';
            7  +		composition = starlit.type.fab {
            8  +			element = {iron = 2.0, carbon = 1.0};
            9  +		};
           10  +		density = 1.0;
           11  +		desc = "steel is a widely used alloy of iron and carbon";
           12  +		color = lib.color(.4,.4,.4);
           13  +	};
           14  +}

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

   162    162   		_starlit = {
   163    163   			mass = 1e3;
   164    164   			material = {
   165    165   				kind = 'metal';
   166    166   				metal = id;
   167    167   			};
   168    168   			fab = starlit.type.fab {
   169         -				flag = {smelt= true};
          169  +				flag = {smelt=true};
   170    170   				element = comp(1e3);
   171    171   			};
   172    172   		};
   173    173   	});
   174    174   
   175    175   
   176    176   end)

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

     8      8   --			a+b = compose a new spec from the spec parts a and b.
     9      9   --      this is used e.g. for creating tier-based
    10     10   --      fabspecs.
    11     11   --
    12     12   --    * used for determining quantities. that is,
    13     13   --			f*x = spec to make x instances of f
    14     14   --
    15         ---    new fab fields must be defined in starlit.type.fab.opClass.
           15  +--    new fab fields must be defined in starlit.type.fab.fields.
    16     16   --    this maps a name to fn(a,b,n) -> quant, where a is the first
    17     17   --    argument, b is a compounding amount, and n is a quantity of
    18     18   --    items to produce. fields that are unnamed will be underwritten
    19     19   
    20     20   local function fQuant(a,b,n) return ((a or 0)+(b or 0))*n end
    21     21   local function fFac  (a,b,n)
    22     22   	if a == nil and b == nil then return nil end
................................................................................
    26     26   		f = (a or 1)*(b or 1)
    27     27   	end
    28     28   	return f*n
    29     29   end
    30     30   local function fReq  (a,b,n) return a or b         end
    31     31   local function fFlag (a,b,n) return a and b        end
    32     32   local function fSize (a,b,n) return math.max(a,b)  end
    33         -local opClass = {
           33  +
           34  +local F = string.format
           35  +local lib = starlit.mod.lib
           36  +
           37  +local fields = {
    34     38   	-- fabrication eligibility will be determined by which kinds
    35     39   	-- of input a particular fabricator can introduce. e.g. a
    36     40   	-- printer with a  but no cache can only print items whose
    37     41   	-- recipe only names elements as ingredients
    38         -
    39         -	-- ingredients
    40         -	element    = fQuant; -- (g)
    41         -	gas        = fQuant; -- ()
    42         -	liquid     = fQuant; -- (l)
    43         -	crystal    = fQuant; -- (g)
    44         -	item       = fQuant; -- n
    45         -	metal      = fQuant; -- (g)
    46         -	metalIngot = fQuant; -- (g)
           42  +	element = {
           43  +		name = {"element", "elements"};
           44  +		string = function(x, n, long)
           45  +			local el = starlit.world.material.element.db[x]
           46  +			return lib.math.si('g', n) .. ' ' .. ((not long and el.sym) or el.name)
           47  +		end;
           48  +		image = function(x, n)
           49  +			return string.format('starlit-element-%s.png', x)
           50  +		end;
           51  +		op = fQuant;
           52  +	};
           53  +	metal ={
           54  +		name = {"metal", "metals"};
           55  +		string = function(x, n)
           56  +			local met = starlit.world.material.metal.db[x]
           57  +			return lib.math.si('g', n) .. ' ' .. met.name
           58  +		end;
           59  +		image = function(x, n)
           60  +			local met = starlit.world.material.metal.db[x]
           61  +			return ItemStack(met.form.ingot):get_definition().inventory_image
           62  +		end;
           63  +		op = fQuant;
           64  +	};
           65  +	liquid = {
           66  +		name = {"liquid", "liquids"};
           67  +		string = function(x, n)
           68  +			local liq = starlit.world.material.liquid.db[x]
           69  +			return lib.math.si('L', n) .. ' ' .. liq.name
           70  +		end;
           71  +		op = fQuant;
           72  +	};
           73  +	gas = {
           74  +		name = {"gas", "gasses"};
           75  +		string = function(x, n)
           76  +			local gas = starlit.world.material.gas.db[x]
           77  +			return lib.math.si('g', n) .. ' ' .. gas.name
           78  +		end;
           79  +		op = fQuant;
           80  +	};
           81  +-- 	crystal = {
           82  +-- 		op = fQuant;
           83  +-- 	};
           84  +	item = {
           85  +		name = {"item", "items"};
           86  +		string = function(x, n)
           87  +			local i = minetest.registered_items[x]
           88  +			return tostring(n) .. 'x ' .. i.short_description
           89  +		end;
           90  +	};
           91  +
    47     92   	-- factors
    48         -	cost = fFac; -- units vary
    49         -	time = fFac; -- (s)
           93  +
           94  +	cost = {op=fFac}; -- units vary
           95  +	time = {op=fFac}; -- (s)
    50     96   		-- print: base printing time
    51         -	size = fSize;
           97  +	size = {op=fSize};
    52     98   		-- printBay: size of the printer bay necessary to produce the item
    53         -	req  = fReq;
    54         -	flag = fFlag; -- means that can be used to produce the item & misc flags
           99  +	req  = {op=fReq};
          100  +	flag = {op=fFlag}; -- means that can be used to produce the item & misc flags
    55    101   		-- print: allow production with a printer
    56    102   		-- smelt: allow production with a smelter
    57    103   	-- all else defaults to underwrite
    58    104   }
    59    105   
    60         -local F = string.format
    61         -local strClass = {
    62         -	element = function(x, n)
    63         -		local el = starlit.world.material.element[x]
    64         -		return lib.math.si('g', n) .. ' ' .. (el.sym or el.name)
    65         -	end;
    66         -	metal = function(x, n)
    67         -		local met = starlit.world.material.metal[x]
    68         -		return lib.math.si('g', n) .. ' ' .. met.name
    69         -	end;
    70         -	liquid = function(x, n)
    71         -		local liq = starlit.world.material.liquid[x]
    72         -		return lib.math.si('L', n) .. ' ' .. liq.name
    73         -	end;
    74         -	gas = function(x, n)
    75         -		local gas = starlit.world.material.gas[x]
    76         -		return lib.math.si('g', n) .. ' ' .. gas.name
    77         -	end;
    78         -	item = function(x, n)
    79         -		local i = minetest.registered_items[x]
    80         -		return tostring(n) .. 'x ' .. i.short_description
    81         -	end;
    82         -}
    83         -
    84    106   local order = {
    85    107   	'element', 'metal', 'liquid', 'gas', 'item'
    86    108   }
    87    109   
    88    110   local lib = starlit.mod.lib
    89    111   
    90    112   local fab fab = lib.class {
    91    113   	__name = 'starlit:fab';
    92    114   	
    93         -	opClass = opClass;
    94         -	strClass = strClass;
          115  +	fields = fields;
    95    116   	order = order;
    96    117   	construct = function(q) return q end;
    97    118   	__index = {
    98    119   		elementalize = function(self)
    99    120   			local e = fab {element = self.element or {}}
   100    121   			for _, kind in pairs {'metal', 'gas', 'liquid'} do
   101    122   				for m,mass in pairs(self[kind] or {}) do
................................................................................
   154    175   			local fml = {}
   155    176   			for i, v in ipairs(sub) do fml[i] = v.f end
   156    177   			if f then table.insert(fml, f) end
   157    178   			fml = table.concat(fml, ' + ')
   158    179   
   159    180   			return fml, ts
   160    181   		end;
          182  +
          183  +		visualize = function(self)
          184  +			local all = {}
          185  +			for i,o in ipairs(order) do
          186  +				local t = {}
          187  +				if self[o] then
          188  +					for mat,amt in pairs(self[o]) do
          189  +						local v = {}
          190  +						v.id = mat
          191  +						v.n = amt
          192  +						if fields[o].string then
          193  +							v.label = fields[o].string(mat,amt,true)
          194  +						end
          195  +						if fields[o].image then
          196  +							v.img = fields[o].image(mat,amt)
          197  +						end
          198  +						table.insert(t,v)
          199  +					end
          200  +				end
          201  +				if fields[o].sort then
          202  +					table.sort(t, function(a,b) return fields[o].sort(a.id, b.id) end)
          203  +				end
          204  +				if next(t) then table.insert(all, {
          205  +					id=o, list=t;
          206  +					header=fields[o].name[t[2] and 2 or 1];
          207  +				}) end
          208  +			end
          209  +			return all
          210  +		end;
   161    211   	};
   162    212   
   163    213   	__tostring = function(self)
   164    214   		local t = {}
   165    215   		for i,o in ipairs(order) do
   166         -			if self[o] then
          216  +			if self[o] and fields[o].string then
   167    217   				for mat,amt in pairs(self[o]) do
   168    218   					if amt > 0 then
   169         -						table.insert(t, strClass[o](mat, amt))
          219  +						table.insert(t, fields[o].string(mat, amt))
   170    220   					end
   171    221   				end
   172    222   			end
   173    223   		end
   174    224   		return table.concat(t, ", ")
   175    225   	end;
   176         -
   177    226   
   178    227   	__add = function(a,b)
   179    228   		local new = fab {}
   180    229   		for cat, vals in pairs(a) do
   181    230   			new[cat] = lib.tbl.copy(vals)
   182    231   		end
   183    232   		for cat, vals in pairs(b) do
   184    233   			if not new[cat] then
   185    234   				new[cat] = lib.tbl.copy(vals)
   186    235   			else
   187         -				local f = opClass[cat]
          236  +				local f = fields[cat].op
   188    237   				for k,v in pairs(vals) do
   189    238   					local n = f(new[cat][k], v, 1)
   190    239   					new[cat][k] = n > 0 and n or nil
   191    240   				end
   192    241   			end
   193    242   		end
   194    243   		return new
   195    244   	end;
   196    245   
   197    246   	__mul = function(x,n)
   198    247   		local new = fab {}
   199    248   		for cat, vals in pairs(x) do
   200    249   			new[cat] = {}
   201         -			local f = opClass[cat]
          250  +			local f = fields[cat].op
   202    251   			for k,v in pairs(vals) do
   203    252   				local num = f(v,nil,n)
   204    253   				new[cat][k] = num > 0 and num or nil
   205    254   			end
   206    255   		end
   207    256   		return new
   208    257   	end;

Added mods/starlit/food.lua version [c58f501acb].

            1  +local F = starlit.item.food
            2  +local lib = starlit.mod.lib
            3  +
            4  +function F.impact(stack,f)
            5  +	if stack and not f then f = stack:get_definition()._starlit.food end
            6  +	local impact = f.impact and starlit.type.impact.clone(f.impact) or starlit.type.impact{}
            7  +
            8  +	if impact.taste then
            9  +		local aff = impact.taste > 0 and 'good' or 'bad'
           10  +		table.insert(impact, {'taste', aff, {'morale', impact.taste}})
           11  +	end
           12  +
           13  +	if stack then
           14  +		impact = impact:effective(stack)
           15  +	end
           16  +
           17  +	return impact
           18  +end
           19  +
           20  +local function foodTip(stack, f)
           21  +	local impact = F.impact(stack,f)
           22  +	local props = impact:propTable()
           23  +
           24  +	if f.mass then
           25  +		table.insert(props, {
           26  +			title='Mass', affinity='info';
           27  +			desc = lib.math.si('g', f.mass, nil, nil, 2);
           28  +		})
           29  +	end
           30  +
           31  +	return starlit.ui.tooltip {
           32  +		title = f.name;
           33  +		desc = f.desc;
           34  +		props = props;
           35  +		color = f.color;
           36  +	};
           37  +end
           38  +
           39  +F.foreach('starlit:gen-food', {}, function(id, f)
           40  +	minetest.register_item(id, {
           41  +		type = f.itemType or 'none';
           42  +		inventory_image = f.tex;
           43  +		short_description = f.name;
           44  +		description = foodTip(nil, f);
           45  +		on_use = function(st, luser)
           46  +			local user = starlit.activeUsers[luser:get_player_name()]
           47  +			st:take_item(1)
           48  +			local imp = F.impact(st,f)
           49  +			imp:apply(user)
           50  +			user:suitSound 'starlit-success' -- FIXME need proper eating sound
           51  +			return st
           52  +		end;
           53  +		_starlit = {
           54  +			food = f;
           55  +			recover = f.recover;
           56  +			mass = f.mass;
           57  +		};
           58  +	})
           59  +end)
           60  +
           61  +starlit.item.seed.foreach('starlit:gen-seed', {}, function(id, s)
           62  +	minetest.register_item(id, {
           63  +		type = 'none';
           64  +		inventory_image = s.tex;
           65  +		short_description = s.name;
           66  +		description = starlit.ui.tooltip {
           67  +			title = s.name;
           68  +			color = s.color;
           69  +			desc = s.desc;
           70  +		};
           71  +		on_place = function()
           72  +			-- FIXME sow
           73  +		end;
           74  +		_starlit = {
           75  +			seed = s;
           76  +			mass = s.mass;
           77  +		};
           78  +	})
           79  +end)

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

   118    118   end
   119    119   
   120    120   local function abilityMenu(a)
   121    121   	-- select primary/secondary abilities or activate ritual abilities
   122    122   	local p = {kind = 'vert'}
   123    123   	for _, o in ipairs(a.order) do
   124    124   		local m = a.menu[o]
   125         -		table.insert(p, {kind='label', text=m.label, w=a.w, h = .5})
          125  +		table.insert(p, {kind='hbar', fac=0.5, text=string.format("<b>%s</b>",m.label), w=a.w, h = .5})
   126    126   		table.insert(p, wrapMenu(a.w, a.h, 1.2, 2, m.opts))
   127    127   	end
   128    128   	return p
   129    129   end
   130    130   
   131    131   local function pptrMatch(a,b)
   132    132   	if a == nil or b == nil then return false end
................................................................................
   459    459   					return true
   460    460   				end
   461    461   			end;
   462    462   		};
   463    463   	};
   464    464   })
   465    465   
          466  +-- TODO destroy suit interfaces when power runs out or suit/chip is otherwise disabled
   466    467   starlit.interface.install(starlit.type.ui {
   467    468   	id = 'starlit:compile-matter-component';
          469  +	sub = {
          470  +		suit = function(state, user, evt)
          471  +			if evt.kind == 'disrobe' then state:close()
          472  +			elseif evt.kind == 'power' and evt.mode == 'off' then state:close() end
          473  +		end;
          474  +		playerInventory = function(state,user)
          475  +			-- refresh
          476  +		end;
          477  +	};
   468    478   	pages = {
   469    479   		index = {
   470    480   			setupState = function(state, user, ctx)
   471         -				if ctx.context == 'suit' then
   472         -				end
   473    481   				state.pgm = ctx.program
          482  +				state.select = {}
          483  +				local E = starlit.mod.electronics
          484  +				if ctx.context == 'suit' then
          485  +					state.fetch = function()
          486  +						local cst = user.entity:get_inventory():get_list 'starlit_suit_chips'
          487  +						local cl = {order={}, map={}}
          488  +						for i, c in ipairs(cst) do
          489  +							if not c:is_empty() then
          490  +								local d = E.chip.read(c)
          491  +								local co = {
          492  +									stack = c;
          493  +									data = d;
          494  +								}
          495  +								table.insert(cl.order, co)
          496  +								cl.map[d.uuid] = co
          497  +							end
          498  +						end
          499  +						if state.select.chip and not cl.map[state.select.chip.data.uuid] then
          500  +							-- chip no longer available
          501  +							user:suitSound 'starlit-error'
          502  +							state.select = {}
          503  +						end
          504  +						state.select.chips = cl
          505  +
          506  +						state.select.scms = {}
          507  +						if state.select.chip then
          508  +							state.select.scms = E.chip.usableSoftware({state.select.chip.stack},nil,
          509  +								function(s) return s.sw.kind == 'schematic' end)
          510  +						end
          511  +					end
          512  +				end
          513  +			end;
          514  +
          515  +			onClose = function(state, user)
          516  +				user:suitSound 'starlit-quit'
          517  +			end;
          518  +			handle = function(state, user, q)
          519  +				local sel = state.select
          520  +				state.fetch()
          521  +				local chips = state.select.chips
          522  +				local function chirp()
          523  +					user:suitSound 'starlit-nav'
          524  +				end
          525  +				local function onPickChip(chip)
          526  +					chirp()
          527  +					sel.chip = chip
          528  +					return true
          529  +				end
          530  +				local function onPickScm(scm)
          531  +					chirp()
          532  +					sel.scm = scm
          533  +					return true
          534  +				end
          535  +
          536  +				if sel.chip == nil then
          537  +					for k in next, q do
          538  +						local id = k:match "^chip_(%d+)$"
          539  +						if id then
          540  +							local cm = chips.map[tonumber(id)]
          541  +							if cm then return onPickChip(cm) end
          542  +						end
          543  +					end
          544  +				elseif sel.scm == nil then
          545  +					if q.back then chirp() sel.chip = nil return true end
          546  +					for k in next, q do
          547  +						local id = k:match "^scm_(%d+)$"
          548  +						if id then
          549  +							local cm = state.select.scms[tonumber(id)]
          550  +							if cm then return onPickScm(cm) end
          551  +						end
          552  +					end
          553  +				else
          554  +					if q.back then chirp() sel.scm = nil return true end
          555  +				end
   474    556   			end;
          557  +
   475    558   			render = function(state, user)
          559  +				local sel, pgmSelector = state.select, {}
          560  +				state.fetch()
          561  +
          562  +				local function pushSelector(id, item, label, desc, req)
          563  +					local rh = .5
          564  +					local label = {kind = 'text', w = 10-1.5, h=1.5;
          565  +							text = '<global valign=middle>'..label }
          566  +					if req then
          567  +						label.h = label.h - rh - .2
          568  +
          569  +						local imgs = {}
          570  +						for ci,c in ipairs(req) do
          571  +							for ei, e in ipairs(c.list) do
          572  +								table.insert(imgs, {kind = 'img', w=rh, h=rh,  img=e.img})
          573  +							end
          574  +						end
          575  +						label = {kind = 'vert', w = 10-1.5, h=1.5;
          576  +							label;
          577  +							{kind ='hztl', w=10-1.5, h=rh; unpack(imgs); }
          578  +						}
          579  +					end
          580  +					table.insert(pgmSelector, {kind = 'hztl', w=10,h=1.5;
          581  +						{kind = 'contact', id=id, w=1.5, h=1.5;
          582  +							item = item;
          583  +							color = {hue=220, sat=0, lum=0};
          584  +							desc = desc;
          585  +						};
          586  +						label;
          587  +					})
          588  +				end
          589  +
          590  +				local back = {kind = 'button', id='back', label = '<- Back', w=10,h=1.2}
          591  +				if sel.chips == nil then
          592  +					table.insert(pgmSelector, {kind = 'img', img = 'starlit-ui-alert.png', w=2, h=2})
          593  +				elseif sel.chip == nil then
          594  +					for i, c in ipairs(sel.chips.order) do
          595  +					-- TODO filter out chips without schematics?
          596  +						pushSelector('chip_' .. c.data.uuid, c.stack, c.data.label)
          597  +					end
          598  +				else
          599  +					if sel.scm == nil then
          600  +						for idx, ent in ipairs(sel.scms) do
          601  +							local fab = ItemStack(ent.sw.output):get_definition()._starlit.fab
          602  +							if fab.flag.print then
          603  +								local req = fab:visualize()
          604  +								pushSelector('scm_' .. idx, ent.sw.output, ent.sw.name, nil, req)
          605  +							end
          606  +						end
          607  +						table.insert(pgmSelector, back)
          608  +					else
          609  +						local output = ItemStack(sel.scm.sw.output):get_definition()
          610  +						local fab = output._starlit.fab
          611  +						local sw = sel.scm.sw
          612  +						table.insert(pgmSelector, {kind = 'hztl', w=10, h=1.2;
          613  +							{kind = 'img', item = sw.output, w=1.2, h=1.2, desc=output.description};
          614  +							{kind = 'text', text = string.format('<global valign=middle><b>%s</b>', sw.name), w=10-1.2,h=1.2};
          615  +						})
          616  +						local inputTbl = {kind = 'vert', w=5,h=0;
          617  +							{kind = 'hbar', w=5, h=.5, text='Input'}};
          618  +						local costTbl = {kind = 'vert', w=5,h=0; spacing=.25;
          619  +							{kind = 'hbar', w=5, h=.5, text='Process'}};
          620  +						local reqPane = {kind = 'pane', id='reqPane', w=10, h=7;
          621  +							{kind = 'hztl', w=10,h=0; inputTbl, costTbl}
          622  +						}
          623  +						local req = fab:visualize()
          624  +						for ci,c in ipairs(req) do
          625  +							table.insert(inputTbl, {kind = 'label', w=4.5, h=1, x=.5;
          626  +								text=lib.str.capitalize(c.header)});
          627  +							for ei,e in ipairs(c.list) do
          628  +								table.insert(inputTbl, {kind = 'hztl', w=4, h=.5, x=1;
          629  +									{kind='img',   w=.5,h=.5, img=e.img};
          630  +									{kind='label', w=3.3,h=.5,x=.2, text=e.label};
          631  +								});
          632  +							end
          633  +						end
          634  +						if sw.cost then
          635  +							local function pushCost(t, val)
          636  +								table.insert(costTbl, {kind='text', w=4.5,h=.5,x=.5;
          637  +									text=string.format('<b>%s</b>: %s',t,val);
          638  +								})
          639  +							end
          640  +							if sw.cost.cycles then
          641  +								pushCost('Energy', lib.math.siUI('J', sel.scm.powerCost))
          642  +								pushCost('Compute', lib.math.siUI({'cycle','cycles'}, sw.cost.cycles, true))
          643  +							end
          644  +						end
          645  +						table.insert(pgmSelector, reqPane)
          646  +						table.insert(pgmSelector, {kind = 'hztl', w=10,h=1.2;
          647  +							{kind = 'button', id='back', label = '<- Back', w=5,h=1.2};
          648  +							{kind = 'button', id='print', label = 'Print ->', w=5,h=1.2, color={hue=120,sat=0,lum=0}};
          649  +						})
          650  +					end
          651  +				end
          652  +
   476    653   				return starlit.ui.build {
   477         -					kind = 'vert', padding = 0.5; w = 5, h = 5, mode = 'sw';
   478         -					{kind = 'label', w = 4, h = 1, text = 'hello'};
          654  +					kind = 'hztl', padding = 0.5; w = 20, h = 10, mode = 'sw';
          655  +					{kind = 'vert', w = 5, h = 5;
          656  +						{kind = 'hbar', fac=0, w = 5, h = .5, text = '<b><left>Recent Prints</left></b>'};
          657  +					};
          658  +					{kind = 'vert', w = 10, h = 10;
          659  +						{kind = 'hbar', fac=0, w = 10, h = .5, text = '<b>Program Select</b>'};
          660  +						{kind = 'pane', w = 10, h = 9.5, id='pgmSelect';
          661  +							unpack(pgmSelector)
          662  +						};
          663  +					};
          664  +					{kind = 'vert', w = 5, h = 10;
          665  +						{kind = 'hbar', fac=0, w = 5, h = .5, text = '<b><right>Print Queue</right></b>'};
          666  +					};
   479    667   				}
   480    668   			end;
   481    669   		};
   482    670   	};
   483    671   })

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

   105    105   					lungCapacity = .6;
   106    106   					sturdiness = 0; -- women are more fragile and thus susceptible to blunt force trauma
   107    107   					metabolism = .150; -- kCal/s
   108    108   					painTolerance = 0.4;
   109    109   					dehydration = 10e-4; -- L/s
   110    110   					speed = 1.1;
   111    111   					staminaRegen = 10.0;
   112         -					psiRegen = 1.3;
          112  +					psiRegen = 0.05; -- ψ/s
   113    113   					psiPower = 1.2;
   114    114   				};
   115    115   			};
   116    116   			male = {
   117    117   				name = 'Human Male';
   118    118   				eyeHeight = 1.6;
   119    119   				stats = {
................................................................................
   126    126   					health = 500;
   127    127   					painTolerance = 1.0;
   128    128   					lungCapacity = 1.0;
   129    129   					sturdiness = 0.3;
   130    130   					metabolism = .150; -- kCal/s
   131    131   					dehydration = 15e-4; -- L/s
   132    132   					speed = 1.0;
   133         -					psiRegen = 1.0;
          133  +					psiRegen = 0.025;
   134    134   					psiPower = 1.0;
   135    135   				};
   136    136   			};
   137    137   		};
   138    138   		traits = {};
   139    139   		abilities = {bioAbilities.sprint};
   140    140   	};

Modified mods/starlit/stats.lua from [3922a2ec73] to [0688acc2a6].

    18     18   	end
    19     19   end
    20     20   
    21     21   local function C(h, s, l)
    22     22   	return lib.color {hue = h, sat = s or 1, lum = l or .7}
    23     23   end
    24     24   starlit.world.stats = {
    25         -	psi        = {min = 0, max = 500, base = 0, desc = U('ψ', 10), color = C(320), name = 'numina'};
    26         -	-- numina is measured in daψ
           25  +	psi        = {min = 0, max = 500, base = 0, desc = U('ψ', 1), color = C(320), name = 'numina', srzType = T.decimal};
           26  +	-- numina is measured in ψ
    27     27   	warmth     = {min = -1000, max = 1000, base = 0, desc = U('°C', 10, true), color = C(5), name = 'warmth'};
    28     28   	-- warmth in measured in d°C
    29     29   	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};
    30     30   	-- fatigue is measured in minutes one needs to sleep to cure it
    31     31   	stamina    = {min = 0, max = 10 * 20, base = true, desc = U('m', 100), color = C(88), name = 'stamina'};
    32     32   	-- stamina is measured in how many 10th-nodes (== cm) one can sprint
    33     33   	nutrition  = {min = 0, max = 8000, base = 0, desc = U('kCal', 1, true), color = C(43,.5,.4), name = 'nutrition', srzType = T.decimal};

Modified mods/starlit/ui.lua from [7085b387cd] to [81aedb85b1].

    29     29   			end
    30     30   			local created = state == nil
    31     31   
    32     32   			if not state then
    33     33   				state = {
    34     34   					page = page or 'index';
    35     35   					form = self.id;
           36  +					self = self;
           37  +					close = function() self:close(user) end;
    36     38   				}
    37     39   				starlit.activeUI[user.name] = state
    38     40   				self:cb('setupState', user, ...)
    39     41   			elseif page ~= nil and state.page ~= page then
    40     42   				state.page = page
    41     43   				local psetup = self.pages[state.page].setupState
    42     44   				if psetup then psetup(state,user, ...) end
................................................................................
   148    150   			widget('container[%s,%s]%scontainer_end[]', state.x, state.y, src)
   149    151   			-- TODO alignments
   150    152   			state.x=state.x + state.spacing + st.w
   151    153   			state.h = math.max(state.h, st.h)
   152    154   		end
   153    155   		state.h = state.h + state.padding
   154    156   		state.w = state.x + state.padding/2
          157  +	elseif def.kind == 'pane' then
          158  +		widget('scroll_container[%s,%s;%s,%s;%s;vertical]',
          159  +			state.x, state.y, state.w, state.h,
          160  +			def.id)
          161  +		local y = 0
          162  +		for _, w in ipairs(def) do
          163  +			local src, st = starlit.ui.build(w, state)
          164  +			widget('container[%s,%s]%scontainer_end[]', 0, y, src)
          165  +			y=y + state.spacing + st.h
          166  +			state.w = math.max(state.w, st.w)
          167  +		end
          168  +		widget('scroll_container_end[]')
          169  +		if y > state.h then
          170  +			widget('scrollbar[%s,%s;%s,%s;vertical;%s;]',
          171  +				state.x, state.y, .5, state.h,
          172  +				def.id)
          173  +		end
          174  +		state.w = state.w + state.padding
          175  +		state.h = state.h + state.padding/2
   155    176   	elseif def.kind == 'list' then
   156    177   		local slotTypes = {
   157    178   			plain = {hue = 200, sat = -.1, lum = 0};
   158    179   			element = {hue = 20, sat = -.3, lum = 0};
   159    180   			chip = {hue = 0, sat = -1, lum = 0};
   160    181   			psi = {hue = 300, sat = 0, lum = 0};
   161    182   			power = {hue = 50, sat = 0, lum = .2};
................................................................................
   181    202   			def.w,   def.h,
   182    203   			def.idx))
   183    204   		local sm = 1
   184    205   		state.w = def.w * sm + (spac * (def.w - 1))
   185    206   		state.h = def.h * sm + (spac * (def.h - 1))
   186    207   	elseif def.kind == 'contact' then
   187    208   		if def.color then table.insert(lines, btnColorDef(def.id)) end
   188         -		widget('image_button%s[%s,%s;%s,%s;%s;%s;%s]',
          209  +		local img = def.img
          210  +		local desc
          211  +		if def.item then
          212  +			img  = ItemStack(def.item):get_name()
          213  +			desc = ItemStack(def.item):get_description()
          214  +		end
          215  +		widget('%simage_button%s[%s,%s;%s,%s;%s;%s;%s]',
          216  +			def.item and 'item_' or '',
   189    217   			def.close and '_exit' or '',
   190    218   			state.x, state.y, def.w, def.h,
   191         -			E(def.img), E(def.id), E(def.label or ''))
          219  +			E(img), E(def.id), E(def.label or ''))
          220  +		if desc and not def.desc then
          221  +			widget('tooltip[%s;%s]', E(def.id), E(desc))
          222  +		end
   192    223   	elseif def.kind == 'button' then
   193    224   		if def.color then table.insert(lines, btnColorDef(def.id)) end
   194    225   		local label = E(def.label or '')
   195    226   		if state.fg then label = lib.color(state.fg):fmt(label) end
   196    227   		widget('button%s[%s,%s;%s,%s;%s;%s]',
   197    228   			def.close and '_exit' or '',
   198    229   			state.x, state.y, def.w, def.h,
................................................................................
   210    241   		-- TODO paragraph formatter
   211    242   		widget('hypertext[%s,%s;%s,%s;%s;%s]',
   212    243   			state.x, state.y, def.w, def.h, E(def.id), E(def.text))
   213    244   	elseif def.kind == 'hbar' or def.kind == 'vbar' then -- TODO fancy image bars
   214    245   		local cl = lib.color(state.color)
   215    246   		local fg = state.fg or cl:readable(.8,1)
   216    247   		local wfac, hfac = 1,1
   217         -		local clamp = math.min(math.max(def.fac, 0), 1)
          248  +		local clamp = math.min(math.max(def.fac or 0, 0), 1)
   218    249   		if def.kind == 'hbar'
   219    250   			then wfac = wfac * clamp
   220    251   			else hfac = hfac * clamp
   221    252   		end
   222    253   		local x,y, w,h = state.x, state.y, def.w, def.h
   223    254   		widget('box[%s,%s;%s,%s;%s]',
   224    255   			x,y, w,h, cl:brighten(0.2):hex())
................................................................................
   228    259   			widget('hypertext[%s,%s;%s,%s;;%s]',
   229    260   				state.x, state.y, def.w, def.h,
   230    261   				string.format('<global halign=center valign=middle color=%s>%s', fg:hex(), E(def.text)))
   231    262   		end
   232    263   	end
   233    264   
   234    265   	if def.desc then
   235         -		widget('tooltip[%s,%s;%s,%s;%s]',
   236         -			state.x, state.y, def.w, def.h, E(def.desc))
          266  +		local coord
          267  +		if def.id then
          268  +			coord = E(def.id)
          269  +		else
          270  +			coord = string.format("%s,%s;%s,%s", state.x, state.y, def.w, def.h)
          271  +		end
          272  +		widget('tooltip[%s;%s;#000000;#ffffff]', coord, E(def.desc))
   237    273   	end
   238    274   
   239    275   	local originX = (parent and parent.x or 0)
   240    276   	local originY = (parent and parent.y or 0)
   241    277   	local l = table.concat(lines)
   242    278   	-- if state.fixed and (state.w < state.x or state.h < state.y) then
   243    279   	-- 	l = string.format('scroll_container[%s,%s;%s,%s;scroll_%s;%s]%sscroll_container_end[]',

Modified mods/vtlib/math.lua from [3edc02f291] to [43eaf4251e].

    38     38   		{12, 'T', 'tera',  true,  'p', 'pico',  true};
    39     39   		{9, 'G', 'giga',   true,  'n', 'nano',  true};
    40     40   		{6, 'M', 'mega',   true,  'μ', 'micro', true};
    41     41   		{3, 'k', 'kilo',   true,  'm', 'milli', true};
    42     42   		{2, 'h', 'hecto',  false, 'c', 'centi', true};
    43     43   		{1, 'da','deca',   false, 'd', 'deci',  false};
    44     44   	}
           45  +
           46  +	local function unitForAmt(n)
           47  +		if type(unit)=='table' then
           48  +			if n == 1
           49  +				then return unit[1]
           50  +				else return unit[2]
           51  +			end
           52  +		end
           53  +		return unit
           54  +	end
           55  +
    45     56   	for i, s in ipairs(scales) do
    46     57   		local amt, smaj, pmaj, cmaj,
    47     58   		           smin, pmin, cmin = lib.tbl.unpack(s)
           59  +
    48     60   
    49     61   		if math.abs(val) > 1 then
    50     62   			if uncommonScales or cmaj then
    51     63   				local denom = 10^amt
    52     64   				local vd = val/denom
    53     65   				if prec then vd = lib.math.trim(vd, prec) end
    54     66   				if math.abs(val) >= (10^(amt)) then
    55     67   					return string.format("%s%s%s",
    56         -						vd, (full and (' ' .. pmaj) or smaj), unit)
           68  +						vd, (full and (' ' .. pmaj) or smaj), unitForAmt(vd))
    57     69   				end
    58     70   			end
    59     71   		elseif math.abs(val) < 1 then
    60     72   			if uncommonScales or cmin then
    61     73   				local denom = 10^-amt
    62     74   				local vd = val/denom
    63     75   				if prec then vd = lib.math.trim(vd, prec) end
    64     76   				if math.abs(val) <= (10^-(amt-1)) then
    65     77   					return string.format("%s%s%s",
    66         -						vd, (full and (' ' .. pmin) or smin), unit)
           78  +						vd, (full and (' ' .. pmin) or smin), unitForAmt(vd))
    67     79   				end
    68     80   			end
    69     81   		end
    70     82   	end
    71     83   
    72         -	return string.format("%s%s", val, unit)
           84  +	return string.format("%s%s", val, unitForAmt(val))
    73     85   end
           86  +function fn.siUI(u,v,f,us,...) return fn.si(u,v,f,us,2,...) end
    74     87   
    75     88   function fn.lerp(t, a, b) return (1-t)*a + t*b end
    76     89   function fn.gradient(grad, pos)
    77     90   	local n = #grad
    78     91   	if n == 1 then return grad[1] end
    79     92   	local op = pos*(n-1)
    80     93   	local idx = math.floor(op)

Added src/sfx/alarm-urgent.csd version [d5c9b81dc6].

            1  +; [ʞ] alarm-urgent.csd
            2  +;  ~ lexi hale <lexi@hale.su>
            3  +;  🄯 CC-NC-BY-SA 3.0
            4  +;  ? VERY angry blarp fer when the bad is done to ya
            5  +
            6  +<CsoundSynthesizer>
            7  +
            8  +<CsInstruments>
            9  +#include "dqual.inc"
           10  +#include "digital.orc"
           11  +</CsInstruments>
           12  +
           13  +<CsScore>
           14  +i"chirp" 0.00 0.20 0.20 30 1700
           15  +i"chirp" 0.20 0.40 0.20 5 1000
           16  +i"blare" 0.10 0.40 0.8
           17  +i"blarp" 0.40 0.70 0.6
           18  +e 0.9
           19  +</CsScore>
           20  +
           21  +</CsoundSynthesizer>

Added src/sfx/alarm.csd version [db2a0672cb].

            1  +; [ʞ] alarm.csd
            2  +;  ~ lexi hale <lexi@hale.su>
            3  +;  🄯 CC-NC-BY-SA 3.0
            4  +;  ? angry blarp fer when the bad is done to ya
            5  +
            6  +<CsoundSynthesizer>
            7  +
            8  +<CsInstruments>
            9  +#include "dqual.inc"
           10  +#include "digital.orc"
           11  +</CsInstruments>
           12  +
           13  +<CsScore>
           14  +i"chirp" 0.00 0.40 0.20 5 1000
           15  +i"blarp" 0.20 0.70 0.6
           16  +e 0.9
           17  +</CsScore>
           18  +
           19  +</CsoundSynthesizer>

Added src/sfx/quit.csd version [c4d80c8f7f].

            1  +; [ʞ] quit.csd
            2  +;  ~ lexi hale <lexi@hale.su>
            3  +;  🄯 CC-NC-BY-SA 3.0
            4  +;  ? choopy chorp for when ya log the off
            5  +
            6  +<CsoundSynthesizer>
            7  +
            8  +<CsInstruments>
            9  +#include "dqual.inc"
           10  +#include "digital.orc"
           11  +</CsInstruments>
           12  +
           13  +<CsScore>
           14  +i"chirp"  0.00 0.10 0.10  20 4000
           15  +i"chirp"  0.15 0.20 0.10  20 2000
           16  +e 0.3
           17  +</CsScore>
           18  +
           19  +</CsoundSynthesizer>