Add visual effects, audio assets, and refactor FTUE namespace#499
Add visual effects, audio assets, and refactor FTUE namespace#499YsKhan61 wants to merge 1596 commits into
Conversation
…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
….com/froglet-studio/Cosmic-Shore into claude/compare-hexrace-endgame-2NC34
…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
…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
There was a problem hiding this comment.
💡 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"; |
There was a problem hiding this comment.
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 👍 / 👎.
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.FTUEnamespace dependency.Key Changes
Visual Effects & Assets
vfx_Lightning_01andvfx_Lightning_02for electrical effectsvfx_Projectile_01andvfx_Projectile_02for projectile impactsvfx_Electricity_01for general electrical effectsGraphics & UI Assets
Audio Assets
FTUE Refactoring
CosmicShore.FTUEnamespace from all FTUE-related classes:TutorialSequenceSet.csFTUEIntroAnimatorAdapter.csFTUEProgress.csTutorialStepType.csITutorialExecutor.csTutorialPhase.csITutorialStepHandler.csITutorialUIView.csIFlowController.csIAnimator.csITutorialStepExecutor.csTutorialFlowController.csTutorialUIViewAdapter.csTutorialStep.csTutorialUIView.csFTUEIntroAnimator.csFreestylePromptHandler.csIntroWelcomeHandler.csLockModesExceptFreestyleHandler.csOpenArcadeMenuHandler.csTutorialSection.csTutorialStepPayload.csFTUEEventManager.csIOutroHandler.csInGameTutorialFlowView.csConfiguration & Setup
ToastNotificationSetup.cseditor script for notification system configurationToastNotificationSettings.assetandToastNotificationChannel.assetfor notification managementGameFeedChannel.assetandReflexSettings.assetfor game systems.gitignoreto exclude build artifactsCode Cleanup
.utmpdirectorySoapGenerated.metafolder referenceNotable Implementation Details
https://claude.ai/code/session_01BC7t43yv45U5EBxKhGWsQV