- JavaScript 68.9%
- Python 17.5%
- CSS 7.5%
- HTML 5.7%
- Shell 0.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| extract | ||
| scripts | ||
| tests | ||
| web | ||
| .gitignore | ||
| README.md | ||
Mannhattan Cash Box
Where MvM cash vanishes on mvm_mannhattan, simulated with TF2's own rules.
Live at https://apps.wizardhat.info/mvm-cashbox/
Click the map to kill a robot (or giant, or tank). Each currency pack is moved the way the server moves it and judged the way the server judges it when it stops.
What the simulation follows
From the Source SDK 2013 TF2 server code:
| Rule | Source |
|---|---|
Pack is a 20-unit box (SetCollisionBounds(±10)), lifetime 30 s |
entity_currencypack.cpp Spawn, tf_powerup.h |
Robot drop: from WorldSpaceCenter, RandomVector(-1,1) with z=1, normalised, ×250 |
tf_player.cpp DropCurrencyPack |
Tank drop: z = RandomFloat(5,20), ×250×RandomFloat(1,4) |
tf_base_boss.cpp |
MOVETYPE_FLYGRAVITY + MOVECOLLIDE_FLY_BOUNCE; bounce = reflect × surface elasticity (≤0.9); floor if normal.z > 0.7; stop under 30 u/s |
tf_powerup.cpp, physics_main_shared.cpp |
Collides with MASK_SOLID plus player clips |
item_world.cpp PhysicsSolidMaskForEntity |
| Stuck with zero velocity off the ground → treated as landed | item_world.cpp FallThink |
At rest: no nav area (5 above … 120 below the centre) → paid; inside an enabled trigger_hurt's AABB → paid; inside any func_respawnroom's AABB, either team → paid |
entity_currencypack.cpp ComeToRest, nav_mesh.cpp GetNavArea |
Box traces against brushes use every brush side including bevels, as the engine does.
Displacements are traced as triangles; ones compiled with "No hull collision"
(ddispinfo_t.minTess high bit set, flag 0x4) are skipped, since box traces pass through them.
What it does not model
- Static props. Their collision shapes are in TF2's VPKs, not the map. Positions are shown.
- Per-surface elasticity. Comes from each material's
$surfaceprop, also in the VPKs. A single value (default 0.25, adjustable) stands in.
Nav mesh
The BSP has no nav inside it. scripts/build.sh ships maps/mvm_mannhattan.nav (or a second argument)
next to the map data, and the page loads it. Its header records the BSP size it was built for; the
extractor checks it against the map. A different .nav can be dropped onto the page.
node tests/nav_analysis.mjs runs the whole map with the nav and reports, for every no-nav payout,
why GetNavArea missed (off the mesh / dip under it / stranded high above) and what the pack rests on.
With the shipped nav, for robots killed anywhere they walk: 17% of packs (19% for giants) are paid by
the no-nav rule, mostly in the strip the nav leaves along walls, and 55% of those rest on displacements.
Build
scripts/build.sh maps/mvm_mannhattan.bsp # writes web/data/ (map + nav) and dist/
node tests/sim_test.mjs # physics against the real map
CHROME=<chrome-headless-shell> node tests/browser_check.mjs dist <shots dir> # end to end
extract/bsp_extract.py reads VBSP v20 with the standard library only.