Commit Graph
3407 Commits
Author SHA1 Message Date
bbassieandClaude Opus 5.5 3a81cfd94b Order fromConfig arguments config first
Controller.fromConfig and Host.fromConfig now take the config, then the
plugin infos, then the logger or connector.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 00:05:32 +00:00
bbassieandClaude Opus 5.5 ee097bfa6f Test that fromConfig saves instances changed while loading
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 23:51:22 +00:00
bbassieandClaude Opus 5.5 d7fc3aaec4 Replace bootstrap methods with async factories
Controller.bootstrap and Host.bootstrap returned constructor arguments
that the caller had to spread into new Controller/Host, and
DatastoreProvider.bootstrap did the same for datastores. They are
replaced by Controller.fromConfig, Host.fromConfig and
KeyValueDatastore/SubscribableDatastore.fromProvider, which load the
data and return the finished instance.

Closes #1126

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 23:51:22 +00:00
c3abf27266 Skip max_pollution_to_restore_trees in 2.1.18+ exchange strings (#1132)
Factorio 2.1.18 removed PollutionSettings::max_pollution_to_restore_trees,
but readPollution still read it for every version, so every map setting
after it was read 9 bytes late: enemy_attack_pollution_consumption_modifier
came out as garbage, the enemy_evolution factors shifted by one field and
enemy_expansion.enabled and both expansion distances came out null.

Read the field only for strings older than 2.1.18. Add modified_v2_1_17
and modified_v2_1_18 fixtures generated with headless 2.1.17 and 2.1.18
from the modified 2.1 settings (base game only), unit tests asserting
their full decoded output, and use the 2.1.18 string for the
load-scenario integration test on 2.1.18 and later.

Fixes #1131

Co-authored-by: ColumbusLabs <287001685+ColumbusLabs@users.noreply.github.com>
Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
2026-10-22 17:22:09 +01:00
BastiaanandClaude Opus 5.5 5d7ecff418 Accept ECONNREFUSED for the dropped HttpCloser request (#1133)
Node 26.12.1 (CVE-2026-105481) makes the http agent check a kept-alive
socket for pending data before reusing it. The queued request now sees
the server's EOF, opens a new connection to the closed server and gets
ECONNREFUSED instead of ECONNRESET. Either way the request is dropped.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 17:14:17 +01:00
ILLISIS 046f0af705 Merge pull request #1115 from ILLISIS/rs-queue-sync
Add a cluster research queue to research_sync
2026-10-21 00:19:28 +11:00
ILLISIS 5b217c38b6 Merge pull request #1114 from ILLISIS/ps-prefetch
Send player data ahead (prefetch) to the instance a player is switching to
2026-10-20 22:43:56 +11:00
Arvids Godjuks 0f201961a2 Update PR guidance to harden the checks to properly trace the PR history so model does not do followup reviews only against latest changes. (#1125) 2026-10-20 14:39:31 +03:00
ILLISISandClaude Opus 5.5 cb450690ba Restore the body's place on the download forced by a dropped copy
The download a join forces after dropping a copy that took the body found the
player already a spectator, so it kept the spectator's place and restored the
character there. It now takes the surface, position and vehicle the dropped
copy kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 13:19:49 +11:00
ILLISISandClaude Opus 5.5 0149c72639 Upload on leave while an unconfirmed prefetched copy is held
The leave handler skipped the upload for any active download, including a
prefetched copy no join had confirmed, which has replaced nothing here. Newer
local data, such as after a failed upload whose retry the join cleared, was then
never uploaded. Only a real download skips it now; a copy whose join took the
body still uploads nothing through sync_in_progress.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 13:19:29 +11:00
ILLISISandClaude Opus 5.5 3f79cdb1a6 Download when a dropped prefetched copy already took the body
A copy confirmed at an earlier join has replaced the body with a spectator.
If a later join drops it (another controller run) while the generations match,
the join kept the local state: a spectator with no body, uploaded on the next
leave. Download instead. A newer copy replacing a held one keeps where the
body stood, so the replacement still counts as having taken it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:37:00 +11:00
ILLISISandClaude Opus 5.5 9c4e354738 Check the epoch on prefetched copies
An admin import or delete bumps the stored data's epoch and can start the
generations over, so a copy held from before it must not apply. The prefetch
carries the epoch like the token, and a held copy is used only when it matches
the acquire reply's.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:27:34 +11:00
ILLISISandClaude Opus 5.5 0bd7fccdf3 Keep newer local player data over a prefetched copy
An instance with older data can upload the generation a save already holds, and a prefetch of it then overwrote that
save's newer state. A join now drops a prefetched copy unless the ordinary download check says the data is needed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:24:44 +11:00
ILLISISandClaude Opus 5.5 fdec6bc249 Explain why a prefetched copy needs the run token as well as the generation
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:24:24 +11:00
ILLISISandClaude Opus 5.5 17f029e39e Check the run token on every prefetched chunk
Chunks of an earlier controller run's copy still in flight could land in a newer run's copy of the same generation. Every chunk now carries the token, and the scenario ignores a chunk whose token differs from its record's.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:24:24 +11:00
ILLISISandClaude Opus 5.5 5e4dcada50 Tag prefetched copies with the controller's run token
If the controller crashes after sending generation N but before saving it, the destination keeps that prefetch while the controller restarts at N-1, and another instance can then upload different data as generation N. The controller now picks a random token per run, sends it with every prefetch and acquire reply, and the scenario applies a held copy only when its generation and token both match, and lets a new run's copy replace one with another token.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:24:24 +11:00
ILLISISandClaude Opus 5.5 ca7caedfbe Restore recipe notifications a prefetched copy would leave cleared
A prefetched copy is sent without the save's list of cleared recipes, so
its delta had only add, and a recipe cleared on the destination but not
in the stored list stayed cleared where a download would give it its
notification back. Without a current list the instance now sends the
whole stored list marked full, and the save gives the recipes it has
cleared that aren't in it their notification back, as a download's
remove does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:23:38 +11:00
ILLISISandClaude Opus 5.5 fe416da26c Apply a prefetched copy on join without downloading
player_sync.prefetch_inventory holds a copy sent ahead in active_downloads
and finished_downloads, for a player who is offline or doesn't exist on
the destination, with the components its first chunk names. Chunk 1
refuses a player who is online, a download in progress, a stream of the
same or a newer generation and a finished copy of a newer one; a finished
copy of the same generation is replaced. At the acquire reply a copy is
used only when the reply is acquired with its generation and its
components are the ones the scenario syncs now. The player is then
switched to spectator as a download does and the copy is applied, at once
or at its last chunk. Otherwise the copy is dropped and the join
continues as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:23:38 +11:00
ILLISISandClaude Opus 5.5 18afae4686 Add set_destination remote interface to player_sync
A plugin that moves a player calls remote.call("player_sync",
"set_destination", player_name, instance_id) before connect_to_server.
It only records the destination. Every leave clears it, and a leave
that uploads with reason switching_servers adds it to the upload as
destination_instance_id. The README describes it for plugin authors.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:23:13 +11:00
ILLISISandClaude Opus 5.5 240180d847 Stream prefetched player data into the destination save
The instance passes destination_instance_id from a leave upload on as
destinationInstanceId, and sends a PrefetchEvent's data to
player_sync.prefetch_inventory in RCON chunks, using the download's
chunk loop. As for a download, the data is stripped by the instance's
own player_sync.sync_* options and the components go with the first
chunk. A non-empty reply means the save refused or ignored the copy and
stops the transfer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:23:13 +11:00
ILLISISandClaude Opus 5.5 b8c1e89dff Forward player data to the instance a player is switching to
An upload can carry destinationInstanceId. Once the upload is stored,
the controller sends the stored data, the upload merged with its copy,
in a PrefetchEvent to that instance, if it is another running instance
with player_sync enabled. No lock is taken or released. A failed send is
logged and doesn't fail the upload.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:23:13 +11:00
ILLISISandClaude Opus 5.5 b10807b6d6 Keep a newer queue change over a queue reply it overtook
A QueueEvent arriving in the same socket read as a QueueSyncRequest reply is
applied before the reply, which then wrote the older queue over it. Skip the
reply when a QueueEvent was applied while it was in flight.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:20:36 +11:00
Cooldude2606 4128f75328 Merge pull request #1012 from bbassie/feature/inventory-sync-tools
Add inspect and modify tools for inventory sync
2026-10-20 00:43:33 +01:00
bbassieandClaude Opus 5.5 ec7b5f2db8 Rework the player sync web page after UX review
The drawer actions move to its header as Export, Import and a trash
icon for Delete. Import and Delete are disabled while an instance
holds the player, since the stored data is out of date until it
uploads. The drawer also shows color and cheat mode.

Inventory tables get title case names, pagination at 10 rows, item
search, sorting and quality icons. The player list shows force with
a filter instead of generation, and the database stats use
Descriptions without the largest entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 23:24:02 +00:00
Arvids Godjuks 37d910fe86 Add CODEOWNERS file (#1122) 2026-10-20 00:28:56 +03:00
Arvids Godjuks 360e12a560 Add quare logo (#1121) 2026-10-20 00:14:49 +03:00
Arvids Godjuks f44e36f2cf Add AGENTS.md guidance for PR reviews (#1119) 2026-10-19 23:14:35 +03:00
bbassieandClaude Opus 5.5 39df22be65 Close the remaining ways an accepted import fails to restore
Imports no longer accept the cutscene controller, which needs waypoints
to restore. Logistic filters and the basic item stack fields now have the
types and limits Factorio enforces, and the inventory summary skips
malformed items and counts repeated exported items once instead of
expanding them.

Lua's JSON loses precision past 2^50, so a generation there could no
longer be counted up and uploads were discarded. Imports now start over
from generation 1 when the stored or imported generation is at that limit.
Instances holding a copy from another epoch take the stored generation
instead of keeping their own higher one when there is no data to download.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:50:37 +00:00
ILLISISandClaude Opus 5.5 fd39e2a171 List a finished technology put back by a stale edit as a known gap
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 00:05:19 +11:00
ILLISISandClaude Opus 5.5 079740d991 Say that the research queue is shared on turning following on, not on a restart
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 00:03:48 +11:00
ILLISISandClaude Opus 5.5 6126b30350 Name the research queue version check after its API
research_queue_api is compat.version_ge("2.0.56"), named like player_sync's version checks, so it doesn't read as a
setting. Handlers check it at the top; only the three research queue events are registered behind it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 00:02:35 +11:00
ILLISISandClaude Opus 5.5 2035f5bc2c Send the research queue to followers after a finished technology is removed
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 08:54:04 +11:00
ILLISISandClaude Opus 5.5 bedfe0873b Check the queue sync version in each handler
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 08:54:04 +11:00
bbassieandClaude Opus 5.5 9ecd497f1e Reserve the deleted marker for tombstones
Imports and uploads could carry deleted: false, which the loader took for
a tombstone on the next restart. The schemas now reject the property, and
the loader only treats deleted: true as a tombstone and strips the key from
entries already saved with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:53:36 +00:00
bbassieandClaude Opus 5.5 feb3253bec Restore each imported appearance field on its own
An import with color but no chat_color or tag assigned nil to the missing
LuaPlayer properties and failed restoration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:53:36 +00:00
Cooldude2606 4fd1d56b4a Merge pull request #1106 from ILLISIS/ps-armor-grid-bonus
Keep main inventory slots from armor equipment when restoring a character
2026-10-18 20:14:02 +01:00
Arvids Godjuks 0fb9e793d7 Stabilize player-list integration tests and RCON fixtures (#1117)
* Wait for player list replication in integration tests

* Select available RCON ports before starting Factorio

* Document zero as automatic RCON port allocation

* Address review wording and test organization

* Restrict port collision hardening to integration fixtures
2026-10-18 22:01:43 +03:00
Bastiaan cbf2b10782 Add notes to hosts, instances, users and other resources (#1000) 2026-10-18 20:06:15 +03:00
Columbus LabsandColumbusLabs ee39945c83 Count send buffer size in UTF-8 bytes (#1116)
The send buffer limit in controller.max_send_buffer_size is set in MiB, but
buffered messages were measured with String.length, which counts UTF-16 code
units. Messages with non-ASCII text could go over the configured size in bytes
before the session was ended.

Measure each serialised message with Buffer.byteLength when it is buffered,
keep that size with the entry, and subtract the same size when it is
acknowledged. The web UI build already provides Buffer through the buffer
polyfill.

Fixes #1108

Co-authored-by: ColumbusLabs <287001685+ColumbusLabs@users.noreply.github.com>
2026-10-18 17:44:27 +01:00
ILLISISandClaude Opus 5.5 bf73afeab2 Describe when a server shares its research queue as the cluster queue
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 00:06:43 +11:00
ILLISISandClaude Opus 5.5 f7c78d9f74 Remove one research queue entry per level a technology sync unlocks
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 00:06:43 +11:00
ILLISISandClaude Opus 5.5 ed1e4ee3cb Fetch the cluster research queue again after a new controller session
Test the research_sync instance plugin.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 00:06:43 +11:00
ILLISISandClaude Opus 5.5 916b57b37c Raise the main inventory only by the armor equipment's bonus
The raise covered the whole gap between the serialized main inventory
and the main inventory with the armor restored, but only the bonus of
the armor's equipment arrives at the end of the tick. When the
destination force has a smaller character_inventory_slots_bonus than
the source, the rest of the gap never arrived and removing the raise on
the armor event dropped those slots on the ground. The raise is now the
equipment's inventory bonus minus what the grid already applies, at
most the gap, and the slots past it overflow into a corpse as without
the raise. Factorio 1.1 has no inventory bonus equipment and gets no
raise.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 23:19:20 +11:00
ILLISISandClaude Opus 5.5 4228b40562 Remove the inventory bonus raise on the armor inventory event
on_player_armor_inventory_changed for the restored armor is raised at
the end of the download's tick, after the inventory bonus of the armor's
equipment applied, so the raise is removed there instead of in the next
tick's on_tick. The raise is only made for armor with a grid, as the
event needs the armor to be restored.

A player leaving on the tick of a download no longer has the raise
removed from the character first, which shrank the main inventory
before the bonus applied and spilled the extra slots. The raise is
taken off the uploaded inventory bonus instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 22:34:34 +11:00
ILLISISandClaude Opus 5.5 b32f5416a1 Keep main inventory slots from armor equipment when restoring a character
The inventory bonus of equipment in the armor's grid (toolbelts) is
applied by the engine on the tick after the armor is inserted. The main
inventory was restored in the same tick, so the items of every slot past
80 plus research plus the armor's own bonus did not fit and were put in
a corpse: legendary mech armor with legendary toolbelts left 2125 of
2330 slots in the corpse.

The armor is now restored first, the character's inventory bonus is
raised to fit the saved main inventory, and the raise is removed again
on the next tick, or before an upload if the player leaves first.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 22:34:05 +11:00
ILLISISandClaude Opus 5.5 c70be25dc9 research_sync: the follow checkbox tooltip says all research progress is counted, whatever is selected
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 22:32:53 +11:00
ILLISISandClaude Opus 5.5 eb869dac5b research_sync: README, the empty-cluster case on turning on, and which changes print where
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 22:31:23 +11:00
ILLISISandClaude Opus 5.5 1f6e0519bd research_sync: README for the cluster research queue, review fixes
Document the cluster research queue in the plugin README and the root
README. Review fixes: ipairs for queue order, event annotations on the new
Lua handlers, an IpcQueue type, a test for clearing the queue, and drop the
controller import from test/missing.js.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 21:54:10 +11:00
ILLISISandClaude Opus 5.5 7175571d5b research_sync: gate the queue sync on Factorio 2.0.56 (on_research_queued)
The last event of a tick now decides which player the queue edit is attributed to.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 21:41:53 +11:00
ILLISISandClaude Opus 5.5 667f1e3c0c research_sync: follow the cluster research queue from the save, with a lab window checkbox and chat lines
The save module reports queue edits on a following tile, applies the cluster
queue (dropping unknown technologies), and offers a lab-window checkbox.
All new events are gated on Factorio 2.0+.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 21:34:35 +11:00