Skip to content

Kill feed: an even layout where every weapon icon has the same height and nothing is cut off (#1022) - #1023

Merged
ccoventry merged 11 commits into
devfrom
feat/killfeed-even-layout-1022
Oct 25, 2026
Merged

ccoventry merged 11 commits into
devfrom
feat/killfeed-even-layout-1022

Conversation

@ccoventry

Copy link
Copy Markdown
Owner

Release note: New: ds_hud_deathmsg layout even draws every kill-feed weapon in the same height box, full magazines and bipods shown; the default stays the game's own layout.

Closes #1022
Supersedes #995 (cutting the MP44's magazine): the user picked R2 instead. This branch is built on #995's, which changed no output, so #995 can be closed.

What it does

  • ds_hud_deathmsg layout even: every kill-feed icon in a box 26 stock rows tall (times scale), every long gun's barrel top on row 8, nothing cut. So every line steps the same (65 px at scale 2.5, 26 at scale 1) and the names are centred on it.
    • Long guns are as wide as Valve's drawing. A gun that doesn't fit shrinks with its barrel kept on the line (MP40 and MP44 71%), and each box is as wide as the gun ends up.
    • Pistols and launchers keep today's size, moved onto the line. The launchers and the Bren "move".
    • Knives, the spade and grenades keep their size, centred.
  • ds_hud_deathmsg layout default is the default: the game's own boxes and spacing with today's HD icons, exactly as now.
  • It switches live, like scale and font, and works at scale 1 with no font.
  • With HD off, the game's layout shows whatever layout says. The bare ds_hud_deathmsg status says why even isn't drawing.

How

  • The atlas. The engine samples the texture from the same rect the layout reads, and Valve's sheets are packed too tight for 26-row boxes. So killfeed_icons.py --even renders the icons into an atlas of our own:

    • sprites\dodstudio_killfeed.spr and .txt (name frame x y w h), in dod_addon;
    • the HD frames dodstudio_killfeed_<n>_<hash>.tga, in the style's sprites folder.

    sprites_hd.py now runs it after the sheets' icons. It took 36 s for POST's 42 icons.

  • The hook (deathmsg/even.rs, new):

    • checks both files (bounds, frame count, format) before the engine sees the sprite, then loads it with SPR_Load once per level load;
    • gives each sprite index its box by name, from gHUD.m_rgszSpriteNames (gHUD+0x190).
  • scale.rs:

    • the line-start stub also stores the line's sprite index;
    • the rect copy holds the boxes, at scale 1 too;
    • the icon draw draws the atlas box through hud_sprites::draw_holes_instead: atlas set with the game's colour, the engine's SPR_DrawHoles, blended like a soft sheet, then the game's sprite set back. ds_map_hide_asset still hides a hidden icon.
  • Both engine builds. Nothing in hw.dll is touched, and the sprite calls go through gEngfuncs. client.dll is byte-identical on PRE and POST. The one new client.dll address (the names field) is checked by tools/verify_killfeed_scale_offsets.py (ALL OK, both installs).

  • Hot path. Per frame: atomics only. The files are read once per level load, and the per-index box table is rebuilt only when the names array or the table changes. Everything runs on the engine thread.

Decisions you may want to know

  • The .spr's own pixels are grey placeholder boxes, made from the boxes alone. So the sprite, and its HD key, are the same for every style. The even layout only draws when every frame of the atlas went up with its HD file and ds_hd_enabled is 1; otherwise it keeps the game's layout.
  • ds_hd_enabled 0 mid-session gives the game's layout from the next frame. The engine keeps an uploaded sprite until the game exits, so Valve's sheets keep whatever icons they loaded with until a restart: stock icons need a restart with HD off.
  • The seven icons with no model are in the atlas too, so their lines step the same:
  • layout even counts as "our layout", so the 1/160 right margin and centred names apply at scale 1 with no font too.
  • A rebuilt atlas needs a game restart. The engine keeps the first upload by name.
  • Test DLL is unsigned. There is no LocalRustDev certificate in this user's store, and every other local\test-* DLL is unsigned too.

Tests done

  • cargo test -p goldsrc-hooks --target i686-pc-windows-msvc: 800 passed. New tests cover:
    • the table and .spr parsing, with bad files refused;
    • index-to-box by name, including 24-byte names with no terminator;
    • every line stepping the same;
    • the stub bytes, line-entry matching and the copy rects;
    • the atlas soft marks.
  • Clippy with 1.98.1 on both targets (i686 and host): clean. cargo fmt --all --check: clean.
  • python -m unittest test_killfeed_icons: 68 OK (7 new: rules per class, packing, sprite round trip, placeholder, table, fit).
  • Default output unchanged: all 35 of the table's own renders (what build() paints) are byte-identical to the pre-branch ones.
  • tools/verify_killfeed_scale_offsets.py: ALL OK, including the new names check.
  • Offline proof: the atlas was built from POST's models into local\test-1022\hd-output\. The review sheets in local\review-images\1022\build\ lay the feed out from those files:
    • atlas.png;
    • feeds_scale2.5.png: default steps 33–85 px, even steps 65 px on every line;
    • feeds_scale1.png.
  • Not tested in game.

In-game test (PRE and POST)

DLL: local\test-1022\dodstudio_goldsrc_hooks.dll. Pick 1022 in Configuration → Paths → GoldSrc Hooks DLL.

Install the atlas (the same files on both installs; on PRE the icons then show POST's skins). Copy:

  • local\test-1022\hd-output\dod_addon\sprites\dodstudio_killfeed.spr and dodstudio_killfeed.txt → <install>\dod_addon\sprites\
  • local\test-1022\hd-output\dod_addon\dodstudio_hd\sprites\ultrasharp\dodstudio_killfeed_0_b1149160.tga and dodstudio_killfeed_1_b5d83aa0.tga → <install>\dod_addon\dodstudio_hd\sprites\ultrasharp\

Don't copy killfeed_icons.json from there: it would replace the install's record.

Or build straight into an install with the tool (writes the same files and the record):
set HD_GAME=<install> then python goldsrc-hooks\tools\hd\killfeed_icons.py "<install>\dod_addon\dodstudio_hd\sprites\ultrasharp" --even

Launch from Studio (it passes -addons), with ds_hd_style ultrasharp. Steps for POST, then the same on PRE:

  1. viewdemo solifotw-greetz1, then ds_demo_seek_to 466. hud_deathnotice_time 60 keeps the notices up.
  2. Fill the feed with a mix of icons:
    ds_hud_deathmsg max 8; ds_hud_deathmsg fake 1 2 mp44; ds_hud_deathmsg fake 2 1 30cal; ds_hud_deathmsg fake 1 2 colt; ds_hud_deathmsg fake 2 1 bren; ds_hud_deathmsg fake 1 2 grenade; ds_hud_deathmsg fake 2 1 garandbutt
  3. ds_hud_deathmsg layout even. The reply should say layout = even: every kill-feed icon in a box of one height (42 icons).
  4. ds_hud_deathmsg scale 2.5. Expect:
    • every line the same height;
    • the barrels on one line;
    • the MP44's and MP40's magazines and the .30 cal's bipod whole;
    • no icon reading the one next to it in the atlas.
  5. ds_hud_deathmsg layout default: today's uneven look, from the next frame.
  6. ds_hud_deathmsg layout even, then ds_hud_deathmsg scale 1: 26 px lines, names centred on each.
  7. ds_hd_enabled 0: the game's layout from the next frame. ds_hud_deathmsg should say layout = even, waiting: HD is off. Then ds_hd_enabled 1: even again.
  8. ds_demo_reload (a level load): still even after it.

Log lines (dodstudio_goldsrc_hooks_<date>.log):

  • deathmsg layout: sprites/dodstudio_killfeed.spr loaded as handle N: 42 box(es) in 2 frame(s)
  • deathmsg layout: 42 of the HUD's N sprites draw from the atlas

If step 3 says waiting: the reply names why. For example, the atlas went up without its HD files: a wrong style folder, or HD was off at the first load, which needs a restart.

🤖 Generated with Claude Code

ccoventry and others added 8 commits October 24, 2026 02:52
Option b for the MP44 beside cut_part's option a: the barrel stays on
its row and the gun shrinks until what hangs below ends inside the rect.
No icon sets it yet; output stays VERSION 13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
render_icon takes an optional Layout: a rect height and width other than
hud.txt's, a shared barrel row, and a size rule (Valve's width, the
table's natural size, or a fixed pixels-per-model-unit scale), with a gun
that doesn't fit shrunk, moved or cut. The table's own placement stays the
reference, and the hand-set places of v_part, skin_art and cut_part are
moved with the gun (remap_table). build() never passes a layout, so the
painted sheets are unchanged; renders without one are byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1022)

ds_hud_deathmsg layout even draws every kill-feed icon in a box 26 stock
rows tall from an atlas of our own, so every line steps the same and full
magazines and bipods show; layout default (the default) is the game's own
boxes and spacing with today's HD icons.

- killfeed_icons.py --even renders R2 (the user's pick on #1022) into
  sprites/dodstudio_killfeed.spr + .txt (dod_addon) and the HD frames
  dodstudio_killfeed_<n>_<hash>.tga. The sprite's own pixels are a
  placeholder from the boxes alone, so every style shares it. build() and
  the sheets are untouched: 35 of 35 table renders byte-identical.
- deathmsg/even.rs loads and bounds-checks both files, maps sprite indices
  to boxes by gHUD.m_rgszSpriteNames (gHUD+0x190), and draws only while
  every atlas frame went up with its HD file and ds_hd_enabled is 1.
- scale.rs: the line-start stub also stores the sprite index, the copy
  holds the boxes (at scale 1 too), and the icon draw draws the box of the
  atlas through hud_sprites::draw_holes_instead.
- verify_killfeed_scale_offsets.py checks the names field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It takes under a minute (36 s for POST's 42 icons), and without it layout
even only works after a hand run of killfeed_icons.py --even. A failure
there leaves the game's layout and never fails the sprites step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1022)

The engine keeps a sprite's first upload, HD frames included, until it
exits, so `layout even` typed again after a rebuild laid the old pixels
out by the new table's boxes. The hook now remembers the files it first
handed the engine and refuses others until a restart, saying why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ccoventry

Copy link
Copy Markdown
Owner Author

Review and in-game smoke test (agent)

Review

I read the whole diff: even.rs, the scale.rs changes (index stub, copy, atlas draw), hud_sprites::draw_holes_instead, the atlas soft marks, texture_hires, killfeed_icons.py --even, sprites_hd.py, the docs and Commands.txt.

Fixed, 7e1bbea: typing layout even again re-reads the sprite and table, and the old comment said "so a rebuilt one counts". But the engine keeps the atlas it first loaded, HD frames included. An atlas rebuilt while the game runs would have drawn the old pixels cut by the new boxes, so wrong or cut icons. The hook now remembers the files it first gave the engine (FNV-1a over the .spr and .txt). Different files are refused until a restart, and the status line says why. A unit test covers it, and the docs (§8, the module doc) say so.

Checked, no change needed:

  • Per frame: atomics only. The RwLocks are taken only at a load or when the names change. Everything runs on the engine thread.
  • Bounds: the .spr header, frames and pixel spans, the table's numbers, the box-in-frame fit, the 24-byte names with no terminator, and the index into the copy are all checked. Nothing indexes without a check, so nothing can panic.
  • Missing or partly loaded atlas: a missing file, a bad file or a frame without its HD file leaves the game's layout, and the status says which.
  • Switching live: layout default resets READY and the rects at once, and scale's copy keys on the layout.
  • HD off gives the game's layout from the next frame.
  • Demo reload: the next weapons-sheet load moves the generation, and the atlas loads again.
  • client.dll reload: it doesn't happen (measured, CLAUDE.md).
  • layout default output unchanged: in render_icon, the only edits on the default path are fit = refit (not read later) and info moved earlier. shrink_below is unset in the table.
  • docs/dodstudio_commands.md and Commands.txt are updated. console_commands_data.js needs no regeneration: its ds_hud_deathmsg text is cut before the changed words.

Tests: cargo test -p goldsrc-hooks --target i686-pc-windows-msvc 801 passed. Clippy 1.98.1 is clean on i686 and host, and fmt is clean. test_killfeed_icons 68 OK. CI is green on 7e1bbea.
Test DLL rebuilt from 7e1bbea: local\test-1022\dodstudio_goldsrc_hooks.dll.

In game (Brain account, game_probe, -addons, 1280x720, solifotw-greetz1, ds_demo_seek_to 466)

I copied the atlas into each install's dod_addon for the test and removed it afterwards. Both installs are as they were.

Measured, POST (first run with the builder's DLL, second run with 7e1bbea):

  • Log: sprites/dodstudio_killfeed.spr loaded as handle 91: 42 box(es) in 2 frame(s) and 42 of the HUD's 201 sprites draw from the atlas. No crash, no errors.
  • scale 2.5:
    • layout default gives today's uneven lines.
    • layout even steps every line 65 px. I read the line y's off the grabs: 52, 117, 182 … 507.
  • scale 1 with layout even steps every line 26 px.
  • Icons seen drawn from the atlas, whole, with nothing from a neighbour box:
    • kar, garand, scoped kar, MP40, MP44 (with its magazine), Thompson, .30 cal (with its bipod), Bren, MG42;
    • colt, luger, bazooka;
    • grenade and stick grenade (on PRE), American, German and British knives, spade;
    • garand-butt cartoon.
  • ds_hd_enabled 0: the status says layout = even, waiting: HD is off, and the game's layout draws. ds_hd_enabled 1 brings the even layout back.
  • ds_demo_reload loads the atlas again (handle 71), and the feed stays even.
  • With 7e1bbea, I changed the table on disk mid-session and typed layout even again. The reply was the atlas changed on disk since the game first loaded it ... restart the game, and the game's layout drew.

Measured, PRE (7e1bbea):

  • The same log lines.
  • Every step above gives the same result, except the stale-table check, which I ran on POST only.
  • Also seen whole: Webley, grease gun, FG42, mortar, Enfield bayonet.

Seen, not caused by this PR: with HD off on PRE the sheets show stock pixels, so scale 2.5 there shows neighbour fragments (the knife and kar lines). With HD off the even layout falls back to the game's layout, so this is #728's path; I didn't compare it against a dev DLL. At scale 1 with the game's layout, the short knife and grenade lines overlap the next line, as in stock DoD.

Grabs:

  • local\review-images\1022\ingame\post\: 01–10, 11 (knives), 12 (stale table refused), and a 2x zoom of 02;
  • local\review-images\1022\ingame\pre\: 01–10, and a 2x zoom of the HD-off frame.

Verdict: ready for your test. The PR body's steps still hold; a rebuilt atlas now needs a restart (it said so already).

…out-1022

# Conflicts:
#	docs/dodstudio_commands.md
#	goldsrc-hooks/src/deathmsg.rs
#	goldsrc-hooks/ui/Commands.txt
@ccoventry

Copy link
Copy Markdown
Owner Author

Merged dev (after #1026 and #1034) in; head caf86b44.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[R&D] Kill-feed icons at one consistent size and position across all weapons

1 participant