Imported from joaomcarlos/InterplanetaryLogistics (
scripts/AGENTS.md). Install upstream withnpx skills add joaomcarlos/InterplanetaryLogistics --skill scripts. Copyright stays with the author.
scripts/ — Runtime Module Contracts
Purpose
All runtime logic for the Interplanetary Logistics mod. Modules are loaded via require("scripts.<name>") from control.lua.
Ownership
Each module owns a single responsibility. Calls flow from event-driven demand discovery through multi-ship planning into vanilla-logistics execution: control → demands → router → platforms → state/util. gui reads persisted Demands and Shipments and invokes their public actions.
Local Contracts
constants.lua— Entity names, reconciliation/execution timeouts, bounded-work budgets, active statuses, schema version, history limit, and thenon_shippable_itemsblockliststate.lua— Persistent Demands (including the primarysourcefield), Shipments, Demand-owned pad sections, tracked construction entities, dirty queues, bootstrap state, fleet snapshots, GUI state, history, and schema migration. Legacyrequests/request_by_key/next_request_idaliases andactive_transfers/platform_transfersmaps are read during migration then dropped so onlydemands/demand_by_key/next_demand_idand Shipments persist. Destination registries (chests, landing pads) are runtime-only, not persisted.State.for_each_sorted_shipmentiterates a demand's shipments in sorted id order for deterministic cleanup.util.lua— Pure item, signal, surface, route, platform, destination grouping, formatting, sorting, GPS, ghost, sprite helpers, blocklist-basedis_shippable, andresolve_cargo_endpointfor Factorio 2.1 cargo pod destination/origin resolution.Util.surface_locationresolves planet surfaces to planet names and platform surfaces to the owning platform's name;Util.location_displayapplies the same platform-name translation to persistedplatform-Nsurface names, andUtil.location_richis the GUI-facing wrapper that renders planets as[space-location=]icons and everything else as translated text.demands.lua— Event-driven chest/construction discovery, tracked reconciliation, Demand lifecycle, priority, approval, suppression, retirement (cancels child shipments viaPlatforms.cancel), and fullDemands.removecleanup (cancels active demand, removes child shipments, drops pad section, clears key/suppression).Demands.scan/Demands.processare synchronous drivers over the boundedstart_scan/step_scanandstart_process/step_processjobs.router.lua— Largest-network source snapshots, deterministic multi-source/multi-ship planning, schedule eligibility, and Shipment creationplatforms.lua— Enrollment, Shipment execution, platform/landing-pad logistic sections, temporary schedules, event-driven progress, cleanup, failure lifecycle,Platforms.remove_shipment(cancels active, deletes terminal), andPlatforms.cancel(cancels child shipments, removes pad section, then marks demand cancelled)scheduler.lua— Independent bounded dirty queues, tracked reconciliation, Shipment execution (starts and steps planned shipments), Shipment maintenance, fleet snapshots, and GUI refresh scheduling. Maintenance lanes (fleet snapshots, GUI refresh) start and step unconditionally every tick — they are single-unit bounded and must never yield to dirty/routing lanes; gating them behind those lanes starved the open dashboard whenever a save kept them busy.gui.lua— High-volume dashboard with Fleet Monitor first; native navigation views for Fleet, Trade Requests, Shipments, Destinations, and History; one scroll owner per visible list; per-row and clear-all remove buttons on Trade Requests, Shipments, and Historysource_stock.lua— Same-tick exact item/quality aggregate reads from Factorio-maintained logistic-network arrays; no entity or silo discovery
Work Guidance
- Fleet Monitor must remain the first dashboard view. Automatic/manual refreshes update existing elements in place and must not replace the frame, reset navigation or selection, or move scroll position. The periodic
Gui.refresh_playerpass walks only the currently visible view's container (il-content-<view>); stale rows/stats are rebuilt via the sharedelement_stalenesspredicate, andGui.set_viewruns the same check once on the view being opened plusrefresh_summaries.element_stalenessalso checks row-container child counts (containers hold only row frames or one empty-state label) so added/removed rows — new demands, shipments, platforms, history entries — triggerrefresh_*_structurerebuilds without a manual refresh. - Native navigation exposes Fleet Monitor, Trade Requests, Shipments, Destinations, and History. Trade Requests aggregate destination Demands and merge Source and Destination into one Route column (
source-icon → destination-iconviarequest_route_caption; before dispatch it shows "Routing..." or the Demand'slast_reasonblocker followed by the destination icon); Shipments expose individual ship execution and link back to their parent Demand via therequest_idcolumn; the list shows live and failed rows only — completed shipments drop off the board (failed rows persist for triage) and remain in Transfer History. Each Trade Request row with a stored position exposes a map-locate button that opens remote view at the request position. - Never nest vertical scroll panes. Keep summaries and column headers outside the single list scroll pane for each visible view.
- Do not use nested tabbed panes for dashboard navigation. Native button navigation keeps the layout tree shallow and avoids the engine sizing recursion observed in
TabbedPane::setSize. - Every custom GUI style must specify a
parentto inherit proper default sizing from Factorio's base styles. - Treat cohesive native styles, visual hierarchy, consistent spacing, readable density, interaction states, tooltips, empty states, and responsive sizing as required implementation work, not optional follow-up polish.
- Use native utility sprites for 32 x 32 row actions and preserve rectangular buttons for text-heavy primary actions. Apply consistent blue, green, orange, red, and muted text colors to statuses, ETAs, metrics, and selected context.
- Centralize width budgets in
layout()so the navigation rail, list columns, scroll bar, and detail panels fit inside the frame at supported UI scales.fit_widthsgrows a designated flex column to fill spare space and scales every column down proportionally when base widths exceed the available width, so no view may overflow the frame. - Delivery Fleet and Other Platforms are separate sections sorted by platform name. Fleet rows merge location and destination into one Route column: docked ships show the location icon, and in-transit ships show
origin → destinationicons viasnapshot.origin(last_visited_space_location) andsnapshot.destination. Demands are ordered by priority, workflow state, then id; Shipments use deterministic status and id ordering. - Enrollment clicks and structural/value refreshes update the smallest existing subtree so dashboard navigation and scroll position remain stable.
- Requester-chest and cargo-landing-pad build/removal events refresh the open Destinations subtree and summary in place. Destination registries are runtime-only locals in
state.lua, rebuilt from world entities at game start and maintained via events; they are never persisted tostorage.on_init/on_configuration_changedrebuild directly; a normal save load skips both andon_loadcannot accessgame, socontrol.lua'son_tickcallsState.ensure_destinations()once per session (gated by thedestinations_initializedlocal) before the periodic demand scan readsState.get_chests(). - The Destinations view shows one row per planet with at least one cargo landing pad; planets with only requester chests are hidden. Each row shows Delivered and Provided totals plus the top 8 delivered item icons with counts (from persisted
destination_metrics[planet]/source_metrics[planet].provided/itemsaccumulated byPlatforms.finish_shipmenton completion), and map buttons for individual pads when fewer than 5, or a count label when 5 or more. - Every player-facing GUI caption and tooltip uses a defined
il-gui.*LocalisedString; validate withpython tests/locale_spec.py. - A Demand is an exact destination need keyed by origin identity, destination surface/network, item, and quality. It owns observed shortage, active-shipment quantity, unplanned quantity, approval, priority, denial, suppression, and a primary
sourcelocation. Tray alerts useadd_custom_alert/remove_alert{entity=, icon=, message=}viaUtil.alert_forceagainstUtil.alert_entity(persistedalert_entityLuaEntity ref — chest or representative construction entity — falling back to unit lookup; never surface searches). Demand creation spawns a new-delivery alert plus a needs-approval alert (Util.approval_icon/approval_message— identical icon and message are required for removal) when auto-approve is delayed; the approval alert is cleared on approve, deny, cancel, fulfill, and remove so it only exists while pending.maintain_shipmentspawns a loaded alert (signal-check, platform hub) on the loading→delivering transition, andfinish_shipmentspawns a delivered alert (space-locationicon, destination pad) on completion. All Demand status transitions go throughState.set_status, which stampsstatus_tick(backfilled once fromcreated_tick) so the Trade Requests status cell can show elapsed time in the current status; re-assigning the same status never resets the timer. - The Demand
sourcefield records the first pickup-leg source of the earliest active child Shipment. It is nil ("Routing...") before dispatch, set byState.create_shipment, and recomputed onState.cancel_shipment/State.delete_shipmentso it always reflects the current active shipment set. Schema 4 backfills it for existing demands; schema 5 consumes legacy transfer indexes; schema 6 reboots construction discovery once to recover references lost by global unit lookup; schema 7 repeats it once to pick up death-created ghosts that were never tracked (on_post_entity_died). - Requester-chest observed shortage subtracts exact-quality chest contents and robot deliveries already targeted to that chest. Construction observed shortage subtracts exact-quality inventory in the tracked construction logistic network.
- A Shipment assigns one enrolled ship to part of one Demand and may contain multiple ordered Pickup Legs. A Demand may own multiple concurrent Shipments.
- Source planning reads exact item/quality counts from
force.logistic_networksand uses the largest valid network count per planet. Prefer planets covering the full remainder, then the best partial counts. - Do not search surfaces for rocket silos or source entities, filter source counts to providers, preserve source reserves, or persist synthetic stock reservations. Factorio arbitrates real inventory contention.
- Within one deterministic planning operation, an ephemeral availability copy may prevent one Demand from assigning the same observed units twice; this is not persisted as source state.
- Multiple ships may fulfill one Demand. Each ship may use only source and destination planets present in its permanent schedule, and pickup order follows that schedule from its current position toward the destination. A failed planning pass must store the specific blocker on
last_reason(missing logistic-network stock, missing enrollment, missing schedule coverage, busy fleet, or no cargo space), never a generic catch-all. - Each Pickup Leg writes a planet-scoped platform-hub logistic request with a cumulative cargo target. Only append/remove temporary source and destination records; never mutate permanent records or their ordering. Per-platform
state.schedule_override[force][platform](default on, toggled from the enrolled fleet row) decides whether dispatch also movesschedule.currentto the first temporary record; when off the stops ride the permanent route and progress still resolves by hub counts andspace_location. - Failed dispatch and cleanup with no matching Shipment-owned temporary records must not assign the schedule or reset its current stop.
- Schedule-record matching accepts Factorio readback normalization:
≥/≤and>=/<=are equivalent, absent signal type means item, and absent quality means normal. Maintenance and cleanup must recognize the same Shipment-owned records after readback. - One Demand-owned logistic section requests all active Shipment cargo at a landing pad in the destination network. Try eligible pads in sorted unit-number order until a request section can be added; a full pad must not block another pad in the same network. Shipment-owned duplicate pad sections are forbidden.
- Execution failures retain a failed Shipment row, release its ship/allocation, and expose the blocker on its approved parent Demand for retry. Player cancellation/removal keeps its separate deletion behavior.
- Factorio owns bots, rocket-silo requests and launches, platform loading, orbital drops, landing-pad receipt, and local delivery. The mod observes those results and cleans up its request sections and temporary records.
- Resolve stored landing-pad unit numbers through
State.get_landing_pads()before global entity lookup; pads may lack theget-by-unit-numberprototype flag. Section reuse, Shipment validation, and cleanup share this resolver and perform no world scan. - Tracked construction entries persist a validated LuaEntity reference. Dirty processing, topology reassociation, and both reconciliation paths read that reference; global unit-number lookup cannot resolve ordinary ghosts/proxies. Schema 6 clears legacy tracking and runs the bounded existing-save bootstrap once.
- Construction demand is maintained from construction-registered entity ghosts, tile ghosts, and item-request proxies through build/remove/revive/upgrade events and death-created ghosts via on_post_entity_died. Preserve exact counts and LuaQualityPrototype names; never infer demand from alert wrapper prototypes. Only items not on the
non_shippable_itemsblocklist inconstants.luacreate Demands; blocklisted items are filtered at both chest and construction discovery.send_to_orbit_modeis not used for filtering — platform hub logistic requests bypass it. - Untracking a construction entity never retires sibling demands eagerly: the (force, surface) pair is dirty-marked and re-aggregation updates Demand quantities in place, preserving approval and active Shipments. Construction Demands retire immediately when no tracked entities remain on the surface, or later when the re-aggregated need is gone. A Demand with no resolved logistic network (
logistic_network_idnil or 0) is unscoped and may deliver to any destination pad. - The construction dirty queue is marked per (force, surface) pair, not per entity, because re-aggregation always recounts the whole surface. Re-aggregation iterates a runtime-only per-surface index of tracked keys (
tracked_by_surface) that rebuilds lazily whenever the persistedtracked_constructiontable is replaced; it also refreshes tracked positions and drops invalid entities, so untrack and reassociation do no per-entity scans of their own. - A bounded one-time bootstrap may discover existing chests, pads, ghosts, tile ghosts, and proxies after installation or schema migration. Normal operation must never repeat a world or roboport-cell construction scan.
- Immediate event dirty queues for chests, construction entities, and Shipments are independent. Low-priority reconciliation reads registered/tracked objects only and cannot block new requester-chest demand.
- Roboport topology changes dirty-mark the affected (force, surface) pair for re-aggregation; they do not trigger broad surface searches.
on_space_platform_changed_state,on_cargo_pod_delivered_cargo, andon_entity_logistic_slot_changeddrive immediate Shipment progress and destination observation. Bounded tracked reconciliation handles missed inventory/state transitions, with a delivery-confirmation timeout for cargo that never appears at its destination.- Invalid ships, pads, or schedules fail only the affected Shipment. Short pickup legs continue to later sources; partial delivery returns the remainder to the parent Demand for replanning. Maintenance backfills missing legacy hub baselines from the current observation before doing delivery arithmetic.
- If local logistics fulfill a Demand while cargo is moving, remove its pad request, clean up child Shipments, preserve onboard cargo, and safely restore each ship's permanent schedule position.
- Space-platform cargo storage is the hub's
defines.inventory.hub_maininventory; do not useLuaEntity.get_main_inventory()for platform hubs. - Quality-aware request icons use
sprite-button.quality; never assign sprite-onlyresize_to_spriteorstretch_image_to_widget_sizeproperties to a sprite button. - Initialize and migrate every persistent Demand, Shipment, tracked-entity, dirty-queue, bootstrap, fleet, history, and GUI field in
State.ensure(). - Sort every iteration that affects game state. Cache same-tick logistic-network aggregates by force, surface, item, and quality.
- Keep fleet snapshots, tracked reconciliation, Shipment maintenance, and open-GUI refreshes bounded and on separate offsets so work does not stack.
- Each history entry carries a monotonic
seqassigned inState.add_historyand snapshots the request'screated_tickso rows can show duration (created → event tick); per-row clear buttons key offseqviaState.remove_history_entry(seq), andState.clear_history()wipes the log. Both refresh viaGui.refresh_history_structure. History columns: request_id, item, amount, route, status, duration, event age, result, actions; entries logged beforecreated_tickexisted show "—" for duration. The History view also shows per-status metric chips, the top delivered items with quantities (aggregateddestination_metricsitems), and per-planetdelivered/providedratios from the same persisted metrics; the strip rebuilds viaGui.refresh_history_structurewhen refresh detects changed counts. - Trade Requests, Shipments, and History each expose a per-row red trash button and a heading clear-all button. Clear routes through
Demands.remove/Platforms.remove_shipment/State.remove_history_entry/State.clear_historyso active demands and shipments are cancelled (cleaning hub/pad sections and temporary records) before deletion; terminal rows are deleted directly. Clear-all iterates the player's force rows in sorted id order for desync safety.
Verification
lua tests/runtime_spec.lua
Child DOX Index
No children.
