Skip to content

SI spawn placement visualizer

Matches the 2026-08 workspace (inf_nav_team_nearest 1): the extension runs one multi-source reverse Dijkstra, recording for every Nav area "the path distance to the nearest available survivor + its owner". The plugin searches ten fixed distance layers from near to far, consuming each layer front-to-back by Flow. Gray walls are unwalkable, making it obvious that path distance ≠ straight-line distance. Survivors can be dragged directly; the distance field is then recomputed for the current state.

Drop to parse in the browser (VPK / ZIP / BSP / NAV)
Wall / unwalkable Exclusion ring <250u Current search band (half-open) Candidate being evaluated Filtered out (reason in the log) Accepted into the scoring pool Final spawn point → line points to its owner Unspawnable areas (checkpoint / rescue-closet / rescue-vehicle flags, graph mode only) Map geometry walls (2D wireframe / 3D translucent) Invisible walls (playerclip) Static prop boxes (fainter = estimated size) Gun & ammo spawns Medical item spawns Throwable spawns Upgrade pack spawns Other item spawns Blocker entity boxes (env_*_blocker) Door / elevator / movable entity boxes Other solid entity boxes Alarm cars Buttons / finale / pour targets / mounted guns

2D mode: left-drag grabs a survivor directly, dragging empty space pans the view, the wheel zooms around the cursor, and double-click resets the view; select "Draw wall / Erase wall" and hold-drag to paint (built-in grid only).

Graph-mode visibility: once geometry data is loaded, real raycasts are performed against the BSP triangles (all world+disp triangles occlude, including floors/ceilings; invisible walls do not block sight; only pairs within 1800u straight-line distance are tested). Without geometry it falls back to the heuristic (straight-line ≤1500, |Δz| ≤120, detour ratio ≤1.6). Exported data only contains ground connections (no ladder edges). Real map data is exported on the server with the sm_navgraph_export command, then loaded via "Import map JSON".

Third-party maps need no server: drop a .vpk / .zip / bare .bsp+.nav onto the canvas, or click "Parse local map". Parsing stays in the browser and nothing is uploaded; .7z/.rar are not supported (unzip to a vpk first). The result is the map-file preset set: no Anne stripper blockers, no Director round items, and flow is a self-computed approximation. For official maps or runtime truth, keep using the dropdown / "Import map JSON" with an sm_navgraph_export dump.

Survivor states (click to toggle, drag to move)

"Cap" = SI_Target_limit targetLimit ((SI cap / mobile survivors) + 1). The leader (highest Flow) uses ×2 when judging capacity. At the cap the member leaves the participant set and the distance field is rebuilt from the remaining members (the plugin-side fingerprint reset + snapshot rebuild).

Current search state

Band: - Inspected: 0 Pool: 0/12 Spawned: 0

Scene saves (browser local)

Search log (matching the [FIND]/[SpawnPerf] wording)

The full pipeline of one normal spawn attempt (team-nearest branch)

0. Participant setIterate survivors: alive, not pinned, not incapacitated, below target capacity (leader cap ×2) → a (client, navId) list. Any member movement or capacity change alters the fingerprint → the cursor resets and the snapshot is rebuilt.
1. Extension snapshotThe worker thread runs one multi-source reverse Dijkstra (AnneBuildTeamNavCandidateSnapshot): every Nav gets "min path distance + owner"; areas within a 250u straight line of any survivor are removed; all candidates are sorted into flowOrder by descending Flow.
2. Distance layersTen fixed layer edges 250/350/500/700/900/1150/1400/1700/2000/2400/3000, intersected with the class min/finalMax into half-open bands; empty bands are skipped (AdvanceToUsableBand).
3. Owner checksCandidates are taken front-to-back by Flow within the band: an invalid owner → ownerInvalid++ and skip; an owner at capacity → ownerCapacity++ and skip (without resetting the cursor). The evaluation frame is then bound to the owner (eye position, feet, progress bucket).
4. Hard filtersNav flags → behind-flow hard reject (cand−owner < −8, HJC −4; exempt when the owner is ≥90%) → cooldown → per-bucket cap (off by default) → random point: hard sep 100u/distance/exclusion → stuck check → visibility → path reachability.
5. ScoringDistance-band fit, Flow gradient (soft penalty behind, HJC ×2.5), height, and kernel density (SI already in the fight are ignored; kernel radius shrinks with the SI cap) → a total score ≤0 is discarded.
6. Pick and spawnWhen the scoring pool holds 12 or the band is exhausted → the top score spawns and the commit is recorded to the owner; when all bands are exhausted → Director Range fallback (2000u straight-line cap).

How this page maps to the real implementation / simplifications

Real implementationThis demo
AnneSpawn_NavCandidatesPrepareTeam / CollectTeam (multi-source Dijkstra; owner = the survivor providing the shortest path, ties go to the lower index)8-direction grid Dijkstra recording dist+owner per cell, ties to the lower index; 1 cell = 50u
TEAM_NAV_LAYER_EDGES 10 layers ∩ class [classMin, min(finalMax, inf_SpawnDistanceMax×2)], half-open bands with a closed final bandIdentical (clipped to the selected class, empty bands skipped)
flowOrder within a band: descending Flow (front first); team mode does no tercile interleaving and no shufflingIdentical (Flow = horizontal progress)
Exclusion within a straight-line max(250, inf_SpawnDistanceMin) of any survivor; visibility trace; stuck check; path re-validationExclusion ring + grid line of sight (Bresenham wall test); stuck and path re-validation omitted
inf_score_behind_reject_gap 8 / _hjc 4; with owner progress ≥90 it only penalizes without hard rejectingIdentical
SpawnScore_BuildCandidate multi-factor scoring + best pick from a 12-candidate budgetFront bonus − behind gradient − distance deviation − kernel density; hard-reject within 100u; best of a 12-candidate budget
SI_Target_limit: limit = (SI cap / mobile survivors) + 1, leader ×2 (spawn-capacity accounting only)Identical; each spawn adds 1 to the owner count, and at the limit the member leaves the participant set and the distance field is rebuilt
Bands exhausted → Director Range fallback (DIRECTOR_FALLBACK_MAX_TEAM_DISTANCE = 2000 straight line)Indicative marker (a random feasible far point)
Kernel density (in-fight SI ignored, radius shrinks with the SI cap) / hard separation (fixed 100u) / Nav cooldown / teleport supervisionCooldown, hard sep 100u, and kernel density (radius shrinks with the SI cap) are implemented; teleport supervision is omitted
Real map Nav graph (exported by sm_navgraph_export: area centers/corners/flow/attribute bits/directed edges)Graph mode: the same state machine runs on the imported adjacency list; with geometry data loaded, visibility uses real triangle raycasts (still slightly different from engine traces: no dynamic props, no glass material semantics), and falls back to the heuristic (straight-line ≤1500, |Δz| ≤120, detour ratio ≤1.6) without geometry; no ladder edges, and the endgame branch of IsValidFlags is not replicated
The blocked snapshot is read live from the engine every frame by the extension (dynamic blockers such as elevators/doors/nav_block)Graph mode uses manual "Block areas" marking; Dijkstra and candidate collection simply skip blocked nodes
The item/blocking entity layer is a runtime snapshot taken after map load (so it naturally includes stripper additions/removals/changes); at round start the Director may prune some pre-placed spawn points based on item densityThe visualizer renders item icons and blocking boxes exactly as captured in the snapshot, does not simulate the Director pruning, and requires v2 export data
Difference from the old mode (inf_nav_team_nearest 0): the old mode locked each class to a single target survivor, built a single-source distance field from that target, widened distance bands per class linearly, and interleaved near/mid/far terciles with shuffling; the new mode uses one distance field for the whole team, owners emerge from the shortest paths, distance layers are shared by all classes, and bands are consumed front-first.