# PORT-ARCH: how the ~812 page kills get into the game (read-only investigation, 29 Oct 2026)

Repo `/Volumes/Workspace/Projects/StickFight` (C# paths are under `StickBrawl/Assets/_Game/Scripts/`). Branch facts come from
`git log` / `merge-tree` run 29 Oct ~00:30 IST. Nothing in the repo was changed. Scratch: `/Volumes/Workspace/Scratch/portarch/`.

**The answer in one paragraph.** Every kill is ported by hand: page JS becomes C# line for line, about 380 lines and about
23 KB of IL per kill. There is no JS-to-C# converter. The only data-driven film is THE OFFERED HAND, which was baked
(tf-movie). None of the 28 ported kills is on v4. None of the 26 SMG ports ever passed the PORT GATE, and all 26 are stale:
every SMG page file changed on 28 Oct. All the kill branches already merge cleanly together in **`v4-play4` (e35e2350)**.
Only tf-movie is missing from it, and adding it gives 6 conflict hunks in 4 files. The real limit is not the number of
porters. It is the shared Unity lock, the player slots and the time each kill's gate takes (about 20 min of player time
per gate pass, with 3-6 passes per kill so far). The 15 central page fixes of 28 Oct are not in the game, and neither are
58k_style.js and most of the STYLE 3 kit. Those fixes have to land first, or every kill ports onto the wrong base.

---------------------------------------------------------------------------------------------------------------------------
## 1. Branch state

| branch (worktree) | tip | vs v4 (ahead / behind) | what it is |
|---|---|---|---|
| v4 (StickFight-dev) | bebef64b 27 Oct 10:41 | | main |
| tf-kills (StickFight-v3-kills) | e3bc5372 27 Oct 01:02 | 204 / 21 | TF-PORT-ENGINE: the G00 engine after 24 Oct (anatomy, blood, bake, clip, ordered pen, quality, perf, parity, units, the KC / KD / KE / RIF / Slice kits), smg.skin-taped |
| tf-killsys (StickFight-killsys) | 0f1b85e9 27 Oct 01:05 | 311 / 21 | TF-PORT-SYSTEM D2c-D3+ on tf-kills 4e810b2e: catalogue, KILL TEST, handback, hazards, multi-kills, net (protocol 40), stage58, reach, callout, labs |
| tf-kills-smg1 (…-kills-smg1) | 6abf0a43 27 Oct 01:07 | 311 / 21 | 16 SMG kills (KillCineSmgSkins6 / B / C.cs), merged tf-kills f43fdf1d |
| tf-kills-smg2 (…-kills-smg2) | 3d9b23dd 27 Oct 00:59 | 247 / 21 | 10 SMG kills (KillCineSmgD / F.cs), KillCineFilmGrade.cs (d9c16c2c), KillCineLabCosm.cs, region_ssim.py |
| tf-kills-anatomy / -blood / -slice / -zone | all eac34eda 25 Oct | 6 / 38 | Spent: fully contained in tf-kills and in every branch above |
| tf-kills-pilot-wip | 597ad764 24 Oct | 1 / 92 | "WIP, not for merge". Ignore it |
| tf-movie (StickFight-movie) | ee82bbdb 28 Oct 23:30 | 218 / **0** | tf-kills e3bc5372 + v4 bebef64b (0cc466e2) + 13 MOVIE-PORT commits (0e38c90c … ee82bbdb) |
| tf-polish (StickFight-v3-perf) | 496b72b6 24 Oct | 0 / 53 | **Already in v4** (HudGloatKill: 38bc7fcc, the plate: e6cd2c38) |
| **v4-play4** (no worktree) | e35e2350 27 Oct 10:51 | 489 / **0** | The user's play build: v4 bebef64b + tf-kills + smg1 + smg2 + tf-gunsfx3 + tf-killsys, conflicts already resolved (41fe9916, 3ddfbe04, e38d3287, a099d7cb, fcee629a). It has **no play-only commits** (`git log --no-merges v4-play4 ^v4 ^<sources>` is empty) |
| friends-play (StickFight-online-c) | fbc1fd34 28 Oct | 581 / 0 | v4-play4 + friends 3c8d10f9: the online tester build. **It already carries tf-killsys / smg1 / smg2** |

How the branches relate: they share base 4e810b2e (27 Oct 00:18). From there tf-kills adds 10 commits, tf-killsys 117,
smg1 117 (it holds 9 of tf-kills' 10), smg2 53, and tf-movie is tf-kills plus 35. The base itself is **194 commits that
are not on v4** (176 of them made on 26 Oct).

**Kill code ON v4 now:** the G00 engine base (merge 3cc04c37, 24 Oct: KillCine.cs, KillCineScene / Actor / Flow / Draw /
Canvas / Overlay / Spot / Stage, the sound library, cue tables, the SMG sound pass). TF-PORT-SYSTEM D1 + D2a/b (f69d5a88,
26 Oct: switches, 6-level gore, film choice / rotation, SKIP). Its merge note says "inert until ports land". The kill
cam's gold plate (a5e010ba, e6cd2c38) and the gloat at the kill (38bc7fcc). Map export CU06 (29ca22ff). GUN-SFX
(f06006b9). The old kill systems are still live: SkinKill* (238 skin kills) and FinisherKills (TF-FINISH's 76). They retire
per weapon once that weapon has films (KS09).

**NOT on v4:** 41 engine files (KillCineAnat*, Blood, BloodBake, Clip, PenOrder, Quality, Perf, Parity, Units, Cache,
Ladder, Looks, Wounds58, Faces58, Gravity58, K40Fx, the KC / KD / KDScr / KE / KESpec / Rif / Slice / SliceB kits,
KillCineSmg.cs (the manifest), KillCinePages.cs, KillSysMapData.cs). Also 22 kill-system files (KillCatalog / Api,
KillSysHandback, Hazards, Items, KillsTab, Multi, Net, Perf, Reach, Stage, KillTest, UI/HudKillSysCallout, ReskinKillTest,
the KillSysLab* parts). The 26 SMG lane files. The movie: KillCineMovie / Data / Draw / Realm / Gen / Lab, KillCineMusic,
KillCineBloodV2, shaders, `Resources/KillMovie`, `Resources/KillMusic`.

**Dry-run merges** (`git merge-tree --write-tree --name-only`):
- Clean: v4 + each of tf-kills / tf-killsys / smg1 / smg2 / tf-movie; smg1 + smg2; killsys + smg2; tf-movie + smg1;
  tf-movie / killsys + friends / friends-transport / -resilience / -ui / -telemetry.
- tf-movie + tf-killsys: 4 files (KillSysFilm.cs, KillSysLab.cs, World.cs, UI/HudKillSysSkip.cs). tf-movie + smg2:
  KillCineLab.cs. killsys + smg1: World.cs (already resolved in v4-play4 fcee629a).
- **v4-play4 + tf-movie: 4 files, 6 hunks** (tree b5f5d719):
  - KillCineLab.cs:202 (1 hunk): play4's `cosmLast` vs the movie's `AudioTap vidTap`. Keep both.
  - KillSysFilm.cs:208, :225 (2 hunks): play4's coroutine `NoFilmCo(...)` vs the movie's `legacyOnly ? Legacy(out decided) : NoFilm(..., k, v)`.
    This one is semantic: the movie's legacyOnly fallback has to be carried into the NoFilmCo flow.
  - KillSysLab.cs:61 (1 hunk): the parts list. Take the union, including the movie's "movie" part.
  - HudKillSysSkip.cs:16, :89 (2 hunks): play4's page-scaled pill (K, W = 76k) vs the movie's PressFx holder fix. Keep both
    (the holder around the page-scaled pill).
- tf-movie + friends-play: the same 4 files, because friends-play contains v4-play4.

**Where a new port integration branch should start.** Make it from **v4-play4 e35e2350**, then merge tf-movie ee82bbdb
(the 6 hunks above). Nothing else has to be merged first, because play4 already contains tf-kills, smg1, smg2, killsys
and gunsfx3, and v4 has not moved since bebef64b. tf-polish and tf-kills-* are already in. This one branch is the engine
base the batches need. It has to pass csc, build_test and the regression labs, and then go into v4 **with the 27
unpassed ports switched off** (§3, §11), so that every batch branch starts from v4.

---------------------------------------------------------------------------------------------------------------------------
## 2. The film engine: how a page kill becomes a game kill

**It is a mix, and the kills themselves are hand-written C#.** Each page def plus its `build(s)` is translated line for
line into a C# `KillCineDef` with a `build = s => {...}` lambda (PORTING_GUIDE.md §4 and §6 on tf-killsys hold the
translation table). It keeps the same rng call order and the same numbers, and gives each gore-level branch its own
ending. Only tables are generated:

| tool (tools/kills/, on tf-movie unless noted) | what it does |
|---|---|
| port_check.py | Lists the scene / body / field calls a page kill makes, each marked HAVE or MISSING in G00 (`--src`, `--weapon smg --missing`). It is a scanner, not a converter |
| port_snd.py | 58k_snd_<weapon>.js → KillCineSound<Weapon>.cs. **Only SMG has been generated** (KillCineSoundTables.RegisterAll registers only RegisterSmg) |
| gen_poses.py | 40k pose table → KillCinePoses.cs |
| make_maps.py | 58k_maps.json → KillSysMapData.cs (351 KB) |
| make_catalog.py (tf-killsys) | 58k_library.js → KillCatalog.cs. **Stale:** 968 rows from 26 Oct 03:57; the library now has 1141 |
| movie_bake.mjs | Runs 58m_movie.html headless and writes `Resources/KillMovie/offered_hand.bytes` (MovieBake v1: 120 Hz, 4 bodies × 13 joints, 6 sword tracks, camera, shot, look, realm; 475 KB) plus KillCineMovieGen.cs. **The only bake path** |
| music_cue_compile.py | MUSIC KIT cue sheet → Resources/KillMusic/<film>.json |
| parity_page.mjs, movie_page_frames.mjs | Page-side captures (headless, 1688×780, six levels, ~20 frames; writes game_in.json for the lab) |
| parity_cmp.py, region_ssim.py (smg2), ab_check.py, strips.py, movie_sbs.py, uniq.py, page_snd.mjs + snd_compare.py | Comparison and sheets (§4) |

**Runtime classes.** The registry and defs are KillCine.cs: `KillCineDef`, `Def / Kill / Skin / Place`, the wire ids
(`WireStride = 64`, `CommonSlot = 61`, `TestSlot = 60`) and `EnsureBuilt → RegisterAll → RegisterPages`. The rest:
- Manifests: KillCinePages.cs `RegisterPages()` and the per-weapon manifests (only KillCineSmg.cs exists: 42 fixed places).
- The s.* API: the KillCineScene partial class (Scene, SceneFx, K58, Overlay, Ladder, Looks, WorldText, Wounds58, Anat*, Gravity58).
- Bodies and world: KillCineActor (13-point verlet), CineClock (KillCineClock), KillCineFlow (lead-in / trigger / exit /
  plate / word), KillCineView (camera), KillCinePage / Map58 / Stage / Spot (the real arena in page coordinates).
- Drawing: KillCineDraw / Canvas / PenOrder / Clip / Cache.
- Blood and effects: CineBlood (KillCineBlood, BloodV2 on tf-movie, BloodBake), KillCineFxP, KillCineQuality (FilmQ).
- Sound: KillCineSound / SoundLib / SoundTables / SoundSmg / GunSfx.
- Kits: KC, KD, KDScr, KE, KESpec, Rif, Slice, SliceB, Arrive.
- The movie: KillCineMovie* and KillCineMusic.
- The kill system: KillSys (the `enum KS` switches, KillSys.cs:18), KillSysFilm (`Try`, KillSysFilm.cs:168), KillCineRotation
  (the pick), KillSysPerf (prewarm), KillSysRecap, KillSysSkip, KillSysGore, KillSysStage, KillSysHandback, KillSysMulti,
  KillSysNet, KillCatalog, KillTest.
- Tooling: KillCineLab, KillCineParity, KillCineUnits, KillCinePerf, KillCineBugFlags, KillSysLab*.

**One kill, page to PASSED** (PORTING_GUIDE §4, and the porter scripts in each Scratch/tf-port-smg<n>/tools):
1. `python3 tools/kills/port_check.py <w>.<id>` and read the whole def plus helpers at all six levels. Engine gaps go to
   `/Volumes/Workspace/Scratch/tf-port/REQUESTS.md` (shared kits are the integrator's).
2. `node tools/kills/parity_page.mjs --keys <w>.<id> --out <scratch>/<id>/page` (about 1 min per kill, one Chrome at a time).
3. Port the def into the manifest place plus the lane file (`static partial void <W>_<Name>()`). Then
   `zsh tools/csc_check.sh` must print `== errors: 0`.
4. Copy game_in.json to `tools/kills/parity_in/<w>_<id>.json` and commit it with the kill. The coordinator pushes.
5. Build: `vmctl lane up/build <branch>` on the VM (incremental build 41 s; a cold build 221 s) or a Mac build (5 min wall,
   for example 19:07 → 19:12 for userbuild2: `[Build] Succeeded size=200497403 time=00:02:23`).
6. Labs: parity at 6 levels with rngtrace (~5 min), perf q=all plus `-perfslow 6` (~1-2 min), play on 18 arenas at g5 and
   g0 (~6 + 5 min), the mid / zone version, the edge cases. **About 18-20 min of player time per pass.** For example THE X
   g6: par done 00:35, perf 00:37, ar5 00:44, ar0 00:49.
7. Compare: `parity_cmp.py`, `region_ssim.py`, `strips.py`, gate_table2.py (that last one is in Scratch, not the repo).
   Iterate. Then the coordinator reviews and the kill merges.

**What one kill took before:**
- SMG-1 ported 16 kills from 26 Oct 02:20 to 21:04, about 1.2 h of code per kill.
- SMG-2 ported 10 kills from 02:39 to 00:42, about 2.2 h per kill.
- Both then ran gate passes g3 … g11 until the pause (27 Oct ~01:06) and **passed 0 kills**. The causes were engine-side
  rng / blood / cavity / elbow gaps (TF_PORT_SMG1.md "PAUSED"; TF_PORT_SMG2.md "STOPPED"; THE X g6: back arm 49 u off,
  FILM SSIM min .42).
- THE OFFERED HAND, done by bake plus hand draw code: 14:50 → 22:40 to all gates green (about 8 h, one agent, one
  bespoke film).

---------------------------------------------------------------------------------------------------------------------------
## 3. Kills already ported

| key | branch | file | merged to v4? | gate |
|---|---|---|---|---|
| smg.skin-taped (the parity pilot) | tf-kills (and everything built on it) | KillCineSmgSkins.cs:15 | no | not passed |
| smg.skin-classic / camo / cartoon / ice / neon / gold, the-autograph, bank-shot, tear-along-the-dotted-line, long-exposure, range-card, house, vacancy, greenhouse, brick, shelf (16) | tf-kills-smg1 | KillCineSmgSkins6.cs, KillCineSmgB.cs, KillCineSmgC.cs (6101 lines) | no (in v4-play4) | 0 passed (g9 / g10 numbers; g11 stopped) |
| smg.the-x, knee-slide, matador, wall-run, name-on-the-stone, the-saw, lathe, xray-chain, peephole, bleed-out (10) | tf-kills-smg2 | KillCineSmgD.cs, KillCineSmgF.cs (2875 lines) | no (in v4-play4) | 0 passed (g6 stopped) |
| common.offered-hand (THE OFFERED HAND) | tf-movie | KillCineMovie*.cs + offered_hand.bytes | no | all gates green 28 Oct 22:40 (9d342cc9) |

The SMG ports are **stale**. Every 58k_w_smg_*.js changed on 28 Oct; SMG-2's snapshot of 58k_w_smg_f.js was sha256
002d4cb6…, and the file is now 2ef96118…. Names changed too: smg.the-autograph is now "THREE MAGS" and smg.lathe is now
"THE CHAIN" in port_batches.json. New SMG keys have no manifest place yet: smg.blind-fire, dogpile, stalemate, taken-back,
the-staff. Treat the 26 as a head start when their batch comes, not as done.

---------------------------------------------------------------------------------------------------------------------------
## 4. Parity, gate and perf tooling (exact commands, sample outputs)

- **csc_check** (`tools/csc_check.sh`): Roslyn type-check of Assembly-CSharp, about 3.5 s, holds the machine "csc" slot,
  and needs the worktree's Library rsp (sparse worktrees symlink Library). Output: errors, then `== errors: 0`.
- **build_test.sh**: `./unity.sh BuildMac buildtest -out Builds/MacTest/StickBrawl.app`, behind `tools/locked.sh unity`
  (ONE editor on the whole Mac). On Linux it runs BuildLinux plus `tools/linux/mactest_compat.sh`. Output:
  `[Build] Succeeded size=… time=… errors=0`. Porters never run it; the coordinator does at merge.
- **locked.sh** (`tools/locked.sh <unity|player|csc> -- cmd`): queued machine slots in /tmp/stickbrawl-lock-<kind>.
  Mac: unity 1, player 2, csc 1. VM (env.sh): unity 1, player 2, csc 2. Stale PIDs are reclaimed.
- **killlab.sh** (`tools/kills/killlab.sh <name> "killcinelab:<part>:…" [player args]`): APFS-clones the build
  (`KILLLAB_BUILD`, default MacTest), runs on the player lock with a group-kill timeout (`KILLLAB_TIMEOUT`, 2400 s) and
  prints PASS / FAIL / SUMMARY / PERF.
  - Parts (KillCineLab.cs:7-17): play (default; `:w= :ids= :arena=all :stills :strip :gore=N :air`), parity
    (`:in=<parity_in json> :ab=bake|blend :post :comp :rngtrace`), perf (`:ids= :gore= :q=0,1,2|all|auto :gov=1 :probe=1
    :bake=0`), mid (`:w= :ids= :arena=all`), rot, spot, net, units.
  - Sample play output (THE X g6): `[KILLLAB] PASS smg.the-x "THE X" RARE arena=castle began=True t=6.38/5.7 film=7.69
    killed=True exceptions=0 sound: cues=24 fired=28`, plus `[KILLLAB-FLAGS] … no-victim x2`.
  - Sample perf output (THE X LOW): `[KILLLAB-PERF] smg.the-x g5 LOW frames=252 film ms p50=3.83 p95=6.73 max=14.32 | begin
    7.9 ms alloc 324 KB …`, followed by -PIECES / -CLIP / -LAYERS / -BEGIN / -GROWTH lines.
  - Sample perf output (the movie MED): `p50=3.03 p95=7.36 … mono 76.8 -> peak 79.5 … GCs during the film 0`.
- **The parity pipeline:**
  - `node tools/kills/parity_page.mjs --keys … --out …` runs the page side.
  - The game side: `KILLLAB_TIMEOUT=2400 zsh tools/vm/vmctl.sh lane lab <branch> tools/kills/killlab.sh par_<id>
    "killcinelab:parity:in=…json" -w 1688 -h 780`, then `vmctl stills <branch> par_<id>`.
  - `python3 tools/kills/parity_cmp.py <page> <game> --out <sheet>` writes `{"frames": 114, "failing": 114, "pass": false}`
    plus sheets.
  - `region_ssim.py` writes lines like `the-x g5: FILM 0.58 bodies 0.54 blood 0.62 … arena(masked) 0.79`.
  - The rngtrace lines are `[KILLLAB-RNGTRACE] … up to frame N: K draws | …`.
- **The arena strip:** `killcinelab:play:w=<w>:ids=<id>:arena=all:stills` (or `:strip`), then `tools/kills/strips.py
  <capture>`, which prints `18 arenas, 18 flags`.
- **Perf labs:** `killcinelab:perf:…:q=all` plus `-perfslow 6` (Game.cs:60, `Perf.Governor.SimulateSlowMs`). The kill
  system's gate is `tools/killsys.sh <name> perf -ksperftiers` (KillSysLabPerf.cs: PF01-PF05, 0 B system share, start
  hitch ≤ 2× median). Also available: `-ksperfgov`, `-ksperfpasses`, `-ksperfuncapped`.
- **killsys lab** (`tools/killsys.sh <name> [parts]`, default all): switches, wire, gore, ladder, killtest, ktui, reach,
  maps, holds, multi, spot58, perf, killstab, results, handback, plus "movie" on tf-movie (KillCineMovieLab).
- **Movie labs and flags** (tf-movie only): `-movieAlways` (KillCineMovie.cs:101; also the KS.MovieAlways switch),
  `-movieKit` / `mvdbg=kit` (KillCineMusic), and `mvdbg=probe|postoff|filmdim|…` (KillCineLab.cs:62-65,
  HudFinisher.cs:349). `KILLLAB_BUILD=MacKills tools/kills/killlab.sh mv_play
  "killcinelab:play:w=common:ids=offered-hand:stills:kw=pistol"` gave `[KILLLAB-SUMMARY] part=play pass=3 fail=0
  errors=0 sceneExceptions=0 started=1 ended=1`.
- **Page side:**
  - `_tools/motionqa.cjs` is the FIX step's motionQA gate.
  - `_tools/verify_kill.cjs` and `verify_sweep.sh` run verification.
  - `_tools/render_slot.sh` limits headless Chromes to RENDER_SLOTS=2 and supports a PAUSE file for clean perf runs.
  - `_tools/ix_gate.sh` probes every 58k page.
  - "playall" means the page smg playall through render_slot.
- **Gaps in the tooling:**
  - The per-porter gate chains are not in the repo. They live in Scratch: chain_gate2.sh, wait_push_gate3.sh,
    collect3.sh, gate_table2.py, rngdiff.py, parity_page_trace.mjs, qrng_cmp.py, perf_tab.py.
  - region_ssim.py is only on smg2. The cosmetics check crashed in THE X g6 (`cosm.jsonl` missing).
  - No single command runs the whole PORT GATE for one kill and writes the sheet.

---------------------------------------------------------------------------------------------------------------------------
## 5. The 28 Oct central page fixes vs the game engine

None of the 15 valves has a C# equivalent on tf-movie or v4-play4 (0 grep hits for each name or concept).

| valve | page file:line | what it does | game |
|---|---|---|---|
| GAIT58 | 58k_cine.js:3502 (gait58 :3515) | The page's simulated lead-in walk plants its feet at real speed (no foot slide) | N/A: the game's lead-in is real gameplay (FighterPose.cs:733). Kit walks use KC.Gait1 (KillCineKC.cs:786) |
| TRIGSETTLE58 | 58k_cine.js:1610, :3162-3185 | In the trigger freeze, live bodies ease from the lead pose to the frame-0 pose over 0.24 s; feet step instead of sliding | **NEEDED**: KillCineFlow freeze + KillCineActor (today only leadDx / leadDy melt: KillCineActor.cs:307, KillCineFlow.cs:97) |
| RESUMESETTLE58 | 58k_cine.js:3188 | When the freeze ends, bodies ease from the frozen pose into the kill over 0.2 s (no 28-44 u hand jumps) | **NEEDED**, same place |
| PULLSIDE58 | 58k_arrive.js:1155-1157 | Arm IK keeps last frame's elbow side | **NEEDED**: KillCineArrive.ArmIK (KillCineArrive.cs:337) |
| GRIPSIDE58 | 58k_cine.js:905 | s.grip's elbow keeps the side it started on | **NEEDED**: KillCineScene.Grip (KillCineK58.cs:152, the side pick ~:174) |
| CASTELBOW58 | 58k_cine.js:3208, :3212 | Zone-cast keys force natural elbows; fast keys blend across the gap | **NEEDED** with the zone cast (engine queue E8, not ported) |
| CASTBACK58 | 58k_cine.js:3221 | A cast that never poses the back arm keeps the pre-cast arm | **NEEDED** with E8 |
| DIEPOSE58 | 58k_cine.js:309-325 | The ragdoll starts from the death pose: limits from the pose, no velocity from a stale Jp, sunk hip lifted | **NEEDED**: KillCineActor.Die (KillCineActor.cs:394; :403 velocity, :433 rig mins) |
| SHORTANG58 | 58k_cine.js:329-335 | Pose mixes and the weapon-angle tween take the short way round ±180° | **NEEDED**: FinisherPoses.Mix (FinisherPoses.cs:25) + KillCineActor wAng tween (:206-214) |
| LIMBOFF58 | 58k_style.js:364 / :389 | Stump spray from 8 px down the bone in pulses; the cap faces out; the limb flies low and back | **NEEDED**, and the whole of 58k_style.js is unported (note a) |
| WLOAD58 | 58k_cine.js:342-362 | The RPG tube draws empty between shot and reload (K58.wLoaded) | **NEEDED**: the film weapon draw (KillCineDraw) + RPG kit hooks |
| BACKPACK58 | 58k_w_rpg_kit.js:375-376 | Spare rockets low along the spine | Lane helper; comes with the RPG kit port |
| FLAT58 | 58k_cine.js:365-367 | Dropped guns and props stop spinning and lie flat at 0° / 180° | **NEEDED**: the CineP step (KillCineFxP.cs:11) |
| PYR58 | 58k_cine.js:1721, :2764-2836 | Arena plates load as tiled pyramids | N/A (page loading only) |
| WORDS58 (opt-in) | 58k_cine.js:3704; 58k_w_smg_skins.js:13-21; 58k_w_x6_kit.js:335-343 | No random killing-hit word unless WORDS58 = true; the rng draw is kept | **NEEDED**: the game still shows it (KillCineFlow.cs:240 picks from WORDS :235 55% of the time, draws at :191). Keep the r.Pick and null the word. Kit pops too |
| 58k_cine3 grade crossfade fix | 58k_cine3.js:127 (mixP) | String preset fields are picked, not lerped (the 'soft-lightNaN' bug). A/B on SHOULDER THROW: pixel-identical to the old page (both presets use 'soft-light'), so the game can simply keep 'soft-light' | Needed together with realGrade (absent) |
| EXPLODE58 (A, 29 Oct) | 58k_style.js:124-143 (liveHeads58, hits58, steer58, awayScope58; the Blood.drop + FXList.add hooks), applied in STYLE.pop / headPop / explode / ending (the limb impulses) and the neck fountain after a head pop (`{ away58: body }`) | Every drop, jet, flying piece and cut limb the gore endings launch is steered off every LIVE fighter's head: the ballistic path (0.6 s, the real gravity and drag) is tested against each standing head disc (head x 1.35); a crossing path turns to the nearest clear angle, away from the head first. Count and power are kept, so XT still reads as the full explosion. A head the drop starts inside is not a target. Extended the same night to EVERY blood drop of the scene (hits, bursts, founts, stump spurts) and to every gore / hit-spray fx piece (58k_hitfx.js spray() sets blood58: 1), tested against the live heads of that step (cached per step) | **NEEDED** with the STYLE gore port (KillGore / CineBlood spawn: test + rotate the launch vector, same constants) |
| SOUL58 (A, 29 Oct) | 58k_style.js:474-486 (in STYLE.soulRise) | The soul never crosses a live fighter's head. While its height range nears a standing head, it drifts sideways (the shift eases in over 1.5 head radii of height), far enough that the body, arms and head clear the head disc. If it is boxed in, it fades while it overlaps; the halo glow fades near any head | **NEEDED** with the soul rise port (Gore.cs DrawSoul) |
| CASTINK58 (A, 29 Oct) | 58k_arrive.js installGesture (castInk / castInkOut gestures) | A gesture at partial weight blends in joint-angle space: solve the arm to the full gesture target, then mix the upper-arm and forearm angles with the pose's own by the eased w (short way). w 0 is exactly the pose (elbow included), w 1 exactly the gesture, so there's no elbow flip when a sweep lets go (freight-train 3.49 was 20 u) | **NEEDED** with the castInk port (KillCineArrive gestures) |
| MANGAHEAD58 (A, 29 Oct) | 58k_w_fists.js KD.manga (the trigger's white burst) | The burst clips out every fighter's head disc (x1.35), so it flares behind heads, never over a face | **NEEDED** with the KD manga port |
| EXPLODE58 draw guard (A, 29 Oct) | 58k_style.js, the Blood.drawFront wrapper | A drop isn't drawn while it is over a live head disc (x1.2), so it reads as behind the head. It covers drops from face hits and drops a head moves into | **NEEDED** in the CineBlood draw |
| ELBOW58 (X5, 29 Oct; valve K58.ELBOW58 = false) | 58k_cine2.js run58 (inside PERF58, just before the narrow-turn block) | When a CINE2 performer key changes an elbow side (eF / eB 1 <-> -1), the hand eases out to full reach along its own line from the shoulder around the switch (k 0.25-0.75), so the two IK elbow solutions meet and the elbow never jumps across the arm in one frame | **NEEDED** wherever the CINE2 performer is ported (the film performer's arm IK): straighten the arm through an elbow-side switch |
| KFPOSE58 (E, 29 Oct; valve K58.KFPOSE58 = false) | 58k_cine.js lead (kfPose58, at the killing frame `V.posek(sc.t, kfPose58(KF.pose), 0.035, 'snap')`) | The lead's killing-frame pose picks reel / shock by seed; both raised the victim's front arm with eF -1, solving the elbow into his own head for ~0.2 s through the trigger (a strict face FAILCLASS on every kill whose seed picks them). At this one call site reel becomes F ['sh', 30, -34] eF 1 and shock F ['sh', 36, -26] eF 1 (shape kept, only the front arm off the head); K.PP is untouched, so kills that use reel / shock in their own choreography are unchanged | N/A for the bake (the page pose is baked); a hand-written game lead must use the same two poses |

**The STYLE 3 kit (58k_cine3.js K58.REAL :71).** Call counts across the lanes: realGrade 106, realCam 50, timeRamp 31,
slowmoAudio 31, multicam 16, mocap 0.

| feature | page | game |
|---|---|---|
| timeRamp / speedAt | :92-99 (curve :35) | **tf-movie only**: CineClock.TimeRamp, KillCineClock.cs:57 (line for line) |
| slowmoAudio | :103-107 | PARTIAL: TimePitch (KillCineSound.cs:257) and the clock LPF (:465). Missing: the per-kill treatment (420 Hz LPF incl. music, −8/−10 st, reverb tail, heartbeat bed, hard cut back). The game has no reverb (:15) |
| realGrade + presets, grain, vignette, warmAt, slow crossfade | :115-163 | **NO**. KillCineGrade (CSS-style presets), KillCineGradeComp and KillCineFilmGrade (smg2) don't cover it. Needs a new KillCineRealGrade |
| multicam layouts | :166-218 | PARTIAL: s.Insert (KillCineOverlay.cs:65), s.Panels (:55), KD.Panels. The layout wrapper is missing |
| realCam | :224-233 | Easy: a span on view.cam (KillCineView.cs:186). No method yet |
| mocap / clip / poseAt | :237-257 | NO; about 40 lines (Actor.Dyn + Mix). Clips: _tools/mocap/clips (4 × 83-95 KB JSON). STYLE 3 lanes inline their own segments (58k_w_x1_kit.js:131-188) |
| FINAL APPROACH (K58.APPROACH) | :259-359 | NO: a KillCineFlow pass + a path check |

Notes:
- (a) The biggest central gap is not a valve. **58k_style.js (K58.STYLE)** has no C# port: blank faces, soul rise, round wet
  caps, openChest routed to limbOff, wound routing. 162 lane files reference it and 48 call limbOff, and it overrides the
  E3 faces and E6 openChest the game already ported.
- (b) TRIGSETTLE / RESUMESETTLE / GRIPSIDE / PULLSIDE / DIEPOSE / SHORTANG / FLAT and WORDS58 are each a one-place change.
  Do them before kill 1, or every port inherits the seams and fails parity against the fixed pages.
- (c) Batch 1 is "STYLE 3 live first" (100 STYLE 3 kills in the pool), so realGrade, realCam and timeRamp (and
  slowmoAudio) are prerequisites.

---------------------------------------------------------------------------------------------------------------------------
## 6. Kill-ending flow (film's last frame → ROUND OVER)

Traced on v4-play4 (tf-movie differences are marked). t = 0 is the film's last frame (`real >= clk.Film`).

| t (s) | step | file:line |
|---|---|---|
| kill moment (in film) | `Finisher.OnKilled` sets `w.lastExec`: no K.O.! title, and no EXECUTION! for films (`!KillCine.Is(kill)`) | Finisher.cs:885-891 |
| kill | `FinisherKilled`: `kill_bass_hit`, then `crowd_cheer` at +1.1 s (`Tune.KillCamTitleDelay`). **Not gated on a film** | FinisherFlow.cs:302-306, Tune.cs:197 |
| kill (next step) | `SettleRoundEnd → EndRound`: Match enters **Ending mid-film**, with the KO slow-mo (0.12×, hold 2.6 s, out 1.0 s), `kill_sting`, `killcam_drone`, `slowmo_in`, punch, `audio.Duck(0.4)`, `cam.Punch`. **Not gated** | Match.cs:591, 673, 687-697; Tune.cs:181-183 |
| kill + 2.6 | `slowmo_out` (it can land mid-film) | Match.cs:445 |
| whole film | KILL CAM stamp and corner tag suppressed for round-ending films; game kill-cam comic fx off | KillCineFlow.cs:424; HudKillCamFx.cs:99 |
| whole film | The page's `s.Card` / `card58` only sets `KillSysRecap.CardDue` (nothing drawn), **but the `ov.card` sound still plays** | KillCineScene.cs:763-764; KillCineFin.cs:88-91; KillSysRecap.cs:25 |
| trigger → kill | HudFinisher's 0.2 purple dim over the film (tf-movie: `HudFinisher.FilmDim = false`, ee82bbdb) | HudFinisher.cs:348 |
| film 0.3 → fX−0.15 | SKIP pill (in 0.2 s, out over the last 0.15 s) | HudKillSysSkip.cs:35 |
| **0.00** | `FlowExit()`: bars out, gold set-pieces un-draw, post back over 0.45 s; `KillSysRecap.Watch` sets T0 | KillCineScene.cs:454, 382; KillSysRecap.cs:36, 44-53 |
| ~0.5 | `roundover` sting inside the exit | KillCineSound.cs:783 |
| **0.56** | Recap card slams in (`RC0 = 0.56`; 0.26 s back-ease from 1.9×, victim line +0.06): kill name, weapon pill, rarity, KILLER > VICTIM, beside the winner (recapPick58) | KillSysRecap.cs:15; HudKillRecap.cs:71-83 |
| **0.70** | Exit ends (`exitDur` 0.7) → `End()`; `w.exec = null` | KillCineFlow.cs:93-95; KillCineScene.cs:387, 459-463; KillCineFin.cs:59-71 |
| ≥ 0.70 | **Gloat**: only after the pick window closes (`CloseAt = t0 + clamp(animEnd − t0, WinMin 3, WinMax 5)`), with `Taunts.CanStart` and the body found (`BodyWait` 1.0 s, gives up at 2.5 / 3.5 s). The taunt **walks to the body**. tf-movie 5a205d2d hides GLOAT during the film and opens the pick with the recap, so a gloat plays at ≈ **3.6 s or later** | GloatKill.cs:26, 132-164; tf-movie HudGloatKill.cs:90-93 |
| 2.05 → 2.40 | Recap fades (`Out` 0.35); `KillSysRecap.HoldsEnding` until `RO = 2.4` | KillSysRecap.cs:15, 67-71 |
| max(2.4, kill + 3.2, gloat end ≤ 5) | Ending → Result needs `stateT > camEnd` (2.6 + 1.0 × 0.6 = 3.2 s **after the in-film kill**), `!KillCam.HoldsEnding` (also KillCard.Holding, KillCard.cs:30), `!GloatKill.HoldsEnding` (HoldMax 5 s), `!KillSysRecap.HoldsEnding` → `ShowRoundResult` (ROUND OVER) | Match.cs:443-460; GloatKill.cs:187-203 |
| + 2.6 (or a tap after 0.6) | NewRound | Match.cs:456, 477 |
| SKIP | `Skip()`: the victim dies, `CardNow`; the recap fires at once; End 0.05 s later | KillCineScene.cs:702-709 |

**Today:** recap at 0.56 s; gloat at 0.7 s at best (≈ 3.6 s+ on tf-movie). ROUND OVER ≈ 2.4 s after the film without a
gloat, up to ≈ 5.7 s with one (≈ 8 s+ on tf-movie).

**Already matches 56_decisions / brief §4:** no KILL CAM stamp during the film; the page card deferred to ONE recap card
beside the winner; no K.O.! / EXECUTION! for films; the gloat as the edge column with the result waiting for it (≤ 5 s);
the SKIP pill.

**Must change** (targets: recap + gloat ≤ 0.3 s after the last motion; ROUND OVER ≤ 1.0 s after the gloat or the film):
1. `RC0` 0.56 → ≤ 0.3, or fire at a per-def "last motion" time instead of fX (KillSysRecap.cs:15). Design the card with the
   0.7 s exit (it now slams while the bars are still leaving).
2. `RO` 2.4 → stop holding the result at ≈ 1.0 s; let the card stay visible into Result (HudKillRecap.cs:65 reads only Age).
3. `camEnd` (3.2 s from the in-film kill) → skip it when a film played (`world.lastExec?.cine`), or count from the film's
   end (Match.cs:443-446).
4. Gloat:
   - Queue the pick during the film and play it at End with no `WinMin` (GloatKill.cs:26, :160).
   - Gloat in place, or face the body without the walk / `BodyWait`.
   - Let `Taunts.CanStart` pass at scene End.
   - **Needs a coordinator ruling.** Picking during the film (R08: GLOAT on the first frame, killer only) clashes with
     tf-movie's 28 Oct "no HUD during a film" (5a205d2d).
5. Gate the mid-film side effects on a film (`KillCineScene.FilmRunning`): Match.cs:687-697 (slow-mo, stings, duck, punch),
   :445 (`slowmo_out`), FinisherFlow.cs:305-306 (bass hit, cheer). The film's own sound design owns that moment, and the
   KO slow-mo must not outlive a late-kill film into the hand-off.
6. Drop the `ov.card` sound when the card is deferred (KillCineScene.cs:764).
7. Add the "KILL CAM REPLAY" header to the recap (56_decisions); HudKillRecap draws none.
8. Merge the `FilmDim` valve (tf-movie ee82bbdb).

**Glitch sources:**
- Game stings over the film (item 5).
- The card sound with no card.
- Leftover 0.12× slow-mo after the hand-off.
- Up to three stacked holds (2.4 / 3.2 / 5 s), plus 2.6 s on the result plate.
- The purple dim over every film until the kill.
- `FinSkipLocal`'s EXECUTION! on a Wi-Fi client HOLD-skip (HudFinisher.cs:198) needs a check that it can't fire for films.
- Pages draw their own exit card (KillCineFlow.cs:35) and kill-cam captions. Each port must strip those by hand; only
  story text stays (brief §9, 14:25 / 15:05). That is a per-port LOOK item.

---------------------------------------------------------------------------------------------------------------------------
## 7. Film loading, memory and build size

**How it loads now:**
- **Code, compiled in.** Every kill is IL in Assembly-CSharp. Defs register at `KillCine.EnsureBuilt()` (KillCine.cs:218),
  all of them on the first ask. That costs one KillCineDef, one FinKill, a beats array and the build closure per kill.
  The build lambda only runs when the film plays, or in a probe.
- **Sounds: lazy.** `KillCineSound.Want(id)` uses `Resources.LoadAsync` per kill's files (KillCineSound.cs:~305). The kill
  audio library is 1494 files, 15.2 MB (Resources/Audio/Kill).
- **Bakes and music: Resources.Load at the film's build.** MovieBake.Load is at KillCineMovieData.cs:32 and is unloaded
  after parsing. KillCineMusic.cs:34 loads the cue, and :45 loads its chunks with LoadAsync.
- **Prewarm** (KillSysPerf.cs:8, Warm :62). At every round start, and when a gun changes hands, every film in every
  fighter's rotation gets a **probe build** (`KillCine.SpotOf`) at one film per frame. Measured probes take 2.6-13.8 ms
  each on the Mac (`[KILLSYS] prewarm: t.smg.e-anat-chest measured in …`). They run once per session per film.
- **Generated static tables:** KillSysMapData.cs (351 KB) and KillCatalog.cs (143 KB).
- No Addressables are used.

**Per-film cost now:**
- smg1's 16 kills add 6101 lines / 580 KB of source, and +436 KB to the csc output (a.dll 14,455,296 vs tf-kills 14,019,072).
  That is about 27 KB of IL per kill.
- smg2's 10 kills add about 191 KB, about 19 KB per kill.
- Call it **~23 KB IL and ~380 lines per kill**.
- The baked movie is 475 KB of .bytes plus ~150 KB of bespoke C#.
- The resident film reserve while a film plays is ≤ 24 MB (PORTING_GUIDE §5b). It is shared, not per film.

**Growth for ~812 films (estimate):**
| | now | +812 films |
|---|---|---|
| Assembly-CSharp IL | ~14 MB | **+~19 MB (≈ 2.3×)** |
| iOS IL2CPP generated C++ (Assembly-CSharp; 23 Oct iOS export: 207 files, 268 MB for ~13 MB IL ≈ 20×) | 268 MB | **+~380 MB of C++ to compile**: much longer iOS / Android IL2CPP builds |
| global-metadata.dat (iOS export 11.9 MB; loaded whole at start) | 11.9 MB | **+~15-18 MB resident at startup** |
| native binary (arm64) | — | roughly +40-80 MB of download (estimated, no compiled UnityFramework found to measure) |
| registry RAM (defs + closures) | — | ~1 MB, negligible |
| prewarm at round start | 1 film per gun | 25-56 films per gun × 3-14 ms of probe = 0.1-0.8 s of work per new gun, at one probe a frame (≈ +5-14 ms on those frames on the Mac, 3-4× that on a phone) |
| a bake route (if used broadly) | — | ~100-200 KB per film × 812 ≈ 80-160 MB. Only acceptable if loaded lazily and quantized |

**Does lazy loading matter?**
- **For memory: NO.** The code-only design keeps the per-film RAM in the shared reserves, and sounds already load lazily.
  Lazy-loading the defs would save about 1 MB.
- **For build size and build time: YES.** C# code can't be lazy-loaded. About +19 MB IL turns into about +380 MB of IL2CPP
  C++ and +15-18 MB of metadata, resident from launch. Use IL2CPP "OptimizeSize" codegen for release builds. Keep big
  numeric data (mocap rows, baked tracks, long keyframe tables) out of C# literals and in `.bytes` loaded per film.
- **For round-start hitch: YES.** Replace the per-film probe build in prewarm with an offline-measured table (set bounds and
  film length per kill, generated at port time and checked by the lab). Prewarm then becomes a lookup.

---------------------------------------------------------------------------------------------------------------------------
## 8. Film selection

**What the code does:**
- **Round-ending films:** `KillSysFilm.Try` (KillSysFilm.cs:168) checks, in order: the killer alive; hand-held beam / fire;
  safe ground; the dev chance `Tune.FinisherChance` (:229); then `KillCineRotation.PickFilm` (KillCineRotation.cs:~105).
  - `FilmsFor` is owned + switched-on (`FinisherLoadout.Equipped`) films of the gun in hand plus the equipped skin's own
    film, sorted **most epic first** (MYTHIC > … > COMMON, then the page's order).
  - Pick order:
    1. A multi-kill film jumps the queue.
    2. **Owed** films come back first, wherever they fit.
    3. The most epic film not yet shown this cycle; a film that doesn't fit is added to owed.
  - When every film has been shown, the cycle restarts. The local player's state is saved per gun (Meta section
    "tfkills_rot"); bots and remote players keep theirs for the session.
  - The fit test: `KillSysFilm.TagsFit` (range / air / hazard / Deep / victims), then `SpotFits` (the spot solver plus
    stage58).
  - There are **no weights and no randomness**; it is a deterministic cycle.
- **COMMON ~1/15 (THE OFFERED HAND):** `KillCineMovie.Roll` (KillCineMovie.cs:103; `Chance = 1f/15f` at :85). It rolls
  `R.V` on every round-ending K.O. with any gun, on the host, before the gun's rotation. `-movieAlways` / KS.MovieAlways
  force it. It never plays on a double K.O., a hazard death, in The Deep, or with films off.
- **Mid-round:** "1 in 4 qualifying kills, max 1 per player per round" (58k_BRIEF §4). The skin's mid film is
  `KillCine.SkinDef`.

**What is missing for 800+ films:**
1. **Most-epic-first means a new player sees the same MYTHICs every cycle,** and with 25-56 films per gun a full cycle
   takes dozens of rounds. Add a "new first" rule: films the player has never seen go ahead, as a seen-set per player
   saved in Meta, since the rotation's `shown` list resets every cycle. Optionally add rarity weights inside a cycle.
   The design (brief §4) only says "most epic first, repeating", so either change needs the user's call.
2. **No repeat across a cycle boundary:** after a reset, the first pick must not equal the last one shown. Today it can
   when an owed film or a single fitting film comes back. This holds per gun, and across guns for the COMMON films.
3. **Repeat guard for COMMON:** the 1/15 roll has no cooldown, so it can hit two rounds in a row. Add "not in the last N
   round-ending films".
4. **Only PASSED films may be listed.** Today `KillCine.Of` lists every registered `ready` def. A ported-but-unpassed kill
   would join the rotation the moment its partial method compiles. Add a PASSED allowlist (generated from
   port_batches.json) checked in `RegisterAll`, with a lab override (`KillCine.Unready`).
5. **Save growth:** `shown` / `owed` hold ids per gun, about 56 × ~25 bytes per gun. That is fine.
6. **Mixed versions online:** see §11.

---------------------------------------------------------------------------------------------------------------------------
## 9. The music duck

- **Where it lives:** Core/KillCineSound.cs, the same on v4 and v4-play4 (tf-movie line numbers in brackets).
  - `SndBegin` emits `"killcam"` at scene 0 of every round-ending film (:781 [:791]).
  - `Emit` handles it: `case "killcam": DuckAt(0.22f)` (:529 [:539]).
  - `SndMix` turns that into `KillCineAudio.musicG = 0.22` and `mlp = 380 + 3200 × 0.22 ≈ 1084 Hz` (:738-742 [:748-752]).
  - Every Music-bus voice gets `bus *= musicG` and that cutoff (:456 [:459-462]).
  - The duck clears only at `SndEnd → ResetMix`, so it lasts the **whole film**.
- It only affects **film** beds (`s.Music(...)`: the amb / mood beds, e.g. KillCineSoundSmg.cs:18-73, and any score). The
  gameplay music deck has its own fade (Services.cs:985-995: to 0 under the kill cam, LPF by slow-mo; Tune.cs:225-234).
- **tf-movie ee82bbdb:**
  - A Music voice whose bed key starts with `"score"` skips `musicG` and gets a 22 kHz cutoff.
  - Bed voice keys are cached (`SBed.vk`).
  - A `musiconly` lab mute is added.
  - It adds KillCineMusic.cs, which plays the MUSIC KIT on its own AudioSources and never reads `musicG` / `mlp`
    (KillCineMusic.cs:17, :152). It is off by default (`-movieKit` / `mvdbg=kit`).
  - It adds the `HudFinisher.FilmDim` valve.
- **Do ports need a change?** No engine change once ee82bbdb is in the base. Porters' rule: a film's score goes through
  KillCineMusic (the per-film cue sheets, the user 28 Oct) or uses an `s.Music(..., key "score…")`; never a plain bed
  (which plays at 22 %, muffled), and never a hand-rolled bypass. Ambience beds staying ducked is faithful to the page.

---------------------------------------------------------------------------------------------------------------------------
## 10. Real cosmetics and real arenas in films

Both are confirmed in code:
- **Fighters** (KillCineDraw.cs:282 `DrawBody`): `var dress = a.f != null ? Cosmetics.DressOf(a.f)` (:288). A live real
  fighter "the game draws it (render hook …)" (:291): the real gameplay renderer with its real cosmetics, with the film's
  look applied through LiveTint / LiveAlpha / LiveMark / LiveCut (B15, 484c2040). Hats and faces follow the dress:
  KillCineFaces.cs:26 (with a face item only the mouth is drawn), KillCineKC.cs:21 (a game body's hat is the fighter's
  cosmetic; `hatOff` hides it, ffbe2beb), KillCineScene.cs:603-604 (visor-as-hat). The cast loadouts (KillCineParity.cs:33)
  are only for the parity lab (`KillCineScene.LabExact`). In films, names come from `s.NameOf` (PORTING_GUIDE §6c).
- **Arena** (KillCinePage.cs:5-18, KillCineScene.cs:6-9): "the ground is the real arena laid into page coordinates
  (CinePage)". The real level's boxes and planks around the spot go through CineFrame, the real art is drawn, and the
  film's corpses stay in the real arena afterwards. Realms cover the arena and LeaveRealm drops the corpse on the real
  floor (KillCineFlow.cs:351-374).
- **Still open (PORT_GATE §1c):** the page's arena plates are stale exports. The plan is to re-export the 18 arenas live
  (MapExportLab with post and look art on) and calibrate ONE film-grade table for page and game. The game-side grade quad
  is in d9c16c2c (KillCineFilmGrade.cs, smg2, now in v4-play4). The re-export and the calibration were NOT started
  (TF_PORT_SMG2 open item 1). The movie draws its own realm over everything and keeps the pair's dress and weapons in its
  intro and outro (TF_MOVIE_PORT design).

---------------------------------------------------------------------------------------------------------------------------
## 11. Concurrency: ~8 porters + the online agent, one Unity on the Mac, one on the VM

**Files every porter would touch, and how to keep them apart:**
| shared file | today | fix |
|---|---|---|
| `KillCine<Weapon>.cs` manifests (fixed `Place(w, n)` + partial declarations) | only KillCineSmg.cs, 42 places, missing the 5 new SMG keys | **Generate all 33 manifests once** from 58k_library.js (1141 kills; max per weapon is minigun with 56 of the 64 places). Places are fixed forever and partials are declared for every kill, so no porter edits a manifest |
| KillCinePages.cs `RegisterPages()` | Smg + Common | Generated once with all 33 `Register<W>()` calls |
| KillCineSoundTables `RegisterAll` + KillCineSound<W>.cs | SMG only | Run `port_snd.py` for all 33 weapons once, centrally |
| KillCatalog.cs / KillSysMapData.cs | generated, stale | Regenerated only by the integrator |
| shared page kits (KC / KD / KE / RIF / Slice, the weapon cine kits 58k_w_<w>_cine*.js, 58k_cine3, 58k_style) | integrator-owned (PORTING_GUIDE §6a) | Stays so. A porter asks via REQUESTS.md and leaves `// PORT-PENDING` |
| lane files `KillCine<W><Lane>.cs` + `.meta` | per porter | **One file per kill** (`KillCine<W>_<Key>.cs`) instead of per lane. Parallel porters on one weapon can't collide, a re-port touches one file, and a git conflict is impossible by construction |
| parity inputs `tools/kills/parity_in/<w>_<id>.json` | per kill | OK |
| the PASSED allowlist | — | One generated file written by the integrator at merge |
| KillCineLab / KillSysLab / KillSysFilm / HUD | engine and system | Never porter-owned. All 4 conflicts in §1 sit here |

**Recommended layout:**
1. **One integration branch per batch, `port-bN`, cut from v4.** Each porter has its own sparse worktree and branch,
   `port-bN-<porter>`, off `port-bN`, with Library symlinked for csc only and no Unity. Each porter owns a set of weapons
   (by lane), so no two porters write the same weapon's kills.
2. **A build train instead of per-porter builds.** Every ~45-60 min the integrator merges each porter's ready commits into
   `port-bN` (they don't overlap: one file per kill). It runs csc and does ONE build per site: the Mac MacTest-B and the VM
   lane `port-bN`. Then every porter's labs run against that same build, from the player slots. That is 1 build per train
   instead of 8.
3. **Lab capacity is the bottleneck, not porters.** One gate pass is about 20 min of player time. With 3 passes per kill,
   that is about 1 h per kill. 812 kills is about 800 player-hours. Mac 2 + VM 2 slots give about 200 h, about 8-9 days
   if nothing else runs. Options:
   - Cut it: run parity on fewer frames until the last pass, and run the 18 arenas once at the final pass (6 frames, g5 +
     g0 in one run).
   - Add capacity: a second VM (a D8as_v5 runs unity 2 / player 3 per TF_VM), or bump the current one.
   - The Mac player stays the perf reference and runs only perf plus the final review captures.
4. **The VM** (`tools/vm/vmctl.sh lane up|build|lab|stills`, TF_VM.md) runs parity, arena and edge labs on llvmpipe. Perf
   tables and the coordinator's final sheets come from the Mac.
5. **The online agent** works in friends* on protocol 141. Rules for living alongside it:
   - It doesn't touch kill files (its only overlap is Match.cs), and none of its branches conflicts with the kill branches.
   - friends-play already contains tf-killsys + smg1 + smg2 (via v4-play4). Rebasing the tester build onto
     v4-play4 + tf-movie therefore hits the same 4 files.
   - **Coordinate the protocol.** The kill system's HMulti = 8 and MFilmSkip = 19 are protocol 40 (MFilmSkip is already on
     v4). Online uses 141 (= 100 + the next v4 number) and renumbers at its v4 merge.
   - Each batch adds wire ids (append-only, same format), but two clients on different batches would agree on the protocol
     and disagree on which films exist. `ByWire` then returns null on the client. Add a **film-catalog hash to the hello**,
     or bump the protocol at each batch merge, and agree on which with the online agent.
   - The Unity lock is shared with online, so their builds queue on the same `locked.sh unity`. Give build trains fixed
     slots (for example on the hour) and leave the half-hours to online and v4 build_test.
6. **Don't use `v4-play*` or `friends-play` as merge targets.** They are play builds. v4 is the only target, by exact hash.

---------------------------------------------------------------------------------------------------------------------------
## RECOMMENDED FOUNDATION WORK BEFORE BATCH 1 (in order)

Estimates are agent time, not counting waits for the lock. F1-F3 are the integrator's. F4, F11 and F13 go to a tools agent.
F5, F6 and F7 go to two engine agents in parallel. F8, F9 and F10 go to a system agent.

1. **F1 Port base branch** (1-2 h).
   - `git branch port-base e35e2350` (v4-play4), then `git merge ee82bbdb` (tf-movie).
   - Resolve the 6 hunks: KillCineLab.cs:202 (keep both locals); KillSysFilm.cs:208 / :225 (carry tf-movie's
     `legacyOnly ? Legacy(out decided)` into play4's `NoFilmCo` coroutine flow); KillSysLab.cs:61 (the union of parts plus
     "movie"); HudKillSysSkip.cs:16 / :89 (play4's page-scaled pill inside tf-movie's PressFx holder).
   - Then `zsh tools/csc_check.sh` → `== errors: 0`.
2. **F2 PASSED allowlist** (2 h).
   - Generated `Core/KillCinePassed.cs` (from port_batches.json and the movie), read in KillCine.RegisterAll / `Of` / `SkinDef`.
   - Unpassed defs register, so wire ids stay stable, but they never enter a rotation. Labs use `KillCine.Unready`.
   - This switches off the 26 stale SMG ports and taped; offered-hand stays on.
3. **F3 Gate port-base and merge it into v4** (½ day, mostly lab and build time).
   - build_test, `tools/killsys.sh ks_base all`, `killcinelab:units`, play smoke on smg.skin-taped (Unready) and
     common.offered-hand on 18 arenas, regression (playall / the regress labs).
   - A Mac test build for the user.
   - Then merge into v4 by hash. **From here every batch branches off v4.**
4. **F4 Scaffolding generators** (½-1 day). Files: new tools/kills/gen_manifests.py, KillCine<W>.cs × 33, KillCinePages.cs,
   KillCineSound<W>.cs × 32, KillCatalog.cs, KillSysMapData.cs.
   - Generate all 33 weapon manifests from 58k_library.js: fixed append-only places; SMG keeps 0-41 and its 5 new keys take
     42+; partials declared for every kill. Also RegisterPages.
   - A one-file-per-kill template (`KillCine<W>_<Key>.cs` plus `.meta`).
   - `port_snd.py` for the 32 missing weapons.
   - Regenerate KillCatalog (1141 rows) and KillSysMapData.
5. **F5 The 28 Oct central valves in C#** (1.5-2 days).
   - TRIGSETTLE58 + RESUMESETTLE58 (KillCineFlow freeze, KillCineActor); GRIPSIDE58 (KillCineK58.cs:152-174);
     PULLSIDE58 (KillCineArrive.cs:337); DIEPOSE58 (KillCineActor.cs:394-433); SHORTANG58 (FinisherPoses.cs:25,
     KillCineActor.cs:206-214); FLAT58 (KillCineFxP.cs); WLOAD58 (the film weapon draw); WORDS58 (KillCineFlow.cs:240:
     keep the Pick, null the word).
   - Each one is checked by a units probe and a parity re-run on a test kill.
6. **F6 Port 58k_style.js as KillCineStyle** (2-3 days). Blank faces, soul rise, round wet caps, openChest → limbOff with
   LIMBOFF58, wound routing. Reconcile it with the E3 faces and E6 openChest. 162 lane files depend on it.
7. **F7 The STYLE 3 kit** (3-4 days; batch 1 starts with STYLE 3 kills).
   - TimeRamp: already in, from tf-movie.
   - realCam: a span on KillCineView.
   - realGrade + presets + the cine3 mixP string fix: new KillCineRealGrade post pass, sharing the film-grade table with F12.
   - slowmoAudio: the per-kill LPF 420 Hz, −8 / −10 st, a reverb tail, the heartbeat bed and the hard cut, in KillCineSound.
   - The multicam layout wrapper: KillCineOverlay Insert / Panels.
   - mocap / clip: rows as `.bytes`, loaded per film.
   - The FINAL APPROACH pass.
8. **F8 Kill-ending flow to 56_decisions** (1 day + labs; needs the coordinator's ruling on picking the gloat during the
   film). Files: KillSysRecap.cs, HudKillRecap.cs, Match.cs:443-460 / 687-697, FinisherFlow.cs:305, GloatKill.cs,
   HudGloatKill.cs, KillCineScene.cs:764.
   - Recap ≤ 0.3 s; the hold ≤ 1.0 s; no camEnd for films.
   - Gate the stings and the slow-mo on films; no ov.card sound.
   - The gloat starts at End, in place; the REPLAY header.
   - Add a lab assert on the three timings.
9. **F9 Selection for 800+** (1 day). Files: KillCineRotation.cs, KillCineMovie.cs:103, the hello in NetProto (with the
   online agent).
   - A new-first seen-set; no repeat across a cycle reset; a COMMON cooldown (not in the last N films); the PASSED filter.
   - A film-catalog hash in the hello, or a protocol bump per batch, agreed with the online agent.
   - Any change to "most epic first" goes to the user as a question.
10. **F10 Prewarm without probe builds** (½-1 day). Per-kill set bounds and film length baked into a generated table at port
    time (the lab checks it against `KillCine.SpotOf`). KillSysPerf.Warm (:62) becomes a lookup.
11. **F11 One gate command in the repo** (1 day).
    - `tools/kills/gate.sh <w>.<id> --quick|--final`: the page capture, parity + rngtrace at 6 levels, perf q=all +
      `-perfslow 6`, the 18-arena strip (g5 + g0; `--final` only), mid / zone, edges, region_ssim (fix the cosm.jsonl
      crash), the PORT_GATE §1b table.
    - It writes `sheet/index.html` + a JSON row and ticks PORT_BATCHES (`port_batches_gen.py --tick`).
    - Promote the Scratch scripts: chain_gate2, collect3, gate_table2, rngdiff, parity_page_trace, qrng_cmp, perf_tab,
      region_ssim.
12. **F12 Arena plates + ONE film-grade table** (1 day + page sign-off; PORT_GATE §1c). MapExportLab renders live (post and
    look art on), the 18 arenas are re-exported on the VM, one per-arena grade table (alpha cap 0.50) is shared by
    58k_cine.js and KillCineFilmGrade, and the user signs off 3 page finals.
13. **F13 Build train + capacity** (1 day).
    - A train script merges the porter branches into `port-bN`, runs csc, then one Mac build plus one VM lane build, and
      announces the build id. A lab queue spreads gate runs over the 4 player slots.
    - Fix a build window for online and v4.
    - Add or upsize a VM (player-hours are the bottleneck: ~800 h for 812 kills at 3 passes).
    - IL2CPP OptimizeSize for release.
    - Measure the iOS size after batch 1.
14. **F14 Pilot two kills on the new pipeline** (1 day): one STYLE 3 kill and one classic kill from batch 1, plus a re-gate
    of offered-hand on v4. Measure the hours and passes per kill, then size the batch teams and slots from real numbers.
    The 26 SMG ports are re-ported inside their own batches (3, 4, 6, 7 …), from the current page and starting from the
    existing C#.

---------------------------------------------------------------------------------------------------------------------------
## DECISION 29 Oct ~04:40 (coordinator): PORT BY BAKE, not by hand
BAKE-SPIKE (port-bake 82c50f85; /Volumes/Workspace/Scratch/bakespike/STATE.md) proved a generic bake path:
- **The bake:** the page runs headless at each gore level and records joints, every world draw (geometry symbol table), the render's camera matrix, multicam + inset cameras, sound, the clock (→ TimeRamp) and overlays, with blood as spawn events.
- **The player:** one generic C# player (KillCineBaked*) replays it on the real arena with real cosmetics.
- **Parity:** exact by construction.
- **Proof:**
  - SHOULDER THROW: 18/18 arenas, pose-for-pose at g5 and g0;
  - LAST STAND: 14/18, with clean refusals on the 4 small arenas.
- **Hand-port record:** 26 SMG ports passed 0 of 11 gate passes; ~1000+ h of code plus ~800 player-hours projected. Bake is ~15-30 agent minutes per kill once the foundation is done.

Open bake foundation (~6-9 agent-days), the gate items before batch 1:
1. **Player perf (BAKE-PERF):** tessellate each symbol once and draw instances by matrix; clip with masks; an allocation-free decoder; warm the grade/multicam at round start; fx particles as events. Both kills fail p95 today.
2. **Recorder (BAKE-LEAD = the spike agent):** fx particles as spawn events; screen-space insets/looks (LAST STAND's backlit look + rifle insets); sprite caches → geometry; gore pieces as semantic symbols; deterministic capture (the splat-bake wall clock); a streaming packer.
3. **Storage (BAKE-STORE):** 30 Hz + interpolation, an LZMA container, a generated meta table (F10), lazy bundles. Target ≤ ~150 KB per kill.
4. **Flow and staging (SYSTEM after F8-F10):** the mid-kill flow (zone, bars, K.O. frame); staging variants per arena class; mirrored in-world text.
5. **Blood/gore look (STYLE-PORT):** the game's blood painters calibrated to the page; EXPLODE58/SOUL58 in the game sim.

F5/F6 (hand-porting the valves and 58k_style.js) are NOT needed for the bake path: page fixes come for free. STYLE-PORT keeps only the game-side art (faces, blood/gore painters, flash, flat props, shadow).
