Skip to content

v1.6.5 parity arc (man/tank/boat/sound) + #139 host crash fix - #151

Merged
CosmicCEO merged 15 commits into
mainfrom
v1.6.1-visual-polish
Sep 24, 2026
Merged

CosmicCEO merged 15 commits into
mainfrom
v1.6.1-visual-polish

Conversation

@CosmicCEO

Copy link
Copy Markdown
Owner

Summary

Test plan

  • swift test (root): 645 + 311 tests, all pass. Two flakes seen under the full parallel run -- the pre-existing documented hostGameEngineBroadcastsExactlyAtTheTimeLimitBoundaryTickThenNeverAgain, plus a network-port-contention flake (aSimulatedGuestDrivingOntoAMineDetonatesItAndLosesArmour, "Address already in use") -- both confirmed passing in isolation with --filter
  • xcodebuild test -scheme "Bolo 2026" -destination "platform=macOS": 146/146 tests pass, TEST SUCCEEDED

Not tagging or merging -- version number for this accumulated work is Jerod's call.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XdDqxCXosBhuFQWVkuCzrz

claude and others added 15 commits September 23, 2026 20:08
Milestone 23 (man) scoped with #145/#146 from a live code-vs-oracle
audit. Milestones 24-26 (tank/boat/sound) scaffolded but empty --
checked the full open-issue backlog and nothing fits; each needs its
own audit pass before real issues can be filed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XdDqxCXosBhuFQWVkuCzrz
BUILD0/BUILD1 (walk cycle) render a head/torso/arms/legs figure with
stride alternating between the two frames; BUILD2 (parachute) renders a
dome canopy over a smaller hanging figure. Covered by a new test
asserting all three frames are non-empty and structurally distinct.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XdDqxCXosBhuFQWVkuCzrz
Disclosed departure from Reference/c (tankcollision() has zero builder-
position awareness, and no oracle call site kills a builder via tank
contact -- only via explosion radius). Added per Jerod's memory of this
mechanic in the original Stuart Cheshire game. tankCrushBuilder() checks
exact tile overlap, gated by testAlliance so a tank never crushes its own
or an allied builder, and reuses killBuilder for the actual kill/respawn.
Wired into tankMoveTick's alive branch after terrain collision resolves.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XdDqxCXosBhuFQWVkuCzrz
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XdDqxCXosBhuFQWVkuCzrz
GlyphRole.tank gained a boat: Bool parameter, threaded from
ImageIndex.swift's already-correct PTKB/FTKB/ETKB vs PTNK/FTNK/ETNK row
selection (which previously dispatched both rows to the identical
glyph). drawBoatHull reuses the land tank's bow triangle but adds a
wide flat stern deck (negative-length fillRotatedBar) and a thin
waterline band instead of the forward gun barrel. Covered by two new
tests confirming the hull differs from the land tank and still follows
dir2vec.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XdDqxCXosBhuFQWVkuCzrz
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XdDqxCXosBhuFQWVkuCzrz
Fixes the root cause behind "boats don't look like boats" (live user
report): no player, under any connection topology, could ever board a
boat -- state.players[player].boat was only ever set true by spawning
directly onto a boat start-tile. grabTile's .boat case (host's own
local player + host-simulated remotes) and recvClGrabTile's .boat case
(real remote guests over TCP) both cleared the terrain and, for the
network path, broadcast SRGrabBoat, but neither updated the host's own
authoritative state.players[player].boat -- so the boat-hull sprite
(#147) never had a state to key off of. The requesting guest's own
client-side recvSrGrabBoat set it correctly on that one machine, which
is why this shipped unnoticed: it looked right locally while the
host's copy, and every other player's view of that guest, stayed wrong.

Extended the two existing tests that already exercised these code
paths (enterTileBoatTerrainPickupWithoutBoat,
recvClGrabTileBoatTerrainBecomesRiver) to assert the flag, since their
prior absence of that assertion is exactly how this shipped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XdDqxCXosBhuFQWVkuCzrz
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XdDqxCXosBhuFQWVkuCzrz
#149: a real networked host and a joined guest played zero gameplay
sound -- SoundPlayer.shared.play(...) was only ever reachable from
GameSession.tick(), which has exactly one caller (the solo/single-
process DispatchSource timer). HostGameEngine.onShouldPlaySound is a
new property, wired the same way onTickRendered/onMessageReceived
already are, threading the same 15 sound-only runTick hooks through
HostGameEngine's own tick() (queued during the synchronous runTick
call, flushed on the main actor after, same shape pending/
maskedPending already use) plus the separate .localLayMineKeyDown
event path that bypasses runTick entirely. Join path wired for its
three already-existing local-prediction call sites (tankMoveTick/
builderTick/shellTick).

#150: near/far selection was entirely unwired even though the fog
machinery (FogState/isFog) already exists and is live on the host path
whenever Hidden Mines is on. isNear(at:) reuses that machinery as-is
(no new distance proxy) for the 7 location-bearing runTick hooks;
SoundPlayer.play(_:near:) resolves the actual far-name asset. Caught a
real nested-inout-exclusivity crash during testing (isNear originally
read the live state.hiddenMines from inside a closure firing during
runTick's own exclusive access) and a real near/far polarity bug
(isFog returns true when a tile IS fogged, i.e. far -- inverted in the
first pass) via two new HostGameEngineTests that exercise both through
a live tick loop, not just unit-test the helper in isolation.

Both issues disclose real remaining gaps rather than overclaiming: 8 of
the 16 sound hooks have no location parameter to gate on at all, and
near/far stays "near always" outside Hidden Mines/host (the solo and
join paths track no fog at all) -- the larger always-on vision-system
project D65 already flagged, not silently expanded here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XdDqxCXosBhuFQWVkuCzrz
Solo play has no second listener for far-sound distinction to matter
to; the unconditional always-on fog/vision extension is parked in the
Decide: Oracle parking lot milestone, not fixed. v1.6.5 (Sound Parity)
is done -- closes out all four 1.6.x themed parity releases.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XdDqxCXosBhuFQWVkuCzrz
GameSession.adminState/liveState read hostEngine.state directly -- a plain
stored property mutated off-main by HostGameEngine's tick-loop Task. Reading
it synchronously from @mainactor raced that mutation and corrupted Array
refcounts on GameState.local, aborting after a long-idle hosted session
(crash log ips: SIGABRT in Array._makeMutableAndUnique during tankLocalTick).

Fix: cache the already-safe value-type snapshot HostGameEngine hands to
onTickRendered (documented as the one sanctioned way to get state off the
tick loop) into a new hostLiveState property, and read that instead.

146/146 Bolo 2026Tests and 956/956 SwiftPM tests pass (one pre-existing
timing flake confirmed unrelated, passes in isolation).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XdDqxCXosBhuFQWVkuCzrz
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XdDqxCXosBhuFQWVkuCzrz
…6.x parity arc

README's version-status lines still said v1.5.0/v1.5.1-in-flight/v1.6.0-next-sprint.
v1.6.0 (Metal renderer) has since tagged and released, v1.6.1 merged to main, and this
branch's full v1.6.x themed parity arc (man/tank/boat/sound, milestones 23-26) plus the
#139 host-engine crash fix are done but not yet tagged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XdDqxCXosBhuFQWVkuCzrz
@CosmicCEO CosmicCEO changed the title v1.6.x parity arc (man/tank/boat/sound) + #139 host crash fix v1.6.5 parity arc (man/tank/boat/sound) + #139 host crash fix Sep 24, 2026
@CosmicCEO
CosmicCEO merged commit 6b77063 into main Sep 24, 2026
0 of 2 checks passed
@CosmicCEO
CosmicCEO deleted the v1.6.1-visual-polish branch September 28, 2026 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants