Merge remote-tracking branch 'origin/master' into eureka/aetheria-living-world-economy-content-b1
This commit is contained in:
commit
78b176a0ff
7 changed files with 431 additions and 6 deletions
|
|
@ -47,9 +47,9 @@ public class CombatState : BaseState
|
|||
// only a contact a member tracks has a track to fly on, so an untracked target is no target here.
|
||||
var view = _agent.View;
|
||||
var target = view.TargetOf(_agent.Ship);
|
||||
if (target == null) return;
|
||||
if (target == null) { ReleaseUnwanted(); return; }
|
||||
var track = view.TrackOf(_agent.Ship, target.Value);
|
||||
if (track == null) return;
|
||||
if (track == null) { ReleaseUnwanted(); return; }
|
||||
|
||||
_availableGroups.Clear();
|
||||
_availableLockingWeapons.Clear();
|
||||
|
|
@ -88,7 +88,9 @@ public class CombatState : BaseState
|
|||
view.Designated(_agent.Ship, weapon, target.Value) &&
|
||||
!view.Refuses(_agent.Ship, weapon, target.Value) &&
|
||||
(weapon is ConstantWeapon ||
|
||||
weapon is InstantWeapon instantWeapon && instantWeapon.CanFire))
|
||||
// A held AutoWeapon stays a candidate between its rounds (it is cooling because it is firing), so the
|
||||
// group it belongs to is judged by the same Fires verdict below, not dropped for cooling down.
|
||||
weapon is InstantWeapon instantWeapon && (instantWeapon.CanFire || weapon is AutoWeapon && weapon.Firing)))
|
||||
{
|
||||
if(weapon is LockWeapon lockWeapon)
|
||||
_availableLockingWeapons.Add(lockWeapon);
|
||||
|
|
@ -151,6 +153,7 @@ public class CombatState : BaseState
|
|||
_agent.Ship.Command.Release(weapon);
|
||||
}
|
||||
}
|
||||
ReleaseUnwanted();
|
||||
_agent.Ship.Command.Aim = toTarget;
|
||||
_agent.Ship.Command.Turn = TurnCommand.Toward(toTarget.xz);
|
||||
_agent.Accelerate(movementDirection * _agent.TopSpeed, true);//selectedGroup >= 0);
|
||||
|
|
@ -158,6 +161,19 @@ public class CombatState : BaseState
|
|||
// Fire charged guns!
|
||||
}
|
||||
|
||||
public override void OnExitState() => ReleaseUnwanted(ending: true);
|
||||
|
||||
// The AI's trigger level is what it commands this step: a weapon holds a down trigger until it is released (a gun, a lock,
|
||||
// a beam), so every held one this step has no command for is let go, whether the target is gone, untracked, out of reach,
|
||||
// its group is not the pick, or Combat has ended. Ending lets go of every held one, a weapon pulled this very step
|
||||
// (held once the pull is applied) included, since nothing after Combat will command it.
|
||||
private void ReleaseUnwanted(bool ending = false)
|
||||
{
|
||||
var command = _agent.Ship.Command;
|
||||
foreach (var weapon in _agent.Ship.Weapons)
|
||||
if ((weapon.Firing || command.Pulls(weapon)) && (ending || !command.Commands(weapon))) command.Release(weapon);
|
||||
}
|
||||
|
||||
// Overdrive to close a distant target ahead, through the same consumable gate as the vent (Agent.VentOnTrack).
|
||||
private void UseConsumables(float3 toTarget, float targetDistance)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -94,6 +94,14 @@ public sealed class PilotCommand
|
|||
// The pilot's own pending decisions, which it reads back instead of the ship (that is last step's applied state).
|
||||
public TargetRef? Designated => Designations.Count > 0 ? Designations[Designations.Count - 1] : (TargetRef?) null;
|
||||
|
||||
// Whether this pilot has commanded a trigger, pull or release, for `behavior` this step.
|
||||
public bool Commands(ActivatedBehavior behavior)
|
||||
{
|
||||
foreach (var trigger in Triggers)
|
||||
if (ReferenceEquals(trigger.Behavior, behavior)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Whether the last trigger this pilot commanded for `behavior` this step is a pull.
|
||||
public bool Pulls(ActivatedBehavior behavior)
|
||||
{
|
||||
|
|
|
|||
113
docs/research/2026-10-23-recipe-graph-structure-prior-art.md
Normal file
113
docs/research/2026-10-23-recipe-graph-structure-prior-art.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# Prior art for fork aetheria-living-world:question:consumer-demand-gear-specific-intermediates: recipe-graph structure in factory and crafting games, 2026-10-23, eyes `eyes-ag8-recipe-graphs` (session self-2026-10-23-ag8)
|
||||
|
||||
Serves: `aetheria-living-world:question:consumer-demand-gear-specific-intermediates`, for Aetheria's economy content batches.
|
||||
Operator question (verbatim): "Are you referencing crafting chains from other supply-chain/logistics oriented games like Factorio or Dyson Sphere Program? No Man's Sky apparently also gets pretty deep in the endgame." Factorio, DSP and NMS are her examples, not the set.
|
||||
Sibling file (not repeated here): `F:\Projects\Aetheria\docs\research\2026-10-22-crafting-chains-prior-art.md`. It covered X4, Anno, Vic3, EVE, Starsector, Elite BGS, RimWorld/DF, faucet-sink and Leontief, and Factorio/Satisfactory only for science-pack depth and by-product stalls. This file looks at recipe-graph shape.
|
||||
|
||||
Provenance tags: [F] = data or page opened and read this session; [S] = search-result summary only, page not opened; [M] = from memory, not fetched. Marked per claim. No recommendations.
|
||||
|
||||
## 0. Method and data provenance (read first)
|
||||
|
||||
- Recipe data for Factorio 1.1, Factorio Space Age ("2x1" dataset), Dyson Sphere Program and Satisfactory was taken from the open-source FactorioLab project's extracted data files, `public/data/<id>/data.json` at https://github.com/factoriolab/factoriolab (1.1, 2x1, dsp, sfy, coi). These are community extractions of each game's own recipe data, not the official wiki or Lua prototypes read directly. [F] Wiki-versus-extraction agreement was not cross-checked. Local copies and the scripts (`an.py`, `mil.py`) are in `C:\Users\Meta\AppData\Local\Temp\claude\F--Projects-CultLib\84318a97-ab31-4505-a924-6bc6eb76f31c\scratchpad\eyes-ag8-recipe-graphs\`.
|
||||
- Counting rules (mine, applied identically across games): technology/research and recycling recipes excluded; raw = outputs of mining-flagged recipes or items no recipe produces; "consumer products" of an item = distinct output items of recipes that take it as an input; "intermediate" = non-raw item with at least one consumer product; "single-use" = exactly one consumer product; "terminal" = non-raw with zero consumers. Buildings, equipment and vehicles are items like any other, so a hub's consumer count includes buildings, not only goods. Depth = steps from raw using the cheapest recipe (min over producing recipes of 1 + max input depth); loops (barrelling, breeding, fuel cycles) make a few depths meaningless (Factorio 1.1 "Raw fish" shows depth 9).
|
||||
- Satisfactory: the file lists default and alternate recipes together (296 recipes, no alternate flag). Two figures are given; neither is the exact default-recipe set. The all-recipes figure understates single-use (alternates add consumers); the second, a subset restricted to recipes whose id equals their single output id (125 recipes), is a rough proxy for default recipes and may drop some defaults.
|
||||
- Captain of Industry was fetched but not analysed: its file has no mining flags, so raw/depth were meaningless (1 raw, depth up to 23 through waste loops). X4 wares and GregTech were not tabulated (see section 6).
|
||||
- Shares are of intermediates that have at least one consumer; terminal counts are in section 1.
|
||||
|
||||
## 1. Shared hub intermediates versus single-use intermediates
|
||||
|
||||
Measured, per game (single-use = exactly one consumer product):
|
||||
|
||||
| Game (dataset) | Items in graph | Raws | Intermediates | Terminal items | Single-use intermediates | 5+ consumer intermediates |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Factorio 1.1 [F] | 202 | 7 | 84 | 111 | 35 (42%) | 22 (26%) |
|
||||
| Factorio Space Age [F] | 303 | 15 | 147 | 142 | 53 (36%) | 49 (33%) |
|
||||
| Dyson Sphere Program (incl. Dark Fog items) [F] | 174 | 26 | 84 | 64 | 34 (40%) | 25 (30%) |
|
||||
| Satisfactory, all recipes incl. alternates [F] | 156 | 28 | 110 | 19 | 18 (16%) | 25 (23%) |
|
||||
| Satisfactory, id-matched subset [F] | 144 | 23 | 94 | 30 | 21 (22%) | 8 (9%) |
|
||||
|
||||
Consumer-product distributions (1, 2, 3, 4, 5+): Factorio 1.1: 35/11/10/6/22. Space Age: 53/25/10/10/49. DSP: 34/12/7/6/25. Satisfactory all-recipes: 18/28/20/19/25; id-matched: 21/30/22/13/8. [F]
|
||||
|
||||
Largest hubs by distinct consumer products [F]:
|
||||
- Factorio 1.1: Steel plate 59, Electronic circuit 52, Iron gear wheel 42, Iron plate 42, Advanced circuit 35, Copper plate 21, Processing unit 20, Pipe 15, Copper cable 12, Low density structure 10, Explosives 9.
|
||||
- Space Age: Steel plate 74, Electronic circuit 51, Iron gear wheel 44, Iron plate 44, Processing unit 41, Advanced circuit 39, Tungsten plate 13, Superconductor 13.
|
||||
- DSP: Iron Ingot 26, Circuit Board 25, Processor 25, Steel 22, Super-magnetic Ring 18, Titanium Alloy 16, Plasma Exciter 12, Magnetic Coil 12, Gear 11, Quantum Chip 10.
|
||||
- Satisfactory, all recipes: Rubber 14, Quickwire 9, Sulfuric Acid 9, Plastic 9, Steel Pipe 9, Concrete 8, Aluminum Casing 8, Wire 8, Petroleum Coke 8, Nitric Acid 8, Electromagnetic Control Rod 8. Counts include alternates, so the high numbers partly reflect alternate recipes.
|
||||
- A large share of the Factorio/DSP hub counts is buildings and equipment consuming the hub (for example Steel plate: 45 of its 59 consumer products in 1.1 are not in the combat category).
|
||||
|
||||
Single-use intermediates found, by category [F, derived from the data above]: not enumerated across all 84-147 items; the weapon/ammunition ones are enumerated in section 3. The non-military single-use population was not listed.
|
||||
|
||||
## 2. Depth
|
||||
|
||||
Steps from raw to the deepest end items, cheapest-recipe depth [F]:
|
||||
- Factorio 1.1: histogram of all items by depth (0..10): 7, 10, 21, 38, 49, 41, 25, 8, 1, 1, 1. Deepest: Spidertron 10, Space science pack 8, Rocket part 7, Speed/Productivity module 3 7, Stack filter inserter 7. Electronic circuit depth 3, Advanced circuit about 4, Processing unit 5, Low density structure 3. Science packs: Military 4, Chemical 5, Utility 6, Production 6. Satellite 6. (Raw fish 9 is a loop artifact.)
|
||||
- Space Age: depth range 0..7 (36 items unreachable by my rule, which are items made only from loops/other-planet raws); deepest 7: Turbo belts/splitter, modules 3, Personal battery MK3, Mech armor. Cryogenic science pack 7, Electromagnetic 6, Metallurgic 3, Military 4. Railgun and Promethium and Agricultural science showed as unreachable (spoilage/biological loops).
|
||||
- DSP: depth 0..8; deepest Orbital Collector 8, Small Carrier Rocket 7, Accumulator (full) 7, Universe Matrix 6, Suppressing Capsule 6. Matrices: Electromagnetic 3, Energy 2, Structure 3, Information 4, Gravity 5, Universe 6. Processor 3, Quantum Chip 4, Dyson Sphere Component 5. Dark Fog end items: Corvette 6, Destroyer 6, Plasma Turret 6, Gravity Missile Set 6, Attack Drone 5.
|
||||
- Satisfactory (all recipes): depth 0..12; deepest Ficsonium Fuel Rod 12, Ficsonium 11, Project Assembly phase 5 11, Plutonium Waste 10, Ballistic Warp Drive 10, Singularity Cell 9.
|
||||
- Correlation of depth and value or tier: wiki-listed science cost is the sibling file's evidence (Factorio utility pack about 190 raw, Space Age packs up to about 477 raw per pack [F: https://wiki.factorio.com/Science_pack, as quoted in the sibling file]). In the data above, tier-defining items (science packs, matrices, project phases) sit at depth 2 to 6 in Factorio/DSP and up to 11 in Satisfactory; depth alone does not order tiers (DSP Energy Matrix depth 2 versus Gravity Matrix 5; Factorio Military pack depth 4 versus Chemical 5). Raw-cost totals are a stronger tier signal than step count in the one source that gives both (Factorio science packs).
|
||||
- NMS: the 15,600,000-unit trade goods sit at 4 to 5 recipe levels and about 20 distinct products (section 4) [F].
|
||||
|
||||
## 3. Ammunition and weapon chains
|
||||
|
||||
### Factorio (1.1 and Space Age) [F, FactorioLab data; mechanics partially also from wiki.factorio.com/Military_science_pack]
|
||||
- Military science pack: 1 Grenade + 1 Piercing rounds magazine + 2 Wall -> 2 packs, 10 s; raw per pack: iron plate 5.75, copper 0.5, coal 5, stone 10 [F wiki.factorio.com/Military_science_pack]. FFF-275 (0.17): the old recipe used a Gun turret; the author says turret was too expensive against the other ingredients, replaced by 2 Walls; Piercing rounds and Grenades stay because they are useful for defence; military upgrades consolidated so the Rocket silo no longer requires them [F https://factorio.com/blog/post/fff-275].
|
||||
- Ammo tiers (1.1 recipe inputs), all from the data: Firearm magazine <- 4 iron plate; Piercing rounds <- Firearm magazine + steel + copper; Uranium rounds <- Piercing rounds + U-238; Shotgun shells <- copper + iron; Piercing shotgun shells <- Shotgun shells + copper + steel; Cannon shell <- steel + plastic + explosives; Explosive cannon shell <- steel + plastic + explosives (2x); Uranium cannon shell <- Cannon shell + U-238; Explosive uranium cannon shell <- Explosive cannon shell + U-238; Rocket <- circuit + explosives + iron; Explosive rocket <- Rocket + explosives; Artillery shell <- 4 Explosive cannon shell + Radar + 8 explosives (Space Age: Radar + Calcite + 4 Tungsten plate + 8 explosives); Grenade <- iron + coal; Cluster grenade <- 7 Grenade + explosives + steel; Flamethrower ammo <- steel + crude oil; Atomic bomb <- 10 Rocket control unit (Space Age: Processing unit) + 10 explosives + 30 U-235 (Space Age 100).
|
||||
- Space Age ammo additions: Railgun ammo <- steel + copper cable + explosives; Tesla ammo <- Supercapacitor + plastic + electrolyte; Railgun <- tungsten plate + superconductor + quantum processor + fluoroketone; Tesla gun <- holmium plate + superconductor + plastic + electrolyte; Capture bot rocket (biter capture).
|
||||
- Intermediates exclusive to weapons: none. Computed "military-exclusive" non-combat intermediates (items whose every consumer is military): 0 in both 1.1 and Space Age. Explosives (sulfur + coal + water chain) has 9 consumer products in 1.1, 8 military and 1 non-military (Cliff explosives); in Space Age 10, 9 military and 1 non-military [F].
|
||||
- Military-category items that are intermediates to other military items, with consumer products (1.1) [F]:
|
||||
- single consumer product: Firearm magazine -> Piercing rounds; Shotgun shells -> Piercing shotgun shells; Cannon shell -> Uranium cannon shell; Rocket -> Explosive rocket; Defender capsule -> Distractor capsule; Distractor capsule -> Destroyer capsule; Rocket launcher -> Spidertron (non-military consumer).
|
||||
- two or three: Explosive cannon shell 2 (Artillery shell, Explosive uranium cannon shell); Grenade 3 (Cluster grenade, Military science pack, Cliff explosives); Piercing rounds 3 (Defender capsule, Military science pack, Uranium rounds); Laser turret 2 (Discharge defense, Personal laser defense, both equipment).
|
||||
- Space Age adds: Explosive cannon shell 1 (Explosive uranium cannon shell); Rocket launcher -> Rocket turret; Rocket turret -> Spidertron; Tesla gun -> Tesla turret; Capture bot rocket 2 (Biolab, Captive biter spawner).
|
||||
- Count: of the 11 military-category intermediates listed for 1.1, 7 have exactly one consumer product (Firearm magazine, Shotgun shells, Cannon shell, Rocket, Defender capsule, Distractor capsule, Rocket launcher); Space Age 14 listed, 10 with one consumer product (the seven above (Rocket launcher now feeds Rocket turret), plus Explosive cannon shell, Rocket turret and Tesla gun). The military end items themselves (the other 33-40 items) are terminal.
|
||||
- Hub inputs shared with civilian goods: Steel plate has 14 of 59 (1.1) / 17 of 74 (Space Age) consumer products in the military set; Coal 3 of 8 / 3 of 9; Plastic bar 2 of 4 / 4 of 7; Electronic circuit, Iron gear wheel, Iron/Copper plate feed ammo, turrets and civilian goods alike. Space-Age top-tier weapons (Railgun, Tesla gun, turrets) use Superconductor, Quantum processor, Tungsten plate, Holmium plate, which are also hub items in Space Age (Superconductor 13 consumers, Tungsten plate 13) [F].
|
||||
|
||||
### Dyson Sphere Program combat (Dark Fog update, Dec 2023) [F data; update facts S]
|
||||
- 37 items carry the `df-` prefix in the data (ammunition, turrets, ships, drones, plus Dark Fog loot-derived items such as Dark Fog Matrix, Energy Shard, Core Element, Negentropy Singularity). Counts below are of these.
|
||||
- Ammo ladders from the data: Magnum Ammo Box <- copper ingot; Titanium Ammo Box <- Magnum + titanium ingot; Superalloy Ammo Box <- Titanium Ammo Box + titanium alloy. Shell Set <- copper + Combustible Unit; High-Explosive Shell Set <- Shell Set + Explosive Unit + titanium ingot; Crystal Shell Set <- High-Explosive Shell Set + Crystal Explosive Unit + titanium alloy. Missile Set <- circuit board + copper + Combustible Unit + Engine; Supersonic Missile Set <- 2 Missile Set + Explosive Unit + processor + thruster; Gravity Missile Set <- Supersonic + Crystal Explosive Unit + strange matter. Plasma Capsule <- deuterium + graphene + magnet; Antimatter Capsule <- Plasma Capsule + antimatter + hydrogen + particle container; Jamming Capsule <- electromagnetic turbine + hydrogen + plasma exciter; Suppressing Capsule <- 2 Jamming Capsule + super-magnetic ring + titanium glass. Prototype <- circuit board + Engine + iron + plasma exciter; Attack Drone / Precision Drone <- Prototype + turbine + particle container/photon combiner + processor/circuit; Corvette, Destroyer <- processor, reinforced thruster, titanium alloy / frame material, strange matter.
|
||||
- Dark-Fog-only intermediates (non-df items whose consumers are all df items) include the Dark Fog drop-derived items above (Matrix Recombinator, Silicon-based Neuron, Assembling Machine Mk.I-III as Dark-Fog recipe inputs, Arc/Plane Smelter, Matrix Lab, Antimatter Fuel Rod) which are mostly buildings or drops rather than crafted goods; the data does not mark them as combat. Combustible Unit, Explosive Unit, Crystal Explosive Unit, High-Explosive Shell Set and similar are `df-` items consumed only by df items (see below).
|
||||
- Ammunition-ladder intermediates and their consumer products [F]: Combustible Unit 3 (all df); Explosive Unit 3 (all df); Crystal Explosive Unit 2 (all df); Magnum Ammo Box 1; Titanium Ammo Box 1; Shell Set 1; High-Explosive Shell Set 1; Missile Set 1; Supersonic Missile Set 1; Plasma Capsule 1; Jamming Capsule 1; Prototype 2 (Attack Drone, Precision Drone); Engine (df-engine) 5 (4 df + 1 non-df: Logistics Bot). So 8 of 13 military intermediates have exactly one consumer product.
|
||||
- Shared with civilian goods: the df recipes' inputs are overwhelmingly general hubs: Circuit Board (8 df recipes; 25 consumer products, 17 non-df), Steel (7; 22/15), Processor (7; 25/18), Plasma Exciter (6; 12/6), Super-magnetic Ring (5; 18/13), Titanium Alloy (4; 16/12), Copper Ingot (5; 12/7), Iron Ingot (2; 26/24). Intermediates only combat uses: the Combustible/Explosive/Crystal Explosive unit family and the capsule/ammo ladders.
|
||||
- Update facts [S, search summary only; devlog not found]: Dec 2023 "Rise of the Dark Fog" introduced a hostile NPC faction; producer Zhou Xun: combat was planned since release, attack intensity correlates with factory size; turrets, planetary shields, rebuild drones added; more space combat promised. https://news.xbox.com/en-us/2023/11/22/dyson-sphere-program-introducing-combat-system/ ; https://en.wikipedia.org/wiki/Dyson_Sphere_Program
|
||||
|
||||
### Satisfactory [F data, id-matched subset]
|
||||
- 12 equipment-category ammo items: Iron Rebar, Stun Rebar, Shatter Rebar, Explosive Rebar, Nobelisk, Gas Nobelisk, Pulse Nobelisk, Cluster Nobelisk, Nuke Nobelisk, Rifle Ammo, Homing Rifle Ammo, Turbo Rifle Ammo. Inputs: Rifle Ammo <- Copper Sheet + Smokeless Powder; Homing <- 20 Rifle Ammo + High-Speed Connector; Turbo <- 25 Rifle Ammo + Aluminum Casing + Packaged Turbofuel; Nobelisk <- Black Powder + Steel Pipe; Cluster <- 3 Nobelisk + Smokeless Powder; Nuke <- 5 Nobelisk + 20 Encased Uranium Cell + 10 Smokeless Powder + 6 AI Limiter; Explosive Rebar <- 2 Iron Rebar + 2 Smokeless Powder + 2 Steel Pipe.
|
||||
- Military-exclusive intermediates (all consumers are ammo) in that subset: 3 - Black Powder (consumers Nobelisk, Smokeless Powder), Smokeless Powder (consumers Cluster Nobelisk, Explosive Rebar, Nuke Nobelisk, Rifle Ammo), Packaged Turbofuel (consumer Turbo Rifle Ammo). Smokeless Powder is the heavy-oil-residue outlet recorded in the sibling file (section 7).
|
||||
- Shared with civilian goods: Copper Sheet, Steel Pipe, Aluminum Casing, High-Speed Connector, Encased Uranium Cell, AI Limiter are general parts.
|
||||
|
||||
### No Man's Sky weapon tech and ammo [S, thin]
|
||||
- A Steam reply says Boltcaster ammo can be crafted in batches of 1000 from ferrite dust; unverified. The Boltcaster's own build recipe is Chromatic Metal + Carbon Nanotubes; 60-round magazine, 1 round per shot; Plasma Launcher full charge gives five reloads (old guides). No verified ammo recipe or weapon-tech tree was fetched. https://steamcommunity.com/app/275850/discussions/0/1762481957323232719 ; Fandom Boltcaster page blocked.
|
||||
|
||||
## 4. Pure value ladders: No Man's Sky crafted trade goods
|
||||
|
||||
- Stasis Device and Fusion Ignitor each have a sell value of 15,600,000 units [F: https://nomansskyrecipes.com/blog/how-to-craft-stasis-device-no-mans-sky ; /how-to-craft-fusion-ignitor-no-mans-sky]. Sources disagree on the Superconductor recipe (Semiconductor + Enriched Carbon versus raw gases) [S].
|
||||
- Stasis Device tree [F]: Stasis Device = Quantum Processor + Cryogenic Chamber + Iridesite; Quantum Processor = Circuit Board (Heat Capacitor + Poly Fibre) + Superconductor (Semiconductor [Thermic Condensate + Nitrogen Salt] + Enriched Carbon); Cryogenic Chamber = Living Glass (Lubricant + 5 Glass) + Cryo-pump (HoT Ice [Enriched Carbon + Nitrogen Salt] + Thermic Condensate); Iridesite = Aronium + Magno-gold + Grantine. One tally: 27 steps (21 crafts, 6 refines), 20 distinct products (17 crafted, 3 refined), 14 raw materials; raw totals include 500 each of Sulphurine, Radon, Nitrogen, 300 Condensed Carbon, 300 Frost Crystal, 400 Gamma Root, 150 Ionised Cobalt. Depth about 5 crafted levels.
|
||||
- Fusion Ignitor tree [F]: Portable Reactor (Liquid Explosive [Acid + Unstable Gel] + Fusion Accelerant [Organic Catalyst + Nitrogen Salt]) + Quantum Processor + Geodesite (Dirty Bronze + Herox + Lemmium); 19 distinct intermediates; 15 raw lines including 600 Fungal Mould, 300 Cactus Flesh, 500 each of Sulphurine, Radon, Nitrogen, 200 Pure Ferrite.
|
||||
- Sharing between the two: Quantum Processor (Circuit Board, Superconductor), Thermic Condensate, Enriched Carbon, Nitrogen Salt are on both trees [F]. Each tree's remaining branches (Cryogenic Chamber and Iridesite; Portable Reactor and Geodesite) have no overlap in the fetched data. Whether these intermediates are consumed by tech or other products was not established in the fetched pages (the page gave no value for intermediates).
|
||||
- Economy effects, player testimony [F: Steam thread https://steamcommunity.com/app/275850/discussions/0/3729576253897554420]: a player reports a pre-nerf 2.5 billion units daily run in under an hour using 20 Fusion Igniters and 20 Stasis Devices per trading rotation plus Activated Indium; another says "everybody is funnelled into stasis/ fusion production really, to make any decent money"; another calls both "still huge money makers, but those are late game". Dev nerfs reported by players: halting oxygen sales and cutting chlorine price (oxygen-to-chlorine loop was "kind of game-breaking"), and a V4 price nerf that "nerfed every good mining based progression except mine based crafting". Duplication of stacks of these items to sell is described by players [S]. No Hello Games statement on Stasis/Fusion was found [S]. A 2026 SEO-style article claims 15.6M per item and is unsourced [S].
|
||||
- Not found: a documented list of NMS crafted-product value tiers, the share of NMS crafted products with no use other than selling, NMS developer statements on this design. Fandom and wiki.gg pages returned 402/401.
|
||||
|
||||
## 5. Developer design statements and player criticism
|
||||
|
||||
- Factorio FFF-275 (0.17 science changes) [F https://factorio.com/blog/post/fff-275]: the author keeps Engine units and Advanced circuits in the Chemical pack because "useful across many other recipes" (engines needed for pumps, trains, cars); Low density structure is in the Utility pack because it is an ingredient in several advanced personal-equipment items; Rails fit the Production pack as a late-game, high-throughput ingredient; the Gun turret was removed from the Military pack as too expensive relative to its other ingredients; Processing units in the Utility pack dropped from 3 to 2 as "just too many"; military upgrades were consolidated so the Rocket silo needs none, "a separate branch" for dealing with enemies; design principles listed: meaningful choice between tech paths, smoother difficulty, avoid forcing upgrades players won't use.
|
||||
- Factorio FFF-351 [F]: contains no statement on recipe design (checked, off-topic). Other Friday Facts on recipe sharing were not found by search [S].
|
||||
- Dyson Sphere Program: no developer devlog or interview on intermediate sharing or chain depth was found [S]. Producer Zhou Xun statement on combat intensity is above. Player discussion of centralised smelting (shared basic materials) versus per-product chains: https://steamcommunity.com/app/1366540/discussions/0/3183488858477589433 [S, opinions].
|
||||
- Satisfactory: no Coffee Stain statement on single-use parts found; only third-party description that alternate recipes come from Hard Drives (each offers a choice of two recipes) [S: https://xgamingserver.com/blog/satisfactory-best-alternate-recipes-tier-list/]. Alternate-recipe count in the data: 296 recipes against about 125 id-matched defaults [F].
|
||||
- GregTech and bloated chains: no source found that complains specifically about single-use intermediates [S]. Closest items: an FTB thread poll on GregTech (about 51% liked, 33% disliked, 236 voters) with a post complaining that GregTech's recipe changes force progression through the IC2/GregTech tree to use other mods, a gating complaint rather than a single-use one [S https://forum.feed-the-beast.com/goto/post?id=277694]; a GTNH reviewer saying each overcome inconvenience is replaced by a new one and that some resources become unneeded once automated [S, https://git.iczelia.net/journal/5-january-2024/ and moddex review]; the Bigger Industrialisation pack advertises "semi-realistic chemical chains with lots of steps, intermediates, byproducts" and simplifies chemistry before HV tier by design [S https://www.modpackindex.com/modpack/133597/bigger-industrialisation]. Factorio-mod pairs aimed at intermediate depth: Cmers' Vanilla Revamp "more use of complex intermediate products rather than large quantities of basic parts"; Baron Many Little Things; Alternative Intermediates [S, mods.factorio.com]. The GregTech canonical complaint named in the brief was not located in text.
|
||||
|
||||
## 6. Not covered
|
||||
|
||||
- X4 wares, Captain of Industry (data fetched, not analysed), GregTech/GTNH recipe tables: no counts. X4's ware-level chain shapes are in the sibling file section 1.
|
||||
- Factorio official wiki recipe tables and the game's Lua prototypes were not read; FactorioLab extractions were used. Space Age spoilage/loop items distort depth for 36 items.
|
||||
- NMS: no complete tier list of crafted products; no verified weapon/ammo tree; no count of single-use NMS intermediates.
|
||||
- DSP and Satisfactory dev statements: none found. Fandom (402/401 on every NMS and Satisfactory page tried), Paradox-style sources not retried.
|
||||
- Fetch budget: about 38 of 40 calls used.
|
||||
|
||||
## Bearing on the open question (facts only)
|
||||
|
||||
- Factorio 1.1: 35 of 84 intermediates (42%) have exactly one consumer product; 22 (26%) have five or more. Space Age: 53 of 147 (36%) and 49 (33%). DSP: 34 of 84 (40%) and 25 (30%). Satisfactory: 16% to 22% single-use depending on counting alternates.
|
||||
- Weapon and ammunition intermediates in Factorio 1.1: 11 military-category intermediates, of which 7 have a single consumer product (Firearm magazine, Shotgun shells, Cannon shell, Rocket, Defender capsule, Distractor capsule, Rocket launcher); 0 non-combat intermediates are military-exclusive (Explosives has 8 of 9 consumers military and one civilian, Cliff explosives). In Space Age, 0 military-exclusive non-combat intermediates; Explosives 9 of 10 military.
|
||||
- DSP combat ladders (Dark Fog): 8 of 13 combat intermediates have one consumer product (the ammo-box, shell-set, missile-set and capsule ladders), Combustible/Explosive/Crystal Explosive Unit are consumed only by combat items (3, 3, 2 consumers); the base inputs of combat recipes are general hubs (Circuit Board, Steel, Processor, Titanium Alloy, Super-magnetic Ring).
|
||||
- Satisfactory: 3 of 94 intermediates in the id-matched subset are ammo-exclusive (Black Powder, Smokeless Powder, Packaged Turbofuel); Smokeless Powder has 4 ammo consumers.
|
||||
- Tier ladders are mostly built by stacking the same ammo type (magazine -> piercing -> uranium; shell -> explosive -> uranium shell; Magnum -> Titanium -> Superalloy box), each stacked step being a single-consumer item; none of the three games has a weapon-only intermediate class outside those ladders and the explosives families.
|
||||
- NMS: both 15.6M-unit trade goods draw about 20 products each with shared Quantum Processor, Thermic Condensate, Enriched Carbon and Nitrogen Salt, and are sold rather than used; players report an in-hour 2.5 billion unit run before nerfs and describe the two as the standard late-game earners; no developer statement was found.
|
||||
- Factorio's own post on the military pack says it was built from items "useful across many other recipes" and treats military as an optional branch not required for the rocket.
|
||||
|
|
@ -43,7 +43,12 @@ public sealed partial class RunStartTests
|
|||
// order, ties by shot id, so every commit of a step precedes its applies (it was commit then apply, shot by shot, in fire
|
||||
// order). The two parts measured: commit-then-apply split alone a1318ee0, with the arrival sort 55ecd858; the old walk at the
|
||||
// h5 head, rebased onto master, still measures 108c93dc, so the rebase and the fire-mode property moved nothing.
|
||||
private const string TwoAiPinnedDigest = "55ecd858875ca6a5418d6badd40e71d9bbd363ca782843005483a308bcf07d74";
|
||||
// Repinned a seventh time, once (h6), for two Combat changes, each of which moves the digest alone (Soul measured them against
|
||||
// 55ecd858, the digest before): a held AutoWeapon stays a candidate between its rounds (the sweep disabled: b559789a), and Combat lets
|
||||
// go of a held weapon it no longer commands (target gone, untracked, another group the pick, Combat ended), where a held gun fired on
|
||||
// until a later step selected its group (the candidate change reverted: d9493bbe). Both together: the digest below. Releasing a beam or a
|
||||
// lock the same way (h7) moved nothing.
|
||||
private const string TwoAiPinnedDigest = "85e85c46cfc66693c819dd294ad02f3cd0eae5486c8f152a97e2e2787f854274";
|
||||
private const float TwoAiSeconds = 60f;
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ public sealed class FireControlPerWeaponTests : IDisposable
|
|||
private Rig Build(
|
||||
(ItemRotation Rotation, float Arc)[] mounts = null, bool beam = false, WeaponFuse? fuse = null, float blast = 0f,
|
||||
float range = 100f, float targetRange = 60f, float accuracy = 1f, float minHit = .2f, bool lockWeapon = false,
|
||||
bool turret = false, float energy = 0f, float heat = 0f, float visibility = 0f, int magazine = 0, bool singleAmmoBurst = false, bool decoy = false, bool charged = false, bool auto = false,
|
||||
bool turret = false, float energy = 0f, float heat = 0f, float visibility = 0f, int magazine = 0, bool singleAmmoBurst = false, bool decoy = false, bool charged = false, bool auto = false, float damage = 100f,
|
||||
float effective = float.PositiveInfinity, float damageAtMax = 0f, float velocity = 0f, float targetLayerHeight = 0f)
|
||||
{
|
||||
mounts ??= new[] { (ItemRotation.None, 0f) };
|
||||
|
|
@ -131,7 +131,7 @@ public sealed class FireControlPerWeaponTests : IDisposable
|
|||
behavior = new AutoWeaponData { Count = Constant(1), BurstTime = Constant(0), Cooldown = Constant(1), SingleAmmoBurst = singleAmmoBurst };
|
||||
else
|
||||
behavior = new InstantWeaponData { Count = Constant(1), BurstTime = Constant(0), Cooldown = Constant(1), SingleAmmoBurst = singleAmmoBurst };
|
||||
behavior.Damage = Constant(100);
|
||||
behavior.Damage = Constant(damage);
|
||||
behavior.Range = Constant(range);
|
||||
behavior.MinRange = Constant(0);
|
||||
behavior.EffectiveRange = Constant(effective);
|
||||
|
|
@ -1041,6 +1041,250 @@ public sealed class FireControlPerWeaponTests : IDisposable
|
|||
if (shots == 0) Assert.Equal(60, readyTicks);
|
||||
}
|
||||
|
||||
// ==== The AI's trigger level for an AutoWeapon ====
|
||||
|
||||
public enum Loss { NoTarget, Untracked }
|
||||
|
||||
private static void LoseTheTarget(Rig r, Loss loss)
|
||||
{
|
||||
if (loss == Loss.NoTarget) r.Shooter.SetTarget(TargetRef.None);
|
||||
else r.Shooter.VisibleEntities.Remove(r.Target);
|
||||
}
|
||||
|
||||
// A pilot that pulled an AutoWeapon and then loses its target, or its track of it, lets the trigger go: the weapon holds
|
||||
// a down trigger until told otherwise, and an early return that issued no release left it firing on at nothing. Through
|
||||
// the whole agent (Combat, then the transition out of it), and through Combat's Update alone. Kills: Combat never
|
||||
// releasing, releasing only when the target is gone (not when it is untracked), releasing a step late.
|
||||
[Theory]
|
||||
[InlineData(Loss.NoTarget)]
|
||||
[InlineData(Loss.Untracked)]
|
||||
public void ACombatPilotThatLosesItsTargetReleasesItsHeldAutoWeaponAndItFiresNoMore(Loss loss)
|
||||
{
|
||||
var r = Build(auto: true);
|
||||
var gun = (InstantWeapon) r.Gun.Weapon;
|
||||
var rounds = 0;
|
||||
gun.OnFire += _ => rounds++;
|
||||
r.Zone.Agents.Add(new Minion(r.Shooter, FactionDoctrine.Default(r.Shooter.ItemManager.GameplaySettings).Combatant));
|
||||
const float dt = 1f / 30f;
|
||||
for (var i = 0; i < 90; i++) r.Zone.Update(dt);
|
||||
Assert.True(gun.Firing, "control: the pilot holds the gun while it has a target");
|
||||
Assert.True(rounds >= 2, $"control: {rounds} rounds in 3 s at a 1 s cooldown");
|
||||
|
||||
LoseTheTarget(r, loss);
|
||||
r.Zone.Update(dt);
|
||||
var after = rounds;
|
||||
Assert.False(gun.Firing, "the trigger is let go the step the target is lost");
|
||||
for (var i = 0; i < 90; i++) r.Zone.Update(dt);
|
||||
Assert.Equal(after, rounds);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(Loss.NoTarget)]
|
||||
[InlineData(Loss.Untracked)]
|
||||
public void CombatUpdateReleasesAHeldAutoWeaponWhenItHasNoTargetOrNoTrack(Loss loss)
|
||||
{
|
||||
var r = Build(auto: true);
|
||||
var gun = (InstantWeapon) r.Gun.Weapon;
|
||||
var combat = new CombatState(new Minion(r.Shooter, FactionDoctrine.Default(r.Shooter.ItemManager.GameplaySettings).Combatant));
|
||||
gun.Activate();
|
||||
Assert.True(gun.Firing, "control: the trigger is down");
|
||||
LoseTheTarget(r, loss);
|
||||
|
||||
combat.Update(.1f);
|
||||
r.Zone.Update(.1f);
|
||||
|
||||
Assert.False(gun.Firing);
|
||||
}
|
||||
|
||||
// Combat's end lets go of what it held, wherever the end comes from.
|
||||
[Fact]
|
||||
public void CombatExitReleasesAHeldAutoWeapon()
|
||||
{
|
||||
var r = Build(auto: true);
|
||||
var gun = (InstantWeapon) r.Gun.Weapon;
|
||||
var combat = new CombatState(new Minion(r.Shooter, FactionDoctrine.Default(r.Shooter.ItemManager.GameplaySettings).Combatant));
|
||||
gun.Activate();
|
||||
Assert.True(gun.Firing, "control: the trigger is down");
|
||||
|
||||
combat.OnExitState();
|
||||
r.Zone.Update(.1f);
|
||||
|
||||
Assert.False(gun.Firing);
|
||||
}
|
||||
|
||||
// The target stands but its group is no longer a candidate (it moved out of the gun's reach): the held gun is let go
|
||||
// the same step, not left to fire at the last aim. Kills: Combat's release sweep after its group pick being lost.
|
||||
[Fact]
|
||||
public void CombatReleasesAHeldAutoWeaponWhoseTargetMovesOutOfReach()
|
||||
{
|
||||
var r = Build(auto: true);
|
||||
var gun = (InstantWeapon) r.Gun.Weapon;
|
||||
var rounds = 0;
|
||||
gun.OnFire += _ => rounds++;
|
||||
r.Zone.Agents.Add(new Minion(r.Shooter, FactionDoctrine.Default(r.Shooter.ItemManager.GameplaySettings).Combatant));
|
||||
const float dt = 1f / 30f;
|
||||
for (var i = 0; i < 90; i++) r.Zone.Update(dt);
|
||||
Assert.True(gun.Firing, "control: the pilot holds the gun while the target is in reach");
|
||||
|
||||
r.Target.Position = r.Shooter.Position + float3(0, 0, 500);
|
||||
r.Zone.Update(dt);
|
||||
var after = rounds;
|
||||
|
||||
Assert.False(gun.Firing);
|
||||
for (var i = 0; i < 30; i++) r.Zone.Update(dt);
|
||||
Assert.Equal(after, rounds);
|
||||
}
|
||||
|
||||
// A held AutoWeapon is cooling between its rounds because it is firing: its group is not dropped for cooling down, or
|
||||
// the trigger would be let go and pressed again at step granularity. Kills: releasing a held gun whose cooldown is
|
||||
// running while its target stands.
|
||||
[Fact]
|
||||
public void CombatKeepsAnAutoWeaponHeldThroughItsCooldownWhileTheTargetStands()
|
||||
{
|
||||
var r = Build(auto: true);
|
||||
var gun = (InstantWeapon) r.Gun.Weapon;
|
||||
var rounds = 0;
|
||||
gun.OnFire += _ => rounds++;
|
||||
r.Zone.Agents.Add(new Minion(r.Shooter, FactionDoctrine.Default(r.Shooter.ItemManager.GameplaySettings).Combatant));
|
||||
const float dt = 1f / 30f;
|
||||
for (var i = 0; i < 60; i++) r.Zone.Update(dt);
|
||||
var cooling = 0;
|
||||
for (var i = 0; i < 240; i++)
|
||||
{
|
||||
r.Zone.Update(dt);
|
||||
Assert.True(gun.Firing, $"step {i}: the held gun was let go with its target standing");
|
||||
if (!gun.CanFire) cooling++;
|
||||
}
|
||||
Assert.True(cooling > 100, $"{cooling} of 240 steps spent cooling");
|
||||
Assert.InRange(rounds, 8, 10);
|
||||
}
|
||||
|
||||
private static CombatState CombatOf(Rig r) =>
|
||||
new CombatState(new Minion(r.Shooter, FactionDoctrine.Default(r.Shooter.ItemManager.GameplaySettings).Combatant));
|
||||
|
||||
// Every weapon that holds a down trigger is Combat's to let go, not only an AutoWeapon: a beam the pilot holds keeps firing
|
||||
// at nothing otherwise. Through the whole agent (Minion in Zone.Agents, Zone.Update). Kills: a release sweep that
|
||||
// reads one weapon kind.
|
||||
[Theory]
|
||||
[InlineData(Loss.NoTarget)]
|
||||
[InlineData(Loss.Untracked)]
|
||||
public void ACombatPilotThatLosesItsTargetReleasesItsHeldBeam(Loss loss)
|
||||
{
|
||||
var r = Build(beam: true);
|
||||
var beam = (ConstantWeapon) r.Gun.Weapon;
|
||||
r.Zone.Agents.Add(new Minion(r.Shooter, FactionDoctrine.Default(r.Shooter.ItemManager.GameplaySettings).Combatant));
|
||||
const float dt = 1f / 30f;
|
||||
for (var i = 0; i < 30; i++) r.Zone.Update(dt);
|
||||
Assert.True(beam.Firing, "control: the pilot holds the beam while it has a target");
|
||||
|
||||
LoseTheTarget(r, loss);
|
||||
r.Zone.Update(dt);
|
||||
Assert.False(beam.Firing, "the beam is let go the step the target is lost");
|
||||
for (var i = 0; i < 30; i++) r.Zone.Update(dt);
|
||||
Assert.False(beam.Firing);
|
||||
}
|
||||
|
||||
// A held lock launcher is let go the same way.
|
||||
[Fact]
|
||||
public void CombatUpdateReleasesAHeldLockWeaponWhenItHasNoTarget()
|
||||
{
|
||||
var r = Build(lockWeapon: true);
|
||||
var launcher = r.Gun.Weapon;
|
||||
var combat = CombatOf(r);
|
||||
launcher.Activate();
|
||||
Assert.True(launcher.Firing, "control: the trigger is down");
|
||||
LoseTheTarget(r, Loss.NoTarget);
|
||||
|
||||
combat.Update(.1f);
|
||||
r.Zone.Update(.1f);
|
||||
|
||||
Assert.False(launcher.Firing);
|
||||
}
|
||||
|
||||
// The target stands and a group is a candidate, but a stronger group is the pick: the held gun in the other group is let
|
||||
// go the same step. Kills: a final release that runs only when no group is picked.
|
||||
[Fact]
|
||||
public void CombatReleasesAHeldAutoWeaponWhoseGroupIsNotThePick()
|
||||
{
|
||||
var r = Build(auto: true, damage: 40f, decoy: true);
|
||||
var gun = (InstantWeapon) r.Gun.Weapon;
|
||||
var combat = CombatOf(r);
|
||||
gun.Activate();
|
||||
Assert.True(gun.Firing, "control: the trigger is down");
|
||||
|
||||
combat.Update(.1f);
|
||||
|
||||
Assert.True(r.Shooter.Command.Pulls(r.Decoy.Weapon), "control: the stronger group is the pick and fires");
|
||||
Assert.False(r.Shooter.Command.Pulls(gun));
|
||||
r.Zone.Update(.1f);
|
||||
Assert.False(gun.Firing);
|
||||
}
|
||||
|
||||
// Which cooling gun stays a candidate: a held AutoWeapon only. A plain gun that is down and cooling, and an AutoWeapon
|
||||
// let go and cooling, are not, so the ready decoy is the pick; the held AutoWeapon's group is. Kills: dropping the
|
||||
// Firing clause, widening it to every gun, or widening the candidate to every AutoWeapon.
|
||||
[Theory]
|
||||
[InlineData(false, true, false)]
|
||||
[InlineData(true, true, true)]
|
||||
[InlineData(true, false, false)]
|
||||
public void ACoolingGunIsACandidateOnlyWhileAnAutoWeaponIsHeld(bool auto, bool held, bool gunIsThePick)
|
||||
{
|
||||
var r = Build(auto: auto, decoy: true);
|
||||
var gun = (InstantWeapon) r.Gun.Weapon;
|
||||
Aim(r, float2(0, 1));
|
||||
gun.Activate();
|
||||
r.Zone.Update(.1f);
|
||||
if (!held) gun.Deactivate();
|
||||
Assert.False(gun.CanFire, "fixture: the gun fired and is cooling");
|
||||
Assert.Equal(held, gun.Firing);
|
||||
var combat = CombatOf(r);
|
||||
|
||||
combat.Update(.1f);
|
||||
|
||||
var command = r.Shooter.Command;
|
||||
Assert.Equal(gunIsThePick, command.Pulls(gun));
|
||||
Assert.Equal(!gunIsThePick, command.Pulls(r.Decoy.Weapon));
|
||||
}
|
||||
|
||||
// Combat's end lets go of a weapon it pulled this very step, which is not held until the pull is applied.
|
||||
// Kills: an exit sweep that spares what the pilot commanded.
|
||||
[Fact]
|
||||
public void CombatExitReleasesAWeaponItPulledInTheSameStep()
|
||||
{
|
||||
var r = Build(auto: true);
|
||||
var gun = (InstantWeapon) r.Gun.Weapon;
|
||||
var combat = CombatOf(r);
|
||||
|
||||
combat.Update(.1f);
|
||||
Assert.True(r.Shooter.Command.Pulls(gun), "control: Combat pulled the gun this step");
|
||||
combat.OnExitState();
|
||||
r.Zone.Update(.1f);
|
||||
|
||||
Assert.False(gun.Firing);
|
||||
}
|
||||
|
||||
// The pilot has commanded a weapon this step when any trigger, pull or release, names it: not only the last one, and not
|
||||
// only a pull.
|
||||
[Fact]
|
||||
public void ACommandNamesEveryWeaponAnyOfItsTriggersNamed()
|
||||
{
|
||||
var r = Build(mounts: new[] { (ItemRotation.None, 0f), (ItemRotation.None, 0f) }, decoy: true);
|
||||
var command = r.Shooter.Command;
|
||||
var pulled = r.Guns[0].Weapon;
|
||||
var released = r.Guns[1].Weapon;
|
||||
var untouched = r.Decoy.Weapon;
|
||||
Assert.False(command.Commands(pulled));
|
||||
|
||||
command.Pull(pulled);
|
||||
command.Release(released);
|
||||
|
||||
Assert.True(command.Commands(pulled));
|
||||
Assert.True(command.Commands(released));
|
||||
Assert.False(command.Commands(untouched));
|
||||
Assert.True(command.Pulls(pulled));
|
||||
Assert.False(command.Pulls(released));
|
||||
}
|
||||
|
||||
// Kills: Trigger that leaves _burstStarted true from the previous burst, so the next burst reads as started
|
||||
// (and ready) before its first round has flown.
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -967,6 +967,32 @@ public sealed class LoadoutTests : IDisposable
|
|||
Assert.Equal(1f, effect.Evaluate(undeclared), 3);
|
||||
}
|
||||
|
||||
// Creating a behaviour reads the fire mode from the data wherever the weapon is carried: a consumable's automatic gun is an
|
||||
// AutoWeapon that repeats, every other kind a plain semi-automatic InstantWeapon. Kills: the consumable CreateInstance
|
||||
// ignoring Automatic.
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public void CreatingABehaviourFromAConsumableHonoursAutomatic(bool automatic)
|
||||
{
|
||||
using var cache = AetheriaStores.Open(Catalog, catalogWritable: true);
|
||||
var maker = cache.RefOf(cache.GetByName<Faction>("Maker"));
|
||||
var hullData = cache.GetByName<HullData>("Skiff");
|
||||
InstantWeaponData gun = automatic ? new AutoWeaponData() : new InstantWeaponData();
|
||||
var consumableRef = cache.Upsert(new ConsumableItemData { Name = "Dose", Duration = 10, Behaviors = { gun } });
|
||||
cache.FlushAsync().Wait();
|
||||
|
||||
var items = new ItemManager(1u, cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var hullItem = (EquippableItem) items.CreateInstance(items.CreateLot(hullData, maker, .5f));
|
||||
var ship = new Ship(items, null, hullItem, new EntitySettings());
|
||||
var consumableItem = (ConsumableItem) items.CreateInstance(items.CreateLot(cache.Get(consumableRef), maker, .5f));
|
||||
|
||||
var behavior = new ConsumableItemEffect(consumableItem, ship).Behaviors.Single();
|
||||
|
||||
Assert.True(automatic == (behavior is AutoWeapon), $"automatic {automatic}: created a {behavior.GetType().Name}");
|
||||
Assert.IsAssignableFrom<InstantWeapon>(behavior);
|
||||
}
|
||||
|
||||
// F4: CreateLot(product) fills each of the design's roles from the product's own per-role spread
|
||||
// (FactionProductData.Roles), not the design-wide default.
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -83,4 +83,17 @@ public sealed class SemiAutoCatalogTests
|
|||
Assert.True(auto.Automatic);
|
||||
Assert.Empty(InstantWeaponData.SemiAutoFaults(new ItemData[] { Gun("Auto", auto), new ConsumableItemData { Name = "Auto dose", Behaviors = { auto } } }));
|
||||
}
|
||||
|
||||
// The floor is the 0.1 s constant itself, not a function of the tick: a 0.08 s semi-auto gun is refused at a step length
|
||||
// whose own multiple (2 x GameStep at 30 Hz is 0.067 s) would let it through, and refused at the constant's edge from
|
||||
// below however the step moves. Kills: SemiAutoFaults comparing against a step-derived value.
|
||||
[Fact]
|
||||
public void TheFloorIsTheConstantNotAMultipleOfTheStep()
|
||||
{
|
||||
Assert.NotEqual(InstantWeaponData.ShortestSemiAutoCooldown, SimClock.GameStep * 2f);
|
||||
Assert.True(SimClock.GameStep * 2f < .08f, "fixture: a step-derived floor would let 0.08 s through");
|
||||
Assert.Single(InstantWeaponData.SemiAutoFaults(new[] { Gun("Eighty", new InstantWeaponData { Cooldown = Const(.08f) }) }));
|
||||
Assert.Single(InstantWeaponData.SemiAutoFaults(new[] { Gun("Just under", new InstantWeaponData { Cooldown = Const(.0999f) }) }));
|
||||
Assert.Empty(InstantWeaponData.SemiAutoFaults(new[] { Gun("Just over", new InstantWeaponData { Cooldown = Const(.1001f) }) }));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue