Under parity.py the release and fast profiles have identical settings
(PROFILE: no LTO, 16 codegen units, incremental), yet 38 harnesses built
horizon-client in release and 16 others in fast: every run where the client
changed compiled it twice. They all use fast now. Release stays for
horizon-world's probes, which no fast harness builds.
Measured on playhorizon (4 CPUs), all 13 probe builds after 78 merges:
1578 s, of which the release client (bin + client_probe) took 485 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game logs into HORIZON_HOME and falls back to the player's own game folder when it is unset. Harnesses
that set only HORIZON_NO_AUDIO (and test_native_client, which set nothing) wrote logs, and on a world run
advancements, into ~/Library/Application Support/horizon.
lab.game_env gives every game a fresh HORIZON_HOME, HORIZON_LAUNCHER_HOME, XDG_DATA_HOME and XDG_CONFIG_HOME,
no audio and no launcher self-update; a folder the caller passes is kept. Every launcher of the game uses it,
parity.py gives each harness its own HORIZON_HOME, and tests/test_game_home.py fails for a script in tools/
that starts the game without it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
free_port (7 copies), offline_uuid (4), varint and parse_varint (3 and 2),
dbits (6), fbits (4) and settled (2) move into lab.py; every caller uses
lab's. The other server harnesses keep importing free_port from
test_dedicated_server_parity, which now takes lab's. Imports the moved
code left unused go too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three races made the two sides differ with nothing wrong in Horizon:
- Steps ended on a fixed 1.5 s sleep. On a slow run a chat or its
broadcast (vanilla queues the chat, then the broadcast as a later
task) landed in the next step. Each step now also ends on a barrier:
every connected client sends `random value 1..2` twice, and waits for
the answer, which only the sender gets and which is left out of the
transcript.
- The client closed every socket at once, and the server logs
"<name> lost connection" in whatever order its network threads see
the closed connections. The players still connected now leave one at
a time: each waits until it has been told the previous one left
(PlayerList.remove broadcasts that after logging). The harness waits
for Carol's line before stopping the server.
- A player the server kicks (Bob, Mallory) is logged as
"lost connection: <reason>", or as "Disconnected" when vanilla's
network thread closes the connection before disconnect(reason) runs.
The reason sent is still compared in the transcript, so the log line
no longer compares it for kicked players.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chat session updates with their profile key checked against the services'
keys, signed chat and the message chain, last-seen acknowledgements, the
signature cache, signed say/me/msg, enforce-secure-profile, and vanilla's
log lines. Offline servers and players without a chat session chat
unsigned, as on vanilla. tools/test_secure_chat_parity.py compares both
servers with test keys and a mock services server.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>