Hold Shift to momentarily enable the pipette (restores prior state on
release) and Shift+Scroll to resize the brush. The orbit zoom ignores the
wheel while Shift is held so it does not zoom and resize at once. The HUD
brush-size slider follows live via a BrushSizeChanged event, and the brush
preview now stays visible while Shift is held (drawn before the pipette
early-return) so the new size is visible while scrubbing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HUD panel positions and brush values were written only from
OnApplicationQuit/OnDestroy, which is not guaranteed in a build (Alt-F4,
process kill, crash), so layout work was lost. DraggablePanel now exposes
an IEndDragHandler callback that saves the layout the moment a panel is
dropped. Brush-value changes are detected against a disk baseline in
LateUpdate and flushed after a 0.5s debounce so per-frame slider drags
do not write every frame. The teardown save path is removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The HUD save key was derived from the active scene name, which now changes
across the additive lobby/map loads (Main -> Lobby -> Test Map). Save and
restore landed in different files, so settings appeared lost. Derive the key
from the HUD's own (stable, persistent) scene instead.
Brush settings also live on the avatar's painter, which is despawned and
respawned on every scene switch, resetting them to prefab defaults. Mirror
the brush values into an in-memory snapshot on the persistent HUD and seed the
freshly bound painter from it, so in-session edits carry across the respawn.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the connection-approval role selection with a server-authoritative
match flow built on NGO additive scene management:
- Scenes: Main is the persistent foundation (NetworkManager, GameModeManager,
cameras, HUD); Lobby and Test Map load/unload additively. Add Lobby + Test Map
to the build settings; remove the obsolete SampleScene.
- MatchManager (plain MonoBehaviour singleton): loads Lobby on server start,
StartMatchTrigger lets the host start the match, rolls one random Hunter (rest
Hider), swaps Lobby -> Test Map (strictly serialized per NGO), respawns each
player as its role variant. Forces additive client sync mode so clients keep
Main instead of single-loading it (fixes "Destination scene is not valid").
- Server-driven spawning: null out NetworkConfig.PlayerPrefab at runtime to
disable auto-spawn, then spawn players after the lobby floor exists (fixes
falling through the floor) with double-spawn and transition guards.
- StartMatchTrigger: server-gated OnTriggerEnter, only the host's own player
starts the match.
- NetworkBootstrap: drop role selection / connection approval; everyone spawns
as the neutral Player prefab. Host/Client/Server/Shutdown buttons stay.
- NetworkPlayerSetup.ScopedModes flag (baked on Hunter/Hider prefabs): lobby
avatar gets the full mode set, match variants get the role-scoped set.
- GameModeManager.BindLocalPlayer reads ScopedModes and rebinds the painter
unconditionally (fixes stale painter binding on respawn -> brush painting in
every mode).
- MeshPainter.PaintingEnabled defaults to false (unbound/pre-spawn/remote never
paints); PaintHud visibility now also gates on a bound painter (fixes the HUD
showing at startup before any avatar exists).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Persistence rework:
- Drop auto-load on setup and auto-save on teardown; the painter now always
seeds fresh from the material.
- Named slots under MeshPaint/Saved/<name>.paint via the existing CHMP
machinery: SavePaintAs / LoadPaintByName / DeletePaint / ListSavedPaintings /
PaintExists. Slot files carry the display name in the header.
- Remove the obsolete persistence partials (PlayerCharacterController,
PlayerPoseController, ThirdPersonOrbitInput) and their references.
Paintings HUD panel:
- Authored Panel_Paintings + Save/Load/Delete dialogs into PaintHudPanel.prefab
(TMP throughout); PaintHud.Library.cs is now a pure binder that clones the
entry template per slot.
- Save dialog blocks empty/existing names; Load and Delete confirm first.
- Fix dialog z-order: open dialogs SetAsLastSibling so a dragged panel can no
longer cover them.
- Lib_ListContent horizontal offsets corrected to 0 (was clipping entry labels
out of the masked viewport).
Multiplayer:
- Loading a painting re-syncs to all peers via the snapshot broadcast path
(BroadcastLoadedPaint), covering later late-joiners for free.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Point the first-person camera at the dedicated FirstPersonTarget anchor
(eye height) instead of the shared CameraTarget, in both the runtime
SetPlayer rebind and the editor ResolveReferences fallback.
Rebind the paint HUD to the locally spawned avatar's MeshPainter via a
new GameModeManager.PainterChanged event. Since the spawn-on-connect
refactor, no player exists at PaintHud.Start(), so the HUD bound to a
null painter (no brush masks shown, color/settings were no-ops).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add mask selector with previews to the brush card (data-driven from
painter.brushMasks); persist activeBrushIndex with HUD layout.
- Fix alpha/coverage artifacts when overpainting after load or backend
switch: derive the dilation source marker from a deterministic UV island
mask instead of resetting it to "unpainted" on every seed.
- Import the two final brush mask textures (Brushes/).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add MeshPainter.Persistence.cs: auto-save the three canonical paint maps
(albedo, MSO, emission) as PNGs under persistentDataPath on quit/play-stop
and auto-load them as the backend seed on start, so painting survives
sessions. Reuses the existing ReadCanonical/SetupBackend seed round-trip;
no new color-space code.
Add PaintHud.Persistence.cs: auto-save draggable panel positions and the
widget-bound painter values to a small JSON layout file, restored on start
via the painter -> SyncFromPainter -> widgets path. Complete SyncFromPainter
to also cover the spray sliders.
After a successful pipette pick the painter now enables all four paint
channels (albedo, metallic/smoothness, occlusion, emission) and raises a
Picked event. The HUD subscribes and re-syncs every widget (toggles,
sliders, both colour pickers) to the adopted brush values.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pipette samples all painted channel values (albedo, metallic, smoothness,
occlusion, emission) at the clicked texel and adopts them into the brush
with every opacity forced to 1, instead of painting. The screen raycast is
extracted into a shared TryRaycastPaintMesh helper used by painting, preview
and the pipette. A pipette toggle is added to the HUD panel prefab and wired
in PaintHud.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Spray brush mode: bakes a procedural scattered-dot mask per stamp (random
position, size, alpha within the brush disc) and paints it through the
existing single-stamp pipeline; dot centers clamped to (0.5 - dotR) so the
spray footprint matches the solid brush radius.
Paint interval: float on MeshPainter; 0 keeps the continuous interpolated
stroke, >0 emits a single timed dab every x.xx seconds.
Color picker alpha: horizontal alpha bar on both HSV pickers, runtime-built
in Rebind (no prefab re-bake), passed through to brushColor.a / emissionColor.a.
Metallic/smoothness + occlusion paint opacity: new opacity fields applied in
both CPU and GPU backends (GPU MSO draw split into MS and occlusion sub-stamps
for independent opacity), with cloned alpha sliders wired into the HUD.
Also includes the prefab layout tweaks and scene adjustments.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each channel panel (Albedo, Metallic+Smoothness, Occlusion, Emission)
gets a CanvasGroup whose alpha follows its toggle: 1.0 when enabled,
0.5 when disabled. Ensured at runtime in WirePanel (no prefab re-bake),
so both the prefab and code-gen paths behave identically. Panel_BrushSize
has no toggle and is left untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drag a panel from anywhere on its surface (header, padding, labels,
background) instead of only the header. Dragging is cancelled when the
press starts on an interactable (Selectable toggle/slider or the color
picker's UiPointerSurface), so widgets stay fully operable. Clamping is
now anchor/pivot-agnostic, computed from the panel's real corners, so
panels anchored bottom-center stay reachable in every screen corner.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Split the single HUD panel into four independent per-channel sub-panels
plus a global brush-size panel, each a standalone RectTransform with its
own header (drag-handle ready). PaintHud now binds to a baked layout
prefab via recursive name lookup instead of building everything in code,
with a code-gen fallback. HsvColorPicker gains Rebind() to re-apply the
procedural gradient sprites and drag relays that a prefab cannot serialize.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a code-driven HUD that appears in paint mode and binds live to the
active MeshPainter: four channel toggles, HSV pickers for albedo and
emission, sliders for metallic/smoothness/occlusion, emission intensity
and brush size. The HUD self-assembles its own overlay canvas and event
system at runtime.
Add a ModeChanged event to GameModeManager so the HUD can follow the
active mode, and guard MeshPainter against painting through the UI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>