The recipe conveyors (game_conveyor_<product>_epb) are placed as named child GameObjects
in the island_* prefabs (islands_assets_all.bundle), NOT via energy_grid (power grid only)
or any config asset. New extractor bundle/extract_conveyor_placements.py walks every island
prefab and cross-references production_lines.json -> extracted/conveyor_placements.json.
Result: Factorio (energyRods, 80mmT3Cannon, contactGrenades, armorPiercingRocket),
Kaiserplatz (energyRods, 40mmT3Cannon), Demo_Wunderinsel (70mmT3Cannon), DeusExMashineSmall
(computingModules, coralDust), testIsland(+Tramplers) (the 4 base test conveyors).
Corrects the earlier "Armory = Sprengstofffabrik hosts explosives" guess: the
LittleFactory/LittleFactoryArmory islands place NO recipe conveyors; grenades/rockets are
on Factorio. Unplaced blueprints: explosiveSmall, mechanicalParts (+ the plural
armorPiercingRockets, whose singular-named instance is on Factorio).
Bulk-decompiled ~17.2k combat/system functions (Ghidra 11.1.2 headless) and confirmed
via a second toolchain what the capstone analysis found:
- GetDamage is a pure component read (returns *(comp+0x10), 0 if absent), no constants.
- PlainDamgeDealerComponent has CopyTo -> it's network-replicated (snapshot from server);
the client receives damage, never computes it.
- No client producer writes a damage value (GetDamage has no real combat caller; the rich
calc factory has 0 callers).
Conclusion documented: per-weapon damage numbers are server-authoritative runtime Entitas
component values, assigned via fully-generic dispatch with no static class/index/string
anchor -> not statically extractable from GameAssembly.dll. Trackable static artifacts are
the model + formula RVAs.
Adds reusable pipeline: reverse/ghidra_decomp_targets.py, reverse/find_damage_writes.py.
Mapped the damage system end-to-end statically and documented it for cross-patch
tracking (docs/WEAPON_DAMAGE.md):
- Model: 8 DamageXxxDataComponent (value @+0x10) on item/ammo, read by
HealthAndDamageExtensions.GetDamage (RVA 0x4BAC520); per-shot formula in
<GetDamage>d__12.MoveNext (RVA 0x4BB3DB0) = base x range-falloff x headshot,
melee skips range falloff.
- Delivery: PlainDamgeDealerComponent{damageAmount,damageType,isMelee} -> HitEventInfo
-> reduces HealthDataComponent.value; networked via DamageEvent.
Verified the base numbers are in NO asset (blueprints/ammo/projectiles/CheatItemDefs/all
bundles UTF-16). Established WHY the literal constants aren't statically anchorable: this
build accesses every component via fully-generic Entitas dispatch (no static class/index/
string reference in producing code; typed setters all dead build-wide; item-id strings
have 0 refs, verified via a calibrated string-xref) and damage resolution is server-
authoritative. So the value is a runtime component, not a reachable static constant.
Corrects the earlier draft that overstated "no value exists".
Tools: reverse/il2cpp_re.py (+find_rip_refs_batch, scan_movss_consts),
bundle/component_census.py, bundle/dump_blueprint.py.
The fixed single-recipe world structures (conveyors, e.g. 1 Raw Aurogen
Crystal -> 10 Energy Rods) are a separate mechanic from workbench recipes.
Document where they live and how to read them, with a reproducible extractor.
- docs/PRODUCTION_LINES.md: the 'where to find it' record — ECS classes
(ProductionLineRecipeComponent -> CraftingRecipe), the epb_assets_all
game_conveyor_*_epb prefabs, EntityBlueprint/Odin serialization, the
extraction path, and the open energy_grid_*/island-placement lead.
- bundle/extract_production_lines.py: UnityPy + odin_read extractor ->
extracted/production_lines.json (14 conveyor recipes).
- BUNDLES.md: add production_lines.json to the data-source map.