Skip to content

Add visual effects, audio assets, and refactor FTUE namespace#499

Open
YsKhan61 wants to merge 1596 commits into
masterfrom
claude/fix-party-lobby-connection-voFVM
Open

Add visual effects, audio assets, and refactor FTUE namespace#499
YsKhan61 wants to merge 1596 commits into
masterfrom
claude/fix-party-lobby-connection-voFVM

Conversation

@YsKhan61

Copy link
Copy Markdown
Contributor

Summary

This PR adds a comprehensive effects library with visual effects prefabs and materials, introduces new audio assets, adds UI graphics for arcade and hangar screens, and refactors the FTUE (First Time User Experience) module to remove the CosmicShore.FTUE namespace dependency.

Key Changes

Visual Effects & Assets

  • Added Froglet Effects Library with 5 VFX prefabs:
    • vfx_Lightning_01 and vfx_Lightning_02 for electrical effects
    • vfx_Projectile_01 and vfx_Projectile_02 for projectile impacts
    • vfx_Electricity_01 for general electrical effects
  • Added 10 materials for particle effects (Circle, DistortedFlare, Flame, Flare, Swirl variants)
  • Added texture assets for effects (Circle01, DistortedFlare01, Flame02, Flare00, Swirl01)
  • Added 3D model asset (Mountains01.fbx) for environmental effects
  • Added Universal Render Pipeline settings and global volume profile for effects rendering

Graphics & UI Assets

  • Added 40+ UI graphics for arcade screen interface
  • Added 20+ UI graphics for hangar/vessel selection screen
  • Added 5 element shape icons (charge pentagon, mass triangle, space kite, time diamond, pip tick)
  • Added profile icon graphics and UI components
  • Added ForcefieldCrackle shader and HLSL implementation for visual effects

Audio Assets

  • Added 9 SFX audio files for Cosmic Shore gameplay:
    • Boost, block break, click, laser, switch screen, drift, shield, and ship impact sounds

FTUE Refactoring

  • Removed CosmicShore.FTUE namespace from all FTUE-related classes:
    • TutorialSequenceSet.cs
    • FTUEIntroAnimatorAdapter.cs
    • FTUEProgress.cs
    • TutorialStepType.cs
    • ITutorialExecutor.cs
    • TutorialPhase.cs
    • ITutorialStepHandler.cs
    • ITutorialUIView.cs
    • IFlowController.cs
    • IAnimator.cs
    • ITutorialStepExecutor.cs
    • TutorialFlowController.cs
    • TutorialUIViewAdapter.cs
    • TutorialStep.cs
    • TutorialUIView.cs
    • FTUEIntroAnimator.cs
    • FreestylePromptHandler.cs
    • IntroWelcomeHandler.cs
    • LockModesExceptFreestyleHandler.cs
    • OpenArcadeMenuHandler.cs
    • TutorialSection.cs
    • TutorialStepPayload.cs
    • FTUEEventManager.cs
    • IOutroHandler.cs
    • InGameTutorialFlowView.cs

Configuration & Setup

  • Added ToastNotificationSetup.cs editor script for notification system configuration
  • Added ToastNotificationSettings.asset and ToastNotificationChannel.asset for notification management
  • Added GameFeedChannel.asset and ReflexSettings.asset for game systems
  • Updated .gitignore to exclude build artifacts

Code Cleanup

  • Removed temporary CMake build files from .utmp directory
  • Removed SoapGenerated.meta folder reference
  • Updated various material references in block materials and shader graphs
  • Minor formatting updates to several editor and plugin scripts

Notable Implementation Details

  • The effects library is organized with separate folders for materials, prefabs, textures, models, and settings
  • FTUE classes now use global namespace instead of nested namespace, simplifying imports
  • New shader graph implementation for forcefield crackle visual effect
  • Audio assets follow Cosmic Shore naming convention for consistency

https://claude.ai/code/session_01BC7t43yv45U5EBxKhGWsQV

YsKhan61 and others added 30 commits March 28, 2026 04:36
…evaluation-y1Nvy

Refactor PrismManagers prefab structure and pool initialization
The client relied solely on NetworkVariable.OnValueChanged to trigger
local track spawning, which is unreliable for scene-placed
NetworkObjects in NGO 2.5.0. Add an explicit SpawnTrack_ClientRpc as
the primary trigger, with the existing OnValueChanged and
OnNetworkSpawn sync check as fallbacks. The _trackSpawned guard
prevents double-spawning across all paths.

https://claude.ai/code/session_01BRL56xwTnJh9i9eqNMxiUF
…pawn-JVgVj

Sync track spawning across clients via ClientRpc
…iggering full recovery

Relay connections can legitimately take >10s (allocation lookup, DTLS handshake,
Netcode approval). The previous behavior threw TimeoutException which triggered
destructive recovery (host restart + Menu_Main reload). Now logs a warning and
proceeds, matching the existing WaitForSceneLoadAsync pattern. Default timeout
increased from 10s to 30s.

https://claude.ai/code/session_01DKATvoNjCPEzTPeHKcmcNg
…dInEvent race

HostConnectionService.Start() only checked _initialized but not _joining,
allowing it to race with HandleSignedInEvent() when cached auth completes
quickly. Both paths called CreatePartySessionAsync concurrently — the second
found the Relay host from the first already running, shut it down, created a
new Relay session, and called ReloadMenuSceneIfActive, causing Menu_Main to
reload while already loaded.

Fixes:
1. Add _joining guard to Start() to match HandleSignedInEvent's guard
2. Add _creatingPartySession mutex to CreatePartySessionAsync as defense-in-depth
3. Fix WaitForSceneLoadAsync stale-scene false positive: when already on
   Menu_Main, wait for sceneLoaded event instead of polling active scene name

https://claude.ai/code/session_01DKATvoNjCPEzTPeHKcmcNg
AttachExternalCancellation registers on the CancellationToken directly, so
when CancelAfter fires via System.Threading.Timer on the thread pool, the
continuation runs off the main thread. All subsequent Unity API calls
(Debug.LogError, SceneManager.GetActiveScene, Debug.LogWarning via
LobbyPatcherLogFilter which accesses Application.isPlaying) crash with
"can only be called from the main thread."

Replace AttachExternalCancellation with UniTask.WhenAny racing against
UniTask.Delay, which runs on the PlayerLoop (main thread). Add
SwitchToMainThread safety nets in AcceptInviteAsync catch block and
RecoverFromFailedTransitionAsync.

https://claude.ai/code/session_01DKATvoNjCPEzTPeHKcmcNg
…-x927a

Increase party connection timeout and handle timeout gracefully
The _creatingPartySession bool guard caused concurrent callers to return
immediately with _partySession still null, leading to NullReferenceException
in SendInviteAsync. Replace with an awaitable Task field so concurrent
callers block until the session is actually ready.

https://claude.ai/code/session_01QrwBTJXLKSAMcYCWMgkJCp
…creation-2ZGGp

Fix concurrent party session creation race condition
The HexRace track intermittently failed to spawn on clients due to a
race condition where all three seed delivery mechanisms could fail
simultaneously: OnNetworkSpawn check (value not yet synced for
scene-placed objects), OnValueChanged callback (not fired for initial
sync), and SpawnTrack_ClientRpc (dropped if client hadn't spawned yet).

Adds a lightweight polling fallback (100ms intervals, 5s timeout) that
watches _netTrackSeed until it becomes non-zero. The existing
_trackSpawned guard prevents double-spawning if another path fires first.

https://claude.ai/code/session_01WoNbGvJvHcv3uuVDeMpQSP
Remove `using System;` and fully qualify System.OperationCanceledException
to avoid CS0104 ambiguity with UnityEngine.Random.

https://claude.ai/code/session_01WoNbGvJvHcv3uuVDeMpQSP
…spawn-6kXCi

Add seed poll fallback to handle race condition in HexRaceController
…ming

MigrateLegacyFields() only ran in Start(), but HexRaceController could
call SegmentSpawner.Initialize() before Start() via OnNetworkSpawn seed
delivery, leaving weightedSegments empty and spawning zero track segments.

- Move MigrateLegacyFields() to Awake() for earlier execution
- Add safety MigrateLegacyFields() call in Initialize() as belt-and-suspenders
- Add ExternalResetControl flag to prevent SegmentSpawner.ResetTrack()
  from racing with HexRaceController.OnResetForReplayCustom()
- Add diagnostic log in Initialize() with segment counts for debugging

https://claude.ai/code/session_01KcoRMJvhqQopum8JyqSq8u
…ck-Eb9z2

Add external reset control to SegmentSpawner for HexRaceController
On multiplayer clients, RPCs can resolve in the same frame as scene
load, firing OnClientReady before MiniGameHUD.Start() subscribes to
the event. This caused the pre-game camera cinematic to be skipped
on clients while playing correctly on the host.

After subscribing in Start(), check if OnClientReady already fired
(LocalPlayer.Vessel is set) and invoke the handler retroactively.

https://claude.ai/code/session_01BubGX7FzyrvgP2vsoLMAyi
…n-sync-nNsok

Fix MiniGameHUD race condition with late OnClientReady subscription
The ScreenSwitcher paused the game (Time.timeScale=0) on every non-HOME
screen, which froze time-dependent party/networking code:

- HostConnectionService refresh loop used Time.deltaTime (returns 0 when
  paused), preventing detection of party member joins
- HostConnectionService rate-limit backoff used Time.time (doesn't advance
  when paused), causing permanent backoff
- PartyInviteController delays used default DeltaTime, hanging forever
  when the client accepted an invite while paused

Fix:
- Remove PauseSystem.TogglePauseGame calls from ScreenSwitcher.NavigateTo
  — menu screen navigation should not pause the game
- Switch HostConnectionService to Time.unscaledDeltaTime/unscaledTime
- Switch PartyInviteController delays to DelayType.UnscaledDeltaTime

https://claude.ai/code/session_01WnQ3uviVjZNwFrDKwmsCnv
…onnection-0hROi

Use unscaled time for game pause-independent operations
…nd client

PrismFactory is DontDestroyOnLoad, so pooled trail blocks survived scene
transitions. The existing SOAP-based cleanup (InvokeSceneTransition) only
fired on the host via SceneLoader — clients never received it because
Netcode drives their scene load directly.

Subscribe all prism pool managers to SceneManager.activeSceneChanged to
synchronously release active objects back to pool. Add synchronous
ReleaseAllActive() to GenericPoolManager with activeSelf guard to prevent
double-release when both SOAP and scene-change paths run on the host.

https://claude.ai/code/session_01RyYQ5HTinTHVtdqCEzfitf
…leanup-LJRrt

Add synchronous pool cleanup on scene transitions
Adds a bool+int inspector toggle on HexRaceController to override
the waypoint-calculated crystal target for quick end game testing.
When useTestCrystalOverride is true, crystalsToFinishOverride (default 3)
replaces the normal target. Applied at the single choke point in
NetworkCrystalCollisionTurnMonitor.StartMonitor() so both network
variables stay in sync.

https://claude.ai/code/session_01AWwrjrLXeA4vmF4CbXSasr
Crystal domain colors were wrong on the client in multiplayer HexRace
because CrystalManager.Spawn() read domains from gameData.Players by
index, but player list order is non-deterministic across host/client
and domain values may not have replicated yet.

Fix: add a parallel NetworkList<int> n_Domains to NetworkCrystalManager
that the server writes authoritatively. All crystal spawn paths (turn
start, late join sync, position change replication) now read domain from
n_Domains instead of guessing from player list index. Extract
SpawnWithDomain() in the base class for explicit domain injection.

https://claude.ai/code/session_01AWwrjrLXeA4vmF4CbXSasr
…callback timing

NetworkList.OnListChanged fires synchronously on the server when a
value is set. Writing n_Positions[i] before n_Domains[i] caused
OnPositionsChanged → Spawn() to read n_Domains[i] before it was
written, resulting in all crystals getting Domains.None (which is
a no-op in ChangeDomain, leaving default Jade appearance).

Fix: write n_Domains[i] first so the domain is available when
OnPositionsChanged fires and triggers Spawn().

https://claude.ai/code/session_01AWwrjrLXeA4vmF4CbXSasr
…ata NetworkList

Two separate NetworkLists had a client-side timing issue: NGO processes
each NetworkList independently during the network update loop. Since
n_Positions was declared first, its OnListChanged callbacks fired before
n_Domains was updated, causing crystals to spawn with stale/None domain
on the client (appearing as default Jade).

Fix: combine position and domain into a single CrystalSlotData struct
in one NetworkList (n_Slots). When the callback fires on any client,
both values are guaranteed available in the same event. This also
eliminates the server-side write-order dependency.

https://claude.ai/code/session_01AWwrjrLXeA4vmF4CbXSasr
claude and others added 29 commits April 21, 2026 15:33
…lobby header

- HostConnectionService.OnSceneLoaded now repopulates PartyMembers from
  _partySession.Players after Menu_Main loads. Obvious.Soap's ScriptableList
  clears itself on LoadSceneMode.Single, which was nuking the [self, host]
  members seeded by AcceptInviteAsync (client) and the host's self entry
  (after the Relay-host Menu_Main reload). Slots in ArcadeLobbyList were
  blank on both sides until the next refresh tick; now they render
  immediately post-reload via re-raised OnPartyMemberJoined events.

- FriendsListPanel.HandlePartyInviteReceived calls Show() when inactive so
  the incoming party-invite row is visible the moment it arrives, without
  requiring the user to manually open the panel.

- ArcadeLobbyList header shows only the raw "N Players Online" count. The
  per-remote status badges ("IN LOBBY X/N", "LOBBY FULL", "IN A MATCH")
  belong on OnlineInfoEntry rows in FriendsListPanel, not on the local
  count text. Removed ResolveLocalStatusBadge, the GameDataSO inject,
  the match-state SOAP subscriptions, and related usings.

https://claude.ai/code/session_017PtFsKssgzPqePi1uFLUEc
…ort swap

Accepting a party invite was reloading Menu_Main as a network scene on
both host (when creating the party session) and client (after joining
Relay). This produced a visible "big load time" and wiped in-menu state
(ScriptableList OnSceneLoaded clears PartyMembers on LoadSceneMode.Single).

Now accept performs a direct transport swap: shutdown local NM, join
the UGS session via Relay, wait for IsConnectedClient, and raise
OnPartyJoinCompleted. Existing NetworkObjects replicate via standard
connection-approval spawn sync — no scene handshake needed.

- PartyInviteController.AcceptInviteAsync: drop CleanUpCurrentSession +
  WaitForSceneLoadAsync; 4-step direct-join flow.
- HostConnectionService.CreatePartySessionCoreAsync: no longer triggers
  ReloadMenuSceneIfActive after creating the Relay-backed session.
- Remove now-unused ReloadMenuSceneIfActive, FadeFromSplashOnReady,
  _hasReloadedMenuForRelay field, SceneTransitionManager inject, and
  CleanUpCurrentSession helper.
- LeavePartyAndReturnToMenuAsync keeps explicit player/vessel destroy +
  runtime-data reset — that path legitimately returns from a game scene.
…adge

Three related fixes for the post-accept UI bugs:

1. Start() and HandleSignedInEvent() now await PlayerDataService.IsInitialized
   (5s timeout) before SyncLocalIdentity, so the presence lobby never
   advertises the local "Pilot{XXXX}" default name to remote players.

2. After JoinPresenceLobbyAsync, re-sync identity and call
   RepublishLocalIdentityAsync unconditionally — catches the race where
   OnProfileChanged fired mid-join (RepublishLocalIdentityAsync early-returns
   when _presenceLobby is still null).

3. ArcadeLobbyList subscribes to PlayerDataService.OnProfileChanged and
   repopulates slot 0 — the local player's slot no longer renders empty
   when the panel opens before the cloud profile resolves.

4. FriendsListPanel.HandlePartyMemberChanged removes the joined player from
   _outgoingInvitePlayerIds, clearing the sender's "PENDING REQUEST" tint
   when the invitee accepts (previously only cleared on send failure).
The empty-slot FriendsInfo prefab instances in ArcadeScreenModal ship with
their AvatarIcon and PartyDescriptionText children inactive at the
GameObject level (so only the "+" Add Button fills the cell). The previous
SetPlayer / SetAsLocalPlayer only flipped the Image.enabled /
TMP_Text.enabled flags — the inactive GameObjects ate the update and
remote party members stayed invisible.

- SetAvatar / SetDisplayName now toggle gameObject.SetActive alongside the
  component enabled flag.
- ClearSlot deactivates both GameObjects so the empty-cell layout matches
  the original prefab state.

Matches the pattern already used for addButton (which was always toggled
at the GameObject level).
Slot 1+ showed avatar but no username because two paths reached the UI
with an empty DisplayName:

1. FriendInfoSlot.SetDisplayName used to deactivate the text GameObject
   when name was null/empty. For an occupied slot this meant "no visible
   label forever" once the property flipped empty (prefab ships with
   these children inactive). Now an occupied slot always activates the
   label and falls back to "Pilot" when the name is missing — so a late
   DISPLAY_NAME_KEY propagation corrects the label on the next refresh
   instead of staying silently blank.

2. RefreshPartyMembersAsync and RefreshOnlinePlayersDiff overwrote the
   "Unknown Pilot" fallback even when p.Properties[DISPLAY_NAME_KEY]
   held an empty string. Match the existing pattern from lines 372/1231
   (IsNullOrEmpty guard) so the fallback survives transient empty
   values.
Menu_Main.unity ships with two FriendInfoSlot instances (slots[1] and
slots[2]) pointing to the same displayNameText GameObject. With the
previous single-pass populate, slot[2]'s ClearSlot ran after slot[1]'s
SetPlayer and deactivated the shared text — hiding the remote party
member's name entirely.

- Two-pass populate: clear empty slots first, then set occupied slots,
  so activation wins over deactivation when refs are shared.
- Awake-time warning logs any shared displayNameText/avatarIcon refs so
  the wiring bug surfaces immediately on future scene edits.
- FriendInfoSlot exposes DisplayNameTextGO / AvatarIconGO for the check.

The root fix is still to rewire slots[1]/slots[2] in Unity so each owns
its own PartyDescriptionText child — but this makes the current scene
render correctly in the meantime.
…rd and end-game

When a teammate crosses the finish line in HexRace, Joust, or Crystal Capture,
the rest of that Domain should share the win instead of showing "X Left".

Changes:
- GameDataSO: add WinnerDomain (non-serialized), reset alongside WinnerName
- HexRaceController / MultiplayerJoustController / MultiplayerCrystalCaptureController:
  compute winning Domain server-side, assign winner's Score to every teammate,
  propagate WinnerDomain through the existing final-score ClientRpc
- HexRaceScoreboard / MultiplayerJoustScoreboard: sort with CrystalsCollected /
  JoustCollisions desc tiebreaker so the finisher ranks above teammates with
  the same shared team score; score formatter keys off Score < penalty threshold
- HexRaceEndGameController / MultiplayerJoustEndGameController /
  MultiplayerCrystalCaptureEndGameController: derive VICTORY/DEFEAT from
  WinnerDomain (domain equality) instead of WinnerName equality so teammates
  see the full victory cinematic; Joust/Crystal Capture delta labels compare
  winning-team best/total vs best-losing-team so "WON BY N" reads correctly
  for teammates
… always loads

SceneTransitionManager.LoadSceneAsync was throwing NREs in two scenarios that
left the user stuck on an opaque black fade overlay with the target scene
never loading:

  1. _cts was null (can happen if OnDestroy ran on a stale instance during
     shutdown) — _cts.Token threw inside the try, but the .Forget() at the
     call site swallowed the exception.
  2. SceneManager.LoadSceneAsync returned null (Unity returns null when the
     scene isn't in build settings or when called at a disallowed lifecycle
     moment) — await op.ToUniTask(...) then NRE'd on the null op.

Hardening:

- SceneTransitionManager: guard _cts against null in LoadSceneAsync and
  LoadNetworkSceneAsync; check LoadSceneAsync's return for null and fall
  back to SceneManager.LoadScene; wrap the body in catch-all that clears
  the overlay via SetFadeImmediate(0f) and attempts a synchronous load.
  FadeToBlack/FadeFromBlack now route through an EnsureCtsToken() helper
  that recreates the CTS if disposed.
- AuthenticationSceneController: wrap LoadSceneAsync in a fallback-aware
  helper (LoadMainMenuDirectWithFallbackAsync) so the 10s-timeout direct
  load still reaches Menu_Main if the fade transition throws.
- DailyChallengeSystem.FetchDailyChallenge: guard Arcade.Instance,
  TrainingGames, empty Games list, and null selected Game — return null
  with a warning instead of NRE'ing during startup. SelectDailyGame
  short-circuits on a null challenge / missing Arcade / missing DailyGame.

The triggering chain (UGS rate limit → host never IsListening → AuthScene
10s timeout → direct Menu_Main load) now degrades gracefully instead of
leaving the player on a black screen.

https://claude.ai/code/session_017bo24QLkc6PoUGmP3bLJRV
… type

The previous fix compared a DailyChallenge struct to null, which won't
compile (CS0019 / CS0037). Refactor FetchDailyChallenge → TryFetchDailyChallenge
with an `out` parameter so the null-guard semantics work against a struct.

https://claude.ai/code/session_017bo24QLkc6PoUGmP3bLJRV
When UGS is rate-limited (or Relay is otherwise unavailable) HostConnectionService
can't create a Relay-backed party session within the auth scene's 12s timeout,
so NetworkManager.IsListening never becomes true. The previous code gave up at
that point and loaded Menu_Main via a plain (non-networked) scene load —
breaking networked vessel spawning ("player spawning will not work").

On timeout, try NetworkManager.StartHost() ourselves as a last-resort local
host so Menu_Main's networked pipeline (MenuServerPlayerVesselInitializer,
Player.OnNetworkSpawn, etc.) still works. If HostConnectionService eventually
succeeds with Relay, it already shuts down any running host before creating
its session (HostConnectionService.CreatePartySessionCoreAsync:1404-1414), so
the takeover case is handled — there is no reload-Menu_Main-twice race
anymore (removed earlier per the comment at 1429-1435).

Also update the stale doc comment on EnsureHostStartedAsync that documented
the old "never start a local host" policy.

https://claude.ai/code/session_017bo24QLkc6PoUGmP3bLJRV
MultiplayerJoustController.countdownTimer was unassigned in the scene,
causing StartCountdownTimer() to silently no-op. With only 1 human +
AI players, pressing Go hid the Ready button but never started the
countdown. Added a stripped reference to the CountdownTimer already
present in the GameCanvas-HexRace prefab and wired it to the controller,
matching Crystal Capture and HexRace.

https://claude.ai/code/session_01LnPdWp96VWjnBwG96FAZud
Tighten every polling and timeout knob in the invite path so presence,
invites, and party-member updates propagate in well under a second,
matching AAA-level responsiveness:

- Drop presence-lobby refresh interval from 3s to 1.5s (still safely
  under the UGS per-client rate limit).
- Drop the boosted interval from 1s to 0.75s and extend the boost
  window whenever a new invite is detected on the receiver side, so
  both parties stay in fast-poll mode while an invite is in flight.
- After a client accepts, stay in fast-poll mode so the party session
  Players list populates the arcade lobby within ~1s.
- Tighten PartyInviteController timeouts: shutdown 5s -> 2s, client
  connect 30s -> 8s, transport settle 200ms -> 50ms.

Add a public ForceRefreshNow() on HostConnectionService (debounced
+ lobby-mutex aware) and call it from the UI entry points so data is
never stale when the user looks at it:

- FriendsListPanel.OnEnable
- ArcadeLobbyList.OnEnable
- PartyInviteController right after OnPartyJoinCompleted

Auto-surface the friends panel when an invite arrives while the
arcade lobby is visible. Previously the user had to navigate to
Arcade -> Add slot themselves to see the Accept/Decline row that
was silently spawning under an inactive panel.

https://claude.ai/code/session_017EWq2CT8w2LGuCkWWzSLzM
The scoreboard's Play Again flow was desynchronizing clients. The host
path invoked RequestReplay() immediately while client clicks entered a
rematch-invite flow that only needed one other player to accept, so
host and client paths behaved inconsistently.

Simplify to host-authority: only the host sees the Play Again button
(ConfigureLobbyButtons gates it just like Main Menu), and their click
forces every client to replay through the existing server-authoritative
reset pipeline. Clients see Leave Lobby as before.

- Gate playAgainButton in Scoreboard.ConfigureLobbyButtons to host/SP.
- Drop the client->server RequestReplay RPC; RequestReplay is now a
  server-only call with a warning-log guard.
- Delete the rematch RPC pair + Scoreboard invite/received/denied UI
  methods. Orphaned prefab refs stay inactive (panels ship inactive).
- Wrap ExecuteSceneReloadReplay in try/finally so an exception can't
  permanently latch _isResetting and brick the button.
Follow-up to the host-only Play Again fix. Replaces the scattered
restart paths (Scoreboard direct, PauseMenu via SOAP → SceneLoader)
with a single polymorphic entry point: MiniGameControllerBase.RequestReplay.

- MiniGameControllerBase gains abstract RequestReplay.
- SinglePlayerMiniGameControllerBase implements it (stats reset +
  ResetForReplay + camera snap — previously split between SceneLoader
  and gameData event handlers).
- MultiplayerMiniGameControllerBase overrides it with the existing
  host-only body.
- Scoreboard's Play Again prefers the serialized MP reference and
  falls back to FindAnyObjectByType so singleplayer scenes work with
  the same code path (no prefab re-wiring needed).
- PauseMenu calls controller.RequestReplay directly, unpauses, and
  hides itself. Adds a replayButton serialized ref that gets gated
  off for non-host multiplayer clients in Show().
- SceneLoader loses RestartGame and the _onClickToRestartButton SOAP
  subscription — restart is no longer its responsibility. Orphaned
  prefab refs on the deleted field are silently dropped by Unity.
…ctByType

FindAnyObjectByType is expensive and was being called on every Play Again
click. Switch both Scoreboard and PauseMenu to a serialized
MiniGameControllerBase reference; error-log if unassigned.

- Scoreboard: renames multiplayerController to gameController and retypes
  to MiniGameControllerBase. FormerlySerializedAs preserves existing MP
  scene wirings (a MultiplayerMiniGameControllerBase is-a MiniGameControllerBase).
- PauseMenu: adds a new serialized gameController field.
With the asset deleted, any lingering references to its GUID either
caused NullReferenceException (EventListener responses in game scenes)
or became harmless orphan YAML keys (serialized field overrides).

Removed:
- EventListener response items (6 game scenes) that subscribed to the
  deleted asset and called SceneLoader.RestartGame or
  HexRaceController.RequestReplay. These now call the controller
  directly via PauseMenu.gameController.
- Orphan `_onClickToRestartButton` field overrides in Bootstrap.unity,
  Pause_Menu_Panel.prefab, R_Pause_Menu_Panel.prefab, and a prefab-
  override in MinigameFreestyle.unity.

Other response items in the same EventListeners (Main Menu, Session
End) are preserved.
On replay, only the host and AI vessels moved — the local client's vessel
stayed frozen. Player.ResetForPlay writes Paused=true via the network
variable as part of the ResetForReplay pipeline, and Player.StartPlayer is
supposed to clear it via ToggleInputPause(false) after the countdown.
In multiplayer replay the clear race lost often enough that the local
client ended up stuck with Paused=true, IsStationary=true, or Idle=true
when the turn actually started.

Add a small defensive helper on MultiplayerMiniGameControllerBase that
explicitly drives the local human player's input state live right after
SetPlayersActive fires. Call it from all four OnCountdownTimerEnded
ClientRpc paths (base, DomainGames, WildlifeBlitz, Freestyle). For
Freestyle the helper only runs when the local player is the one whose
name just activated. AI and remote players are untouched — the helper
is local-human-only.
The Volume/Pause button on the menu MiniGameHUD now calls
MenuCrystalClickHandler.ToggleTransition(), returning the user from
freestyle back to the main menu instead of opening the vessel selection
panel.

https://claude.ai/code/session_01GdwhzpvUeCS9SwYE8397Ef
The Volume/Pause button's CanvasGroup is now driven by the freestyle
state transition events: faded in on enter, faded out on exit, and
re-shown after a mid-freestyle vessel swap so the button stays
accessible across replacement ship HUDs.

https://claude.ai/code/session_01GdwhzpvUeCS9SwYE8397Ef
Captures root cause of the invite-accepted-but-not-joined bug (missing
InvokeSessionStarted + StartClient in MultiplayerSetup.JoinSessionAsClientById)
plus prioritized fix list covering connection approval, readiness gating,
disconnect/reconnect handling, and observability gaps.

https://claude.ai/code/session_01BC7t43yv45U5EBxKhGWsQV

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b0eb1024bb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

[MenuItem("Cosmic Shore/Toast Notification/Create Settings Asset")]
public static void CreateSettingsAsset()
{
var path = SOFolder + "/ToastNotificationSettings.asset";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Create toast settings in the path used by manager wiring

CreateSettingsAsset writes ToastNotificationSettings.asset to Assets/_SO_Assets, but CreateManagerInScene later tries to load settings from Assets/Resources/ToastNotificationSettings.asset. In a fresh setup flow this leaves ToastNotificationManager.settings null, and ToastNotificationManager.Show drops every toast with a warning instead of displaying notifications. Use a single shared path (or constant) for both creation and wiring so the generated manager is functional.

Useful? React with 👍 / 👎.

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.

7 participants