Repository navigation
Conversation
|
Unity PR artifacts are ready.
Workflow run: Unity Source Validation #673 Artifacts are temporary and expire according to the workflow retention policy. |
SDK UI surfaces now live as long as the mod, or the scope, that made them, and a gameplay HUD needs no scene code. - Every SDK HUD, window, fullscreen tool and modal draws on a persistent canvas. A HUD made in OnLoad, before the game's first scene, reaches the screen, and a window survives the next world load. A scope's surfaces end with its scope. - A surface whose canvas the game or another mod destroys is never reported visible again: a visible one raises Dismissed, its id is freed, and the mod's log says to create it again. Windows, tools and modals on such a canvas give the cursor and Escape stack back; a modal ends as cancelled. - Gameplay HUDs show only while a loaded gameplay scene is active and no scene loads, sampled each frame before mods update. UiHudVisibility.Always opts out. IsVisible reports what the player sees. - Sorting slots go back by what was allocated (TopiaForgeLayerLedger), never by reading a destroyed canvas, so a HUD no longer moves up the band with every world change. - A HUD's text column fills its panel, so short lines never wrap. - HUDs dock bottom-left by default (left edge when shown everywhere), clear of the game's microphone label, version, objective and subtitles, and HUDs sharing an anchor stack. UiHudAnchor picks another place. Toasts stack up from the bottom right, off the level objective. - A window shown again during its close fade stays shown, and a window is re-placed when its canvas changes size. - The TF1008 docs and PendingOperation's example name ElapsedTime. - The multiplayer template makes one HUD in OnLoad with no scene bookkeeping; the ui template, the SDK acceptance mod and the testing kit's fakes follow the new lifetime. Signed-off-by: Furroxide <221987073+Furroxide@users.noreply.github.com>
Furroxide
force-pushed
the
fix/ui-surface-lifetime
branch
from
October 19, 2026 04:12
de770e7 to
1c48d68
Compare
This branch has not been deployed
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.
Outcome
SDK UI surfaces now live as long as the mod, or the scope, that made them, and a gameplay HUD needs no scene code. A
mod makes its HUD once, in
OnLoad; TopiaForge shows it in every world and hides it at the main menu and while a sceneloads. This fixes five defects that #227's review proved in the game on Robotopia 0.0.2545, for every mod with UI,
offline and online alike.
OwnerUiServicemade non-persistent canvases in the active scene. A HUD made inOnLoadnever reached the screen, because mods load before the game's first scene, and a window made in a worldvanished at the next world load while
IUiSurface.IsVisiblestill said true. Every SDK surface (HUD, window,fullscreen tool) and every SDK modal now draws on a persistent canvas (feat(multiplayer): add the in-game sessions tab and connecting overlay #225's persistence), owned by the mod's or the
scope's lifetime. No SDK surface ends with a scene: a mod cannot see a scene change coming, and every flow it builds,
a world session included, already has a lifetime of its own. A surface a gamemode session's scope makes ends with the
session. Kit layers made directly keep the kit's default and stay scene-owned unless created
persistent: true.reported visible again, a visible one raises
Dismissed, its id is free for a new surface, and the mod's log says,in plain words, that it was destroyed outside TopiaForge and to create it again. A modal on such a canvas completes as
cancelled, and windows, tools and dialogs on it give the cursor and the Escape stack back.
(
UiHudVisibility.Gameplay): hidden at the main menu (byGameScenes.IsNonGameplayScene, the template's ownclassification) and while any scene loads, shown again in the next world.
IsVisiblereports what the player sees,so the multiplayer template counts a press only while
hud.IsVisible. TopiaForge's hiding never raisesDismissed. The opt-out is one call:request.WithHudVisibility(UiHudVisibility.Always). Windows and fullscreentools show exactly while the mod shows them.
TopiaForgeLayers.Releaseread the order from a canvas a scene haddestroyed, so the slot leaked: the HUD went from 30001 to 30002 across one world change, in a band of 800. Every kit
canvas now holds its slot through
TopiaForgeLayerLedger, keyed by the canvas object, and a component on each canvasgives the slot back whenever anything destroys it. Window focus moves the record with the order it permutes, so the
registry and the ledger always agree. In the game the counter's HUD kept order 30000 across six world changes, and
destroying it from outside released its slot.
wrapped (53 pixels of body at 1600x900). It now fills the panel inside the padding: a 260-unit HUD wraps body text at
224 units, at every UI scale, and its title stays on one line.
development build's version at the top, a level's objective at the top right (above every mod's UI), a crosshair at
the centre and dialogue subtitles across the bottom middle (measured in five levels at 2560x1440). A gameplay HUD now
docks at the bottom left by default, and a HUD shown everywhere at the middle of the left edge, which also keeps
clear of the main menu's TopiaForge buttons.
WithHudAnchorpicks any corner or edge middle (UiHudAnchor), neverthe centre. HUDs docked at one place stack a small gap apart, first created nearest the edge, so HUDs from different
mods never overlap. Toasts moved from the top right, under the level objective, to the bottom right, stacking
upwards.
docs/Diagnostics.md) andPendingOperation's XML example usedContext.Time.Frame.UnscaledTime, which does not exist; they now use(float)Context.Time.Frame.ElapsedTime, and theexample compiles. No other use remains.
TopiaForgeWindow.Showduring a close fade was swallowed, so a surface'sHide()thenShow()ended hidden although the surface said it showed; the fade now stops and the window stays, asthe fullscreen tool already did. Windows were clamped only when shown, so a new resolution or UI scale could leave a
title bar off screen; a component on each window's canvas now arms fix(ui-kit): keep windows whole when shown on an unmeasured canvas #248's deferred clamp when the canvas changes
size, so the window is re-placed on the next frame.
OnLoadand lost itsSubscribeSceneLoadedbookkeeping, its
ShowHudForWorldmethod and its HUD field (the file is 26 lines shorter); its tests and READMEfollow. The
uitemplate's window nowsurvives scene loads; its test proves the same window toggles after a world and the main menu. The SDK acceptance
mod's
OnLoadHUD and modal now reach the main menu; its HUD shows everywhere, since its last step is taken at themain menu. No first-party mod re-created surfaces per scene.
FakeUiSurface.IsVisiblefollows the fake scenes as the game does, andIsShownsays whether the mod shows a surface.Compatibility and security
TopiaForge.Mods.AbstractionsgainsUiHudAnchor,UiHudVisibility, andUiSurfaceRequest.HudAnchor,HudVisibility,WithHudAnchorandWithHudVisibility(21 baseline lines added, none changed). The copy methodskeep the constructor binary-compatible; on a window or fullscreen request they throw, saying to create a HUD.
TopiaForge.Mods.TestinggainsFakeUiSurface.IsShown(1 line).TopiaForge.Mods.UnityUi, the loader's internal renderer, no SDK baseline) gainsTopiaForgeLayerLedger<TCanvas>,TopiaForgeHudColumn,TopiaForgeHudDock,TopiaForgeLayerBands.Holds,TopiaForgeTokens.ScrollGutterandUiHost.LayerLost;TopiaForgeCornermoved to the Unity-freeCore/folder(same namespace and assembly).
IsVisiblefalse, at the main menu and during loads; a mod whose HUD mustshow there opts out with
UiHudVisibility.Always;FakeModContextstart at the main menu, so a gameplay HUD is hidden until the test loads aworld (
context.Scenes.Load(...)); a test that assertedIsVisibleon a HUD without loading a world needs thatline or
Always;log line names the surface's id.
Deviations from the plan
an anchor stack; without it the default would trade one overlap for another.
uitemplate still installs disabled. Its manifest category isDevTool, which TopiaForge installsdisabled, while its README says to install and press U. Left for a follow-up: the category is a product decision.
Verification
On a Windows 11 x64 machine, on
devat bc3153f (#245) plus #248's head (559c1ea):dotnet build TopiaForge.slnx -c Release -warnaserror --no-incremental: 0 warnings, 0 errors.dotnet run --project tests\TopiaForge.ModManager.Tests\TopiaForge.ModManager.Tests.csproj -c Release: "AllTopiaForge tests passed.", 197 lines reporting a pass, among them the new
UiKitLayerLedgerTests,UiKitHudLayoutTests,UiSurfaceLifetimeTests,UiSurfaceLifetimeConventionTestsandTestingKitTests.UiPresence, fix(ui-kit): keep windows whole when shown on an unmeasured canvas #248'sUiKitWindowMathTests, and the 363 gamemode conformance fixtures.fuzzing and the network world over the real provider): passed.
dotnet format TopiaForge.slnx --verify-no-changes: only the 7 known Windows findings inRobotProgramDirector.cs;none in a file this pull request touches.
dotnet restore TopiaForge.slnx, thenbash tools/verify-csharp-release-surface.sh: "Validated 11 SDK packages andall 7 C# release harnesses."
check_topiaforge_residue.py,check_readme_counts.pyandnode website/scripts/check-markdown-links.mjs(147files): pass. No Dart file changed.
mod sync multiplayer,dotnet build -warnaserror(0 warnings) anddotnet test: the multiplayer template's 6 tests and theuitemplate's test pass; the multiplayer template's menu and load assertions can only pass against the updated testing
kit. The CLI's
multiplayer template packs only after its contract lock is synchronizedandlists built-in templatespass.world, through 1,000 loads, never dismissed by TopiaForge; a HUD shown everywhere; the mod's own show and hide; a
destroyed surface never visible again and dismissed once; a player-closed window; disposal; the gate's scene
classification, its load detection and isolated listeners; sorting slots released by the record after the canvas is
destroyed, a late destroy notice unable to free another canvas's slot, window focus moving the record, shared
exhausted orders, and 1,000 scene changes with HUDs, windows and dialogs leaking no slot; the column math (a 260 x 96
HUD wraps body text at 224 units at any UI scale) and degenerate panels; the dock at every anchor, stacking, closing
up, centred edges, mixed UI scales and a settled frame allocating nothing; default anchors clear of the game's HUD;
the fakes following fake scenes; the request copy methods and their refusals; and, read from the sources, the Unity
wiring: persistent surfaces and modals, slots released by the record, lost canvases reported to their owners, the
gate sampled before mods update, toasts at the bottom right, and both window fixes.
restored tree built, passing and clean afterwards:
Release, and in the window registry), asurface left non-persistent (HUD, window, fullscreen tool and modal each), and a destroyed surface reported visible
(in the presence, and in the surface);
or not following focus, HUDs shown during loads, the gate dismissing a HUD, the column left at 100 x 100 or wrapped
at the old width, HUDs stacked on top of each other, gameplay HUDs back at the top left, toasts back at the top
right,
Showswallowed during a close fade, no re-placement on a canvas size change, fake HUDs ignoring thescenes, and the gate never sampled.
patch at 2560x1440 and switched to 1920x1080 in the same run, with this branch's runtime and first-party mods, the
scaffolded multiplayer and
uitemplates and the SDK acceptance mod installed. A temporary probe, kept out of thebranch, loaded scenes through the game's own loader (
SceneUtil.LoadScene), called small drivers in scratch copiesof the two templates (presses go through the K key's own gate), clicked kit buttons through their own handlers,
logged the scene counts every frame, and captured the game's frames. Every run was killed, never quit.
objective at the top right, the crosshair, and dialogue subtitles across about the middle half of the bottom edge;
the bottom left and bottom right were free in every level.
until the old one unloads in a single frame.
count) and while City Streets loaded; shown in City Streets at the bottom left, where three presses counted;
hidden at the main menu with the count kept; shown again in City Streets with "Shared count: 3". Its canvas kept
order 30000 through six world changes. Destroying its canvas from outside left it reported hidden, a press no
longer counted, every sorting slot came back, and the mod's log said in plain words that it was destroyed outside
TopiaForge and to create it again.
and the body 224, on one line; 299 and 448 pixels at 2560x1440 and UI scales 1.0 and 1.5, and 336 at 1920x1080
and 1.5, one line each, high contrast included.
uitemplate's window, opened at the main menu, stayed open through City Streets and back to the mainmenu.
Hide()thenShow()in one frame left it visible (it faded back in). Moved near the bottom right and thenput on a UI-scale-1.5 canvas, it was re-placed fully on screen, its right edge at the window's edge.
OnLoadsurfaces: its modal showed at the main menu over a scrim, and its HUD, showneverywhere, sat at the middle of the left edge at the main menu and in City Streets, clear of TopiaForge's buttons.
one and three lines stacked up from the bottom right, above the window, leaving the objective uncovered; nothing
covered the microphone label, the version or the objective at either size, UI scale 1.0 or 1.5, or high contrast.
gate. A pass with real input is pending for the owner.
Checklist
AGENTS.mdand preserved CLEAN/Bloc/Unity/TopiaForge UI boundaries.