v1.6.5 parity arc (man/tank/boat/sound) + #139 host crash fix - #151
Merged
Merged
Conversation
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
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
v1.6.xthemed visual/play parity arc againstReference/c-- little green man (New feature: tank squishes builder on drive-over (disclosed departure from oracle) #145/Builder (little green man) sprite has no visual identity #146), tank (Boated tank renders identically to a land tank (no hull sprite) #147), boat (Boarding a boat never sets player.boat = true (host-authoritative state) #148), and sound (Real networked host and joined guest never play any gameplay sound #149, Sound plays regardless of distance from the listening player (no near/far fog gating) #150 partial/parked per Jerod's ruling)SIGABRT--HostGameEngine's off-main tick-loop mutation ofstateracedGameSession's@MainActorreads ofhostEngine.state, corruptingArrayrefcounts; fixed by caching the existingonTickRenderedsnapshot intoGameSession.hostLiveStateinsteaddocs/STATUS.mdTest plan
swift test(root): 645 + 311 tests, all pass. Two flakes seen under the full parallel run -- the pre-existing documentedhostGameEngineBroadcastsExactlyAtTheTimeLimitBoundaryTickThenNeverAgain, plus a network-port-contention flake (aSimulatedGuestDrivingOntoAMineDetonatesItAndLosesArmour, "Address already in use") -- both confirmed passing in isolation with--filterxcodebuild test -scheme "Bolo 2026" -destination "platform=macOS": 146/146 tests pass, TEST SUCCEEDEDNot tagging or merging -- version number for this accumulated work is Jerod's call.
🤖 Generated with Claude Code
https://claude.ai/code/session_01XdDqxCXosBhuFQWVkuCzrz