- Manual hider ping on "E": server-authoritative RequestManualPing gated to
Seek + living hider; shared EmitRadarPings path so interval and manual pings
produce identical blips. Ping key read client-side in GameModeManager, role-
gated and excluded in FreeCam (E is fly-up there).
- Radar refreshes an existing blip for the same transform target instead of
duplicating it (lifetime resets to the configured value) via RadarTarget.Refresh.
- Blips fade in/out independently of the disc fade: per-blip MoveTowards factor
multiplied on top of the radar fade, refresh-safe, with separate in/out speeds.
- Resolution countdown waits for conversion statues to load: generalized the
match-start loading barrier (RunReadinessBarrier over _barrierTargets, hold
phase carried in MsgBeginLoad) and run it a second time over statue IDs before
starting the real resolveDuration. Only holds for Conversion with real statues.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Set GameModeManager.playersCollide to off in Main.unity, retune the
NetworkSimulator preset reference, and pick up the ConversionStatue
prefab's regenerated GlobalObjectIdHash.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a per-machine "Players Collide" switch on GameModeManager: a
self-healing reconciler routes the local capsule through other avatars'
body colliders via Physics.IgnoreCollision when disabled, re-asserting
every frame while off to survive the late-created runtime MeshCollider,
conversion respawns and teleport resets. Expose Active/ActiveVersion/
BodyCollider on NetworkPlayerSetup for the reconciler.
Give MatchStatue the same Active registry + BodyCollider pattern so the
frozen conversion statues obey the same toggle (they carry their own
paint MeshCollider but are deliberately not NetworkPlayerSetup avatars).
Also drive the statue's steady X-ray reveal in Resolution, matching an
un-found hider.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In Conversion mode, capture a snapshot (pose index + paint) of each hider
at conversion time and materialize a frozen, painted statue at the found
spot for the resolution phase, independent of the now-hunter avatar.
- MatchStatue.cs (new): server-init pose/paint, network-driven pose then
freeze, reveal-gate (hidden until paint applied), kills inherited sim.
- PlayerPaintSync: _isStatue (sibling auto-detect) suppresses the three
auto-behaviours so MatchStatue drives the paint flow deterministically.
- MatchManager: statuePrefab; capture in ConvertToHunter; spawn on
resolution enter; despawn + record clear on EndMatch/teardown/start.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Server-authoritative skip flag consumed by each phase wait loop
(loading barrier, hide/resolution wait, seek phase), routed via
authority like RequestEndMatch. Exposed as a [ContextMenu] and
public method; single transition per trigger, cleared on teardown.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Main.unity: matchLoadTimeout 30, hitRevealDelay 0.35 -> 0.2, sim
preset reselect (Mobile 2G), and remove the redundant AudioListener
on Radar Camera (silences the "2 audio listeners in scene" warning).
- PlayerCharacterController: GravityEnabled toggle. When off, hold
vertical velocity at zero and skip steep-slope sliding so a frozen
avatar neither falls nor drifts; true is byte-for-byte the old path.
- GameModeManager: during Loading, additionally disable gravity and
hide the local body (SkinnedMesh + fur shell), re-asserted each frame
to catch the mid-load lobby->match respawn. Restored (body shown,
gravity on) before ActiveMode.Enter() on release to Hide.
- MatchManager: enter Loading *before* SwapScenes so still-bound lobby
avatars are frozen before their floor unloads. Fixes the player
falling into the void during the lobby->map scene swap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gate the Lobby-to-world transition on a new MatchSubPhase.Loading before
Hide: movement stays frozen and the Hide countdown does not start until
every connected client has spawned all match avatars and applied their
paintings.
- MatchManager: Loading phase; matchLoadTimeout tunable; Match_BeginLoad
broadcasts the respawned avatar NetworkObjectIds (+ generation tag),
Match_Ready collects per-client ACKs (host self-checks locally); barrier
coroutine in RunMatchPhases before Hide; disconnect/teardown cleanup.
- GameModeManager: freeze local player (movement/paint/tab) during Loading,
restored via ActiveMode.Enter() on transition to Hide (no camera jump).
- NetworkPlayerSetup: treat Loading like Hide in ComputeVisible so a
fast-loading client cannot reveal a hider early.
- MatchHud: "Loading" phase label.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Radar: nur sichtbar bei >=1 Target, in First-Person und in der Seek-Phase
(Overlay-Kamera getoggelt, Blip-Skripte laufen weiter). Singleton fuer
Server-Zugriff. GameModeManager: Kamera + Tab-Moduswechsel erst ab
gebundenem Lobby-Player (Inert-Zustand ohne Player, Orbit-Input geparkt).
MatchManager: Server tickt im Seek alle radarPingInterval (30s) und
broadcastet noch nicht gefundene Hider (NetworkObjectId + radarPingLifetime
3s) nur an Hunter-Clients; jeder Hunter loest lokal auf und spawnt Blips.
Ping-Loop in die Seek-Coroutine gefaltet (EndMatch stoppt ihn mit).
MatchHud: Ping-Countdown lokal aus Seek-Start + Kadenz berechnet, Format
m:ss (0:30..0:01), ausserhalb Seek / bei deaktivierten Pings 0:00.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Radar.Spawn(transform, lifetime) clones the "Target Template", names it
"Target" and attaches a RadarTarget that self-destructs after lifetime.
RadarTarget aims each frame toward camera->target (full 3D LookRotation),
drives the point renderer's _BaseColor from a forward-bearing gradient
(ahead/side/behind depth cue lost in the flat disc projection) via a
MaterialPropertyBlock, and scales the point renderer transform by
camera->target distance.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HsvColorPicker: read the color swatch from the SV handle's child
("Handle/Swatch") instead of a standalone root child; drop the dead
leftover-alpha bar and root-swatch fallback handling.
PaintHudPanel.prefab: restructure the picker so the swatch lives under
the SV handle and wire _swatch accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
X-ray hit flash: drive a dedicated flash mode instead of the normal
reveal - pulse off (steady), intensity boost (xrayFlashBoost, default 3x),
snap-on, then restore authored color/pulse exactly. Fixes the weak/dark
flicker where the 0.35s hit window often caught only the pulse's near-off
plateau.
Spawn/respawn freeze: pool GPU RenderTextures across despawn->respawn
(TeardownGpu recycles, CreateRT pops) so a hider->hunter conversion no
longer churns ~70MB of RT alloc/free on every observer; reuse staging
textures to cut per-map GC. Defer the blank base-seed on reveal-gated
remote replicas (DeferInitialSeed) - the real pixels arrive with the
snapshot apply anyway; gate the FurShellRenderer alongside the body so a
deferred replica never shows the pooled RT's stale fur; EnsureBackendSeeded
+ TryCaptureSnapshot guard against capture-before-reseed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Laser.shader + HunterLaserBeam: additive HDR beam, fade in/out over opacity
and thickness, code-driven LineRenderer, tunable emission multiplier.
- FirstPersonMode: TryShoot computes world endpoint, fires beam with lateral
muzzle offset (right) so it isn't dead-center from hunter view.
- NetworkPlayerSetup: shot relay RPCs (owner->server->all) + hit-flash RPC
(server->all), flash OR-branch in ComputeXRay.
- MatchManager: hit reveal broadcasts X-ray flash first, delays hider removal
by hitRevealDelay so the glow is visible before the body vanishes.
- MeshPainter reseed: in-place fast path avoids full GPU backend teardown on
snapshot apply (no ~70MB RT churn), fixes spawn/paint-load hitch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Installs the Multiplayer Tools package to simulate poor network
conditions (delay/jitter/drop) in the editor/dev builds, replacing the
Debug Simulator inspector fields removed in UnityTransport 2.x.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hide remote avatars until this client has actually applied a snapshot
reaching the owner-published baseline cutoff, preventing the default
grey/white material from flashing before a player's painting has loaded.
- PlayerPaintSync: owner-authoritative _publishedCutoff NetworkVariable,
receiver tracks _appliedCutoff; explicit blank publish for never-painted
avatars so they still reveal.
- NetworkPlayerSetup: cache _paintSync, hide body renderers on spawn, gate
UpdateVisibility on PaintRevealReady.
- MeshPainter.Pick: fur toggle now follows pickedHeight (own paint-mesh
only), matching the Occlusion/Height pipette behaviour.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Avatar-Setup und HUD-Autoring fuer das Fur-Feature (Code liegt in 1b783a3):
- Player.prefab: FurShellRenderer am Player verdrahtet, furLength-Default 0.5
- PaintHudPanel.prefab: Slider_FurLength + Fur-Panel-Widgets
- The Room.unity: Fur-Testaufbau
- FurShell_Test.mat: Test-Material fuer den Shell-Shader
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Fur-Map trägt nur noch R = Länge (linearer Datenkanal); Farbe kommt per-texel
aus der Live-Albedo-RT (an _BaseMap des Shell-Shaders gebunden)
- Toggle_Fur als echtes Gate (IsChannelActive = fur), Default aus, furLength 0.5
- Weicher Alpha-Übergang (radial/tip/length coverage) statt Hard-Clip; Transparent-
Pass mit Depth-Bias (Offset -1,-1) gegen Body-Durchflimmern
- Root dunkler / Tip heller aus gemalter Farbe abgeleitet (kein Fix-Tint)
- Gravity/Curl in World-Space nach Transform (unabhängig von Mesh-Rotation)
- Pipette aktiviert Fur-Toggle immer; liest R-Kanal als Länge
- First-Person blendet FurShellRenderer mit aus
- Netzwerk (FlagFur), Undo, Persistence (fur.png R=Länge) mitgezogen
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fur wird die vierte kanonische Paint-Map (RGB=Farbe, A=Länge). Integration
über Seed (kahl), GPU/CPU-Paint, Undo, Netzwerk-Sync, Persistence (fur.png,
v6) und Live-RT-Hook im FurShellRenderer. HUD-Binding (Toggle/Picker/Alpha)
aktiviert. Fur-Alpha lerpt jetzt zu einer Ziel-Opacity (Slider_FurOpacity)
statt additiv Richtung 1 zu akkumulieren (zwei Brush-Passes: Fade + Deposit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FurShell now reads an RGBA fur map (RGB = per-texel strand colour,
A = fur length) so the standalone look can be driven per-texel. Alpha 0
clips the whole shell stack (root included) so a painted body shows
through; _SolidRoot keeps the opaque base shell only for the full-coverage
prototype. White map default keeps the standalone prototype unchanged.
FurShellRenderer drops the MeshFilter requirement and resolves its mesh
per frame: on a SkinnedMeshRenderer it borrows the SkinnedColliderBaker's
per-frame baked pose so the fur follows the animation with no extra bake.
Curvature/gravity now bends strands toward WORLD down (converted into
object space via unity_WorldToObject) instead of object-local down, so a
rotated player mesh droops downward correctly. Identity rotation is
unchanged, so the test sphere is unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FurShell.shader renders N normal-extruded shells with per-strand
alpha-cutout for hair/fur/grass on any mesh (needs only normals/UVs).
Parameters cover length, density, thickness, taper, tip fill, curvature,
per-strand direction randomness, wind sway and silhouette strand stretch.
FurShellRenderer drives every parameter via MaterialPropertyBlock and
draws the mesh shellCount times with RenderMeshInstanced from
beginCameraRendering (works in edit, game and screenshot cameras).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The orbit rig read the wheel unfiltered for zoom, so scrolling a paint-mode
scroll list also zoomed the camera. Gate the zoom behind a shared
InputUtil.PointerOverUi() helper (EventSystem raycast); MeshPainter's private
IsPointerOverUi() now delegates to the same helper (DRY).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the former per-value PlayerPrefs storage with a shared human-readable
Settings.json store (GameSettings). MouseSensitivity keeps its public .Multiplier
API and only swaps its backing to the JSON store, so the camera controllers stay
untouched. Add a MasterVolume facade that drives the AudioMixer's exposed
"MasterVolume" parameter (loaded from Resources, linear<->dB mapping), applied at
startup and on change, plus a MasterVolumeSlider binder. The default is the level
currently authored on the mixer instead of an arbitrary constant.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the scene-derived hudSaveId/key mechanism and save the HUD layout to a
single PaintHud.json under persistentDataPath.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the flat binary .paint format with a folder per painting under
Paintings/<key>/: albedo/emission as sRGB PNGs, the packed MSO map split
into human-readable metallic/occlusion/smoothness grayscale PNGs, and the
non-baked scalars (name, size, emission intensity, camo/height knobs) in
painting.json. MSO.B is always 0 (pack shader), so the split round-trips
losslessly with no alpha ambiguity. Public API (SavePaintAs/LoadPaintByName/
ListSavedPaintings/PaintExists/DeletePaint) and the network snapshot path
(WriteCanonicalMaps/ReadCanonicalMaps) are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Count spawned role variants from a client-side avatar registry in
NetworkPlayerSetup (network objects exist on every machine), so the
counts work without server-only match state. MatchHud binds the two
count texts and rewrites them only on change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Authored HUD count widgets (Hunter/Hider label + count) wired to new
_hunterCount/_hiderCount serialized fields; phase labels switched to English.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove Assets/FR2_Cache.asset (+ .meta) and gitignore it - it is a
regenerated Find References 2 editor tool cache, not source.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the MatchHudCanvas (TopBar > Phase/Time, Crosshair > H/V) and move
PaintHud onto a scene-authored canvas, matching the binder rewrites. Your
scene authoring, kept in its own commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the code-gen canvas/EventSystem bootstrap and the layoutPrefab
instantiation; the canvas and its panel now live in the scene, so PaintHud
binds to its first child at startup instead of building the UI at runtime.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the runtime IMGUI (OnGUI) HUD with a binder that drives the authored
MatchHudCanvas widgets via [SerializeField] references (Canvas / Phase /
Time / Crosshair). Visibility toggles Canvas.enabled instead of the
GameObject, so Update keeps polling while hidden; an Awake guard disables
the component on unwired references. Drop the AddComponent<MatchHud>()
bootstrap in MatchManager - the component now lives on the scene canvas.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Paint RTs were ARGB32 (8 bpp). Repeated source-over at low alpha rounds
back to the previous value once the per-stroke decrement dst*alpha drops
below half an LSB, so it freezes at a grey floor (~0.5/alpha in 255ths) -
e.g. black at low opacity never reaches 0. Switch the accumulation buffers
to ARGBHalf (16-bit linear). The sRGB round-trip stays at the byte boundary
(seed decodes on upload, ReadRtPixels re-encodes on readback), so canonical
bytes and CPU/GPU parity are unchanged - only the accumulation gained bits.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Main: hunterCount 0 -> 1, wire connectTimeoutSeconds = 25 on the session
manager. The Room: player prefab tag Player -> Untagged, deactivate an object.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Relay host/join/refresh/sign-in could hang forever: none of the UGS
Sessions 2.2.4 calls accept a CancellationToken, so a stuck op left _busy
true and locked the whole browser UI with no recovery.
Wrap every hangable call in a Timed() helper (Task.WhenAny + Task.Delay,
the only option the API allows) driven by a new connectTimeoutSeconds
inspector field (default 25s, 0 = wait forever). On timeout the status is
reported, _busy is cleared and the event-driven UI recovers immediately.
Guard the two ways a late/partial op can corrupt shared state:
- LeaveIfDisplaced: an aborted CreateSessionAsync keeps running and may
materialise a ghost host seconds later; leave it, but only when the user
has no tracked session (_session == null), else it would tear down the
shared NetworkManager and kill a fresh host.
- Partial-start shutdown: only for connection-owning ops (host/join), so a
refresh timeout during hosting can't shut down the user's own host.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce PersistentPlayerId: a stable id that survives an app restart and is
unique per install (built on the UGS install-path-namespaced anonymous
PlayerId, with a local PlayerPrefs GUID fallback for loopback). Clones on the
same machine stay distinct.
Each client reports its id to the server on connect via a new Match_Identity
named message (mirroring Match_ReportHit, so no ConnectionApproval timing
risk); the host records its own directly. The server keeps an ephemeral
clientId -> persistent id map, exposed via PersistentIdOf(clientId) as the
foundation for later reconnect recognition. Map entries are cleared on
disconnect and net stop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The freshly converted hunter's body was hidden for the settle window via a
WaitForSeconds coroutine. If the object was disabled or the converting owner
disconnected mid-settle, the coroutine was stranded and _renderGate stayed
true forever, leaving the avatar invisible on every remote.
Replace it with a frame-driven Time.time deadline (_renderGateUntil) lifted
in UpdateVisibility, which runs every frame the component is active and can
never get stuck. Remove the coroutine and the now-unused System.Collections
using. Visible behaviour in the normal case is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The FreeCamera's CharacterController stayed enabled permanently, so its
invisible capsule sat in the play area at (0,1,0) and blocked players even
when the freecam was not in use: the controller script disables only itself
in Awake, not the independent CharacterController collider.
Keep the capsule out of the physics world unless the freecam is actually
flying: disable it in Awake and toggle it with SetActive (enabled before the
teleport in SyncToCurrentView, which preserves the current enabled state).
Also ignore collision against all live players instead of just the first on
each activation, since Physics.IgnoreCollision is reset when the capsule is
toggled off and back on.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The orbit camera suppressed wheel zoom whenever Shift/Ctrl was held, to
reserve Shift/Ctrl+wheel for the paint brush resize. But Shift is also
the sprint key, so sprinting blocked zoom outside paint mode.
Gate the reservation on PanEnabled (set only by PaintMode.Enter/Exit), so
the wheel is only claimed for brush resize while actually in paint mode;
otherwise it zooms normally.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MapPropIds (in MeshPainter.Backend.cs) referenced the *Id fields from the
MeshPainter.cs partial in its field initializer. Static field-initializer
order across partial-class files is unspecified in C#; after the script
reorg the composite array initialized before the *Id fields and captured 0
for every property id. HasProperty(0) is always false, so AssignMapTexture
bound no maps at all -> the shader fell back to its white default textures,
making every fresh avatar fully glossy (smoothness 1) and emissive.
Build MapPropIds lazily on first access (runtime, after all static
initializers) so the file order no longer matters. The per-stroke allocation
savings are preserved; only a single null-check per call is added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move scripts under Assets/Scripts/ into Components, GameMode, Helper,
Network, Enum, Struct and ScriptableObject folders. Pure file moves
(plus folder .meta files); no code changes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Trim restated narration, dead commented-out code, and oversized class
headers; keep all why-comments (authority, timing, edge cases), tooltips,
and serialization caveats.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract byte-identical duplicated code into shared helpers (behaviour-neutral):
- New InputUtil.cs: single source for mouse-delta/scroll/cursor-lock, modifier
and mouse-button reads, previously copied verbatim across camera controllers,
MeshPainter, FirstPersonMode and PlayerCharacterController.
- GetMatFloat/SetMatFloat replace the 12x material-instance + HasProperty guard
pattern for the camo/height float properties.
- Route two inline button hooks through the existing Hook() helper.
Bespoke variants deliberately kept local (FreeCam SprintHeld = LeftShift only,
MeshPainter.TryGetMouse with out-params).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Force the local avatar out of Paint mode and block the switch key from
re-entering it while the match is in its resolution (reveal) window.
Painting is off for the local player regardless of role. No restore
needed: the match ends by despawning/respawning the avatar, rebuilding
the mode cycle at index 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Select the whole pre-filled painting name one frame after activating the
input field so the first keystroke replaces it. Done explicitly so it works
regardless of the field's authored "On Focus Select All" toggle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ramp the along-surface climb velocity via a gentle acceleration instead of
snapping to full speed, so a brief tap nudges only a little. Sprint scales
the accel so it stays snappy; releasing decelerates at the ground decel rate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the fixed additive brush-size step with a fraction of the current
size, so small brushes resize a texel at a time (fine control) while large
ones jump in bigger increments. Always moves at least one texel per notch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the symmetric sine with a raised cosine raised to
_XRayPulseShape: a long near-zero plateau, a slow rise and a short
full peak. Both shader halves use the identical formula to stay in
phase.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
While a match is in the resolution sub-phase, hiders that have not
been found hold their current pose and position: the pose graph is
frozen (mixer speed 0, current frame re-asserted) and owner input is
disabled while gravity/grounding keep them planted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The pipette now reads the height (occlusion) value from the own
paint mesh and enables the height toggle; picking anything else
disables the toggle while leaving value and alpha untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brush radius now derives from the mesh-wide average UV density instead of
the hit triangle's own density. Per-triangle density varied up to ~7.6x
across the mesh (spiking at UV seams and stretched islands), so the same
brushSize painted a visibly larger patch on seam triangles than on
evenly-mapped ones. A single global scale keeps the brush the same physical
size everywhere; the stamp remains a 3D box, so seams are still bridged.
Also decouple the hover preview opacity from the brush albedo alpha: the
preview now stays fully opaque and is scaled only by the Preview Opacity
property, since the ring is a size/shape indicator, not a paint-strength
preview.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A slab that spans a concave silhouette (e.g. a table side = two legs
plus a top rail, all coplanar and thin) produced one convex hull that
filled the open gap between the legs, so a player standing between them
counted as embedded. Add a refinement pass: each slab whose in-plane
coverage grid shows its convex-hull footprint is less than a
configurable fraction (hullFillThreshold, default 0.6) covered by real
surface is recursively halved along its larger PCA axis until every
piece hugs actual geometry. Solid boards (the shelf) are ~fully covered
and pass through untouched. Verified over a 9600-point grid: zero
bridging (no collider interior lies >6cm from the source surface),
shelf output unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generate multiple convex collider proxies from a mesh by growing
triangles into thin slabs (PCA minor-axis thickness gate) and hulling
each slab, so shelves and open compartments stay hollow while boards
count as embedding surfaces. Baked meshes get readable, deterministic
asset names (scene/prefab path + hierarchy + collision index) instead
of GUIDs. ConcealmentMeter now also excludes a furniture's own concave
mesh collider when a ConcealmentColliderBaker sits in its parent,
symmetric to the existing ConcealmentVolume rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SkinnedMeshRenderer.BakeMesh(mesh) bakes the renderer's world scale
into the vertices. SkinnedColliderBaker already accounts for this and
divides it back out, but ConcealmentMeter did not: it lifted the
already-scaled verts to world via localToWorldMatrix (scale again),
yielding scale-squared voxel positions. This threw off the embedded
broadphase box, the EmbeddedPercent computation (which now drives the
X-ray reveal) and the debug gizmos whenever PlayerScaler != 1.
Rescale the baked mesh to unit scale right after baking (no-op at
scale 1), matching the SkinnedColliderBaker convention documented in
PlayerScaler, so world = localToWorldMatrix holds on unit geometry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a second, additive HDR emission color (_XRayColor) that reveals a
player through walls as a pulsing energy glow, driven during matches:
- Shader: XRayOverlay pass (ZTest Greater, additive) for occluded parts
plus a matching additive term in the forward fragment for visible
parts, so the glow shows everywhere and never double-blends. Exposes
_XRayColor, _XRayPulseSpeed and _XRayPulseStrength for a breathing pulse.
- Render it after opaques via a RenderObjects feature (custom XRayReveal
light-mode tag) on both PC and Mobile renderers, so the through-wall
test is depth-sort independent and no longer drops out at some camera
angles/distances.
- MeshPainter drives _XRayColor alpha through a framerate-independent
fade (xrayFadeDuration) rather than a hard toggle.
- Reveal is driven per avatar on every client: all remaining un-found
hiders glow during the Resolution phase, and a hider whose
ConcealmentMeter EmbeddedPercent exceeds MatchManager.embeddedRevealPercent
glows via an owner-authoritative NetworkVariable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Set occlusion to 0.5 (the new neutral for painted height) so Reset
HUD/Values lands on neutral, and adjust the default brush colour,
metallic/smoothness, active brush and alignment.
The pipette now samples only what is painted (albedo, metallic,
smoothness, emission), not how strongly - the albedo alpha,
metallic/smoothness opacity and emission alpha are preserved instead
of being forced to 1 in every pick path and in FinalizePick.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the occlusion-shift, normal-intensity and parallax-intensity slider
instances to the HUD layout prefab under their stable widget names so the
binder wires them to the painter's material knobs.
Wire three material-backed look sliders (occlusion AO shift, relief normal
intensity, parallax intensity) through the paint HUD, its layout/state
persistence, the .paint slot format (bumped to v4), owner-authoritative
network sync and the runtime reset, mirroring the existing camo-knob path so
they save, restore, replicate and reset alongside the brush values.
Change the occlusion AO mapping from a division threshold to a linear shift:
AO fades from 1 down to (1 - shift) at the deepest painted hole, so a 0 shift
no longer leaves black spots. Enable _NORMALMAP alongside _PARALLAXMAP so URP
transforms the painted relief normal into world space (otherwise it discards
normalTS and the relief is invisible), and pin the added keyword in the build
keep-alive material.
Stop the pipette from picking the occlusion/height value and turn its toggle
off after a pick. Default the knobs to 0.2 shift / 1.4 normal / 1.0 parallax
across the shader, painter, network and HUD fallbacks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reuse the existing occlusion paint channel (no new map, MAP_COUNT stays 3):
0.5 is neutral, above 0.5 raises a hill, below 0.5 sinks a hole and also
fades ambient occlusion (saturate(2*occ)). Bind the MSO map to _ParallaxMap,
enable _PARALLAXMAP, derive a relief normal from the height gradient, and
reseed unpainted occlusion to 0.5 so untouched surfaces stay visually neutral.
Pin the _PARALLAXMAP runtime variant in the build keep-alive material.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Load now applies the selected slot immediately instead of going through a
confirmation dialog. Save keeps its OK button enabled even when the name
already exists (the warning text stays, purely informational) and writes
through with overwrite, so re-saving under the same name replaces the slot.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the second HeroCutout from the Main Camera (the GameManager host is the
intended, single driver), set the GameManager cutout radius/feather, and set
hunterCount to 0.
The painter clones Player.mat (Custom/PlayerCamoLit) at runtime and enables
_METALLICSPECGLOSSMAP, _EMISSION and _OCCLUSIONMAP, all shader_feature_local.
That keyword combination existed on no PlayerCamoLit material at build time,
so Unity stripped the variant and the build fell back to the base variant -
metallic/smoothness/emission/occlusion ignored, giving the flat look. The
keep-alive material that should pin this variant pointed at URP/Lit instead
of PlayerCamoLit, so it kept the wrong shader alive. Swap its shader GUID to
PlayerCamoLit; the three keep-alive keywords already match what the painter
sets, so the needed variant is now pinned into every build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The cutout cone's radius and feather are now multiplied by the hero target's
uniform world scale (lossyScale.x, the same body-scale source the orbit input
uses). The authored values are the scale-1 sizes, so the see-through hole and
its soft edge keep the same on-screen proportion for a small hider or a large
hunter, and a mid-match conversion tracks live since lossyScale is read every
frame.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Destroy every GameObject tagged "PreMatch" locally when the match enters
the Seek sub-phase (hooked in ApplyPhaseLocal, runs on every machine; the
objects are plain scene geometry with no NetworkObject, so each client
cleans up its own copies). Block hunters from starting a wall- or
ceiling-climb during the Hide sub-phase via a WallClimbBlockedNow() gate
in UpdateWallClimbState; an in-progress climb is exited cleanly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two paint-mode pan refinements:
- The max-distance clamp is back to body-relative only (CurrentScale, no zoom
factor), so the world-space pan limit is identical at every zoom level;
only the sensitivity stays zoom-scaled (finer when zoomed in).
- Don't zero the pan offset in OnDisable. The only thing that disables this
component is FreeCamMode taking over, and the freecam snaps to the live
(still panned) view. Zeroing here jumped the orbit camera the brain blends
FROM to center while the freecam sat at the panned pose - the visible
"snap to 0, then translate to the offset" on the paint->free-cam switch.
Leaving the offset frozen keeps blend-from == blend-to (both panned) for a
seamless handoff; the pan glides home on its own once the orbit input next
runs in a non-paint mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The save-dialog name field is a focused TMP_InputField, but global discrete
hotkeys kept reading the keyboard every frame, so typing a name like
"Checkers" cycled the pose (C) and shut the server down (K). Add a shared
TypingGuard.IsTyping check (focused TMP/legacy InputField) and short-circuit
the global handlers on it: pose cycle, network dev keys, mode switch, and
paint undo/redo. Movement was already gated out via ControlEnabled.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The middle-mouse pan now persists when the button is released and only glides back to center on leaving paint mode. Pan sensitivity and max distance scale with the orbit zoom distance, so panning stays consistent on screen at any zoom and unchanged at the default zoom.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the BrushesSquare texture, replace the duplicated brush slots on the
player with it, show a brush preview in the paint HUD, and surface the
spawn-area tags and conversion reveal delay in the inspector.
Undo is implemented (checkpoint-based, replicated); a separate full reset
is intentionally unnecessary since undo covers it. Drop the stale open item
and polish note.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the implemented systems (netcode, hunter role, phase/timer match
loop, win conditions, both match modes, spawn areas, shooting,
role-based visibility, spectator freecam, networked paint) out of the
"missing" section, document the multiplayer and match-loop subsystems,
and trim the open items to what's actually left.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Save and restore the emission intensity and the three camo knobs
(half-Lambert, half-Lambert brightness, specular dampen) with a named
painting. The .paint format moves to v3 with a small scalar block
between the header and the pixel maps; v2 paintings still load with
their painter values left untouched. Loading re-syncs the emission and
camo sliders, and peers pick the values up for free through the
existing owner-authoritative paint NetworkVariables.
Add a HUD reset (Lib_ResetHud button plus context-menu fallback) that
behaves as if the saved hud-layout file were deleted: it removes the
file, restores panels to their authored prefab positions, and resets
the brush values to the painter/material defaults captured before any
carry/restore runs.
Remove the alpha bar from HsvColorPicker so alpha is driven by
standalone Slider_AlbedoAlpha / Slider_EmissionAlpha bindings; the
picker now writes RGB only and the sliders write alpha only, with no
feedback between them. Add a generic SliderCurve component that reshapes
a slider's handle position into a non-linear value (with an inverse for
setting values back), and make the slider wiring curve-aware.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix the in-place Conversion respawn snapping to the origin (0,0,0),
which surfaced when a hider was tagged mid wall-climb. The owner holds
authority over the NetworkTransform, so the server's Instantiate
position is ignored on the owner. Deliver the tagged world position
through a replicated _spawnOverride the server writes in OnNetworkSpawn,
and have the owner re-assert it before PlayerCharacterController's first
Update snapshots the kill-plane rescue target. This replaces the fragile
SpawnedMidSeek phase check.
Also hide a freshly converted hunter on other clients for a short
settle window (conversionRevealDelay) so nobody sees it pop in or
interpolate from the spawn frame; the shot hider is already despawned,
then the hunter cleanly materializes in its place.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>