## About The Pull Request
Fixes a merge conflict between 2 of my stat panel PRs that caused the
'Toggle Stat Panel' button to not work.
Also converts more skin stuff to defines that I missed in the last PR.
## Why It's Good For The Game
Button works wahoo!
## Changelog
🆑
fix: Stat panel's "Toggle Stat Panel" button now works.
/🆑
## About The Pull Request
Ports our optimizations to the version of ghost runechat introduced by
https://github.com/tgstation/tgstation/pull/95223
Instead of looping through all the mobs a second time it will do it in
deadchat_broadcast(). Also adds a global for if drunechat is
enabled/disabled and an admin verb for toggling it on or off.
<details><summary>still works</summary>
<img width="330" height="220" alt="dreamseeker_kkNBPBjbMv"
src="https://github.com/user-attachments/assets/4706ad98-fafe-4f88-9de0-6b72983ab044"
/>
</details>
## Why It's Good For The Game
Better option for performance
## Changelog
🆑
code: speeds ghost runechat up a bit, and makes it show as italic
admin: adds a 'toggle dead runechat' admin verb in case you need to
disable it midround for an event or something
/🆑
## About The Pull Request
Removes Say/Me/OOC/Pray/Whisper as stat panel verbs
1. Adds a hotkey for Pray, P by default, and also adds it to the list of
say modes (so tabbing on tgui-say will show it)
2. Makes Whisper a button on the commandbar, which is literally just
``say "#``, letting players know it exists in a more direct manner.
3. You now need 2 characters to be considered "typing", so `#` for
whisper won't cause you to start to think anymore.
Also messes around with the skin a bit, moving the commandbar to be
separate from the chat window, therefore not resizing if the chat
window/stat panel splitter changes, since I was already messing with the
command bar entirely I might as well look into getting this fixed. This
will go even better with
https://github.com/tgstation/tgstation/pull/95383 as it wont stretch the
command bar when the stat panel is closed.
https://github.com/user-attachments/assets/26cd9df0-7582-445e-9f67-c03d2c9cf46d
Also adds a new Adminhelp submenu for the Pray button for additional
visibility
https://github.com/user-attachments/assets/64f31f86-92a2-4f04-b248-6bbe301919fe
My current goal is the deletion of the IC tab so I've been trying to
inch towards that.
## Why It's Good For The Game
Moves Praying & Whispering into something that's more easily seen by
newer players, and helps cut some of the more useless verbs out of the
stat panel.
This is also part of my project you can see more of here -
https://hackmd.io/443_dE5lRWeEAp9bjGcKYw?view
## Changelog
🆑
qol: Pray verb is now a hotkey, Whisper is now a command bar button, and
all say verbs have been removed from the stat panel.
qol: Admin button on the escape menu is now its own section for praying,
as well as seeing your latest ticket & admin notes.
fix: Resizing the stat panel will no longer change the size of the
command bar.
/🆑
## About The Pull Request
Add the `ooc_colour` var to a `client`. admins can feel free to set it
to whatever. It will even override other admins ooc colour preferences!
GO be dickheads to each other!
## Why It's Good For The Game
Admin freedom is always good.
## Changelog
🆑
admin: admins can more freely mess with ooc colours
/🆑
Co-authored-by: Josh Powell <josh.powell@softwire.com>
## About The Pull Request
Removes Object tab, which didn't work as it required a whole reload of
your verbs to update to what verbs would show up in it (which is very
hard to actually trigger as a player), but leaves the verbs that had
them show in the dropdown menu.
Removes the Point-To and Examine verbs from the Stat panel, now it's
commandbar only. Originally only did Point-To, but because the stat
panel sorts itself by the category of the item, then the order of the
name, I had to remove Examine's category so it would remain sorted the
same.
bruuuuuh
<img width="1259" height="985" alt="image"
src="https://github.com/user-attachments/assets/ea89db24-fafd-41d6-939e-4dbc7bbf3828"
/>
<img width="309" height="244" alt="image"
src="https://github.com/user-attachments/assets/3b8d9c52-056b-44ce-8ee8-051439a2fa51"
/>
Removes Server tab for players (not Admins) by moving Show Map Vote
Tallies to the OOC tab, as that was the only verb there.
Activate Held Object has been moved to IC since it's a roundstart verb,
I thought I should leave it be (do we have numbers on how many people
use these? Why would someone use the verb over clicking on the item
in-game or Z? Should I remove this too?).
Admin's Object Possession verbs are now in Admin Fun instead of Object.
Player stat panel
<img width="299" height="194" alt="image"
src="https://github.com/user-attachments/assets/8a3c9ad0-514a-44c5-b023-f71e57de4758"
/>
Admin stat panel
<img width="581" height="224" alt="image"
src="https://github.com/user-attachments/assets/7232485f-af41-469e-abfd-fce066816eec"
/>
<img width="624" height="297" alt="image"
src="https://github.com/user-attachments/assets/6eb540be-4912-44f6-be36-5fe3a74b7f19"
/>
## Why It's Good For The Game
We have 2 tabs that exists for a total of 3 procs, 2 of them fit
elsewhere and the last one doesn't work at all. Most verbs here don't
show up in the stat panel at all for the vast majority of players due to
the stat panel not actually loading them in when it's available, which
to me shows that it's a feature that isn't really cared about anyways.
It also helps newer players because there's less tabs to navigate and
less verbs to sift through, we have a ton of verbs that basically don't
need to exist and they only exist to make looking for the important
verbs a larger hassle.
## Changelog
🆑
del: Deleted Object tab, and Server tab for players. Activate Held
Object verb is now in the IC tab. Examine and Point To was removed from
the stat panel.
admin: Object possession verbs have been moved to Admin Fun.
/🆑
## About The Pull Request
It's just a partial cleanup of
anti-[STYLE](https://github.com/tgstation/tgstation/blob/master/.github/guides/STYLE.md)
code from /tg/'s ancient history. I compiled & tested with my helpful
assistant and damage is still working.
<img width="1920" height="1040" alt="image"
src="https://github.com/user-attachments/assets/26dabc17-088f-4008-b299-3ff4c27142c3"
/>
I'll upload the .cs script I used to do it shortly.
## Why It's Good For The Game
Just minor code cleanup.
Script used is located at https://metek.tech/camelTo-Snake.7z
EDIT 11/23/25: Updated the script to use multithreading and sequential
scan so it works a hell of a lot faster
```
/*
//
Copyright 2025 Joshua 'Joan Metekillot' Kidder
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
//
*/
using System.Text.RegularExpressions;
class Program
{
static async Task Main(string[] args)
{
var readFile = new FileStreamOptions
{
Access = FileAccess.Read,
Share = FileShare.ReadWrite,
Options = FileOptions.Asynchronous | FileOptions.SequentialScan
};
FileStreamOptions writeFile = new FileStreamOptions
{
Share = FileShare.ReadWrite,
Access = FileAccess.ReadWrite,
Mode = FileMode.Truncate,
Options = FileOptions.Asynchronous
};
RegexOptions regexOptions = RegexOptions.Multiline | RegexOptions.Compiled;
Dictionary<string, int> changedProcs = new();
string regexPattern = @"(?<=\P{L})([a-z]+)([A-Z]{1,2}[a-z]+)*(Brute|Burn|Fire|Tox|Oxy|Organ|Stamina)(Loss)([A-Z]{1,2}[a-z]+)*";
Regex camelCaseProcRegex = new(regexPattern, regexOptions);
string snakeify(Match matchingRegex)
{
var vals =
matchingRegex.Groups.Cast<Group>().SelectMany(_ => _.Captures).Select(_ => _.Value).ToArray();
var newVal = string.Join("_", vals.Skip(1).ToArray()).ToLower();
string logString = $"{vals[0]} => {newVal}";
if (changedProcs.TryGetValue(logString, out int value))
{
changedProcs[logString] = value + 1;
}
else
{
changedProcs.Add(logString, 1);
}
return newVal;
}
var dmFiles = Directory.EnumerateFiles(".", "*.dm", SearchOption.AllDirectories).ToAsyncEnumerable<string>();
// uses default ParallelOptions
// https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.paralleloptions?view=net-10.0#main
await Parallel.ForEachAsync(dmFiles, async (filePath, UnusedCancellationToken) =>
{
var reader = new StreamReader(filePath, readFile);
string oldContent = await reader.ReadToEndAsync();
string newContent = camelCaseProcRegex.Replace(oldContent, new MatchEvaluator((Func<Match, string>)snakeify));
if (oldContent != newContent)
{
var writer = new StreamWriter(filePath, writeFile);
await writer.WriteAsync(newContent);
await writer.DisposeAsync();
}
reader.Dispose();
});
var logToList = changedProcs.Cast<KeyValuePair<string, int>>().ToList();
foreach (var pair in logToList)
{
Console.WriteLine($"{pair.Key}: {pair.Value} locations");
}
}
}
```
## Changelog
🆑 Bisar
code: All (Brute|Burn|Fire|Tox|Oxy|Organ|Stamina)(Loss) procs now use
snake_case, in-line with the STYLE guide. Underscores rule!
/🆑
## About The Pull Request
Abductors, while "talking" and emoting, have typing indicators.
<img width="114" height="138" alt="image"
src="https://github.com/user-attachments/assets/b48c8647-a96f-4a81-b117-9c13c58863fd"
/>
## Why It's Good For The Game
Removes the inherent type-baiting abductor players face by other people
literally being unable to know they're typing. They still can't talk to
other people, this just shows the little bubble. _Technically_ an
abductor-antag nerf, but it's so minor and adds QoL, I think it's very
worth it.
## Proof Of Testing
Compiles.
<details>
<summary>Screenshots/Videos</summary>
</details>
## Changelog
🆑 sunnyaries
add:onlookers may know when an abductor is emoting. millions must gleep
/🆑
## About The Pull Request
#91419 removed change_view in mob login for un-inited clients, which
leaves 1 second delayed fit_viewport calls from
attempt_auto_fit_viewport as the only way for new clients to adjust
their viewport from the default square (which is also only ran once all
subsystems finish initializing).
Just calling apply() after applying our prefs to the view_size is enough
to make it work, also we don't need the said second delay in
attempt_auto_fit_viewport anymore either.
## Changelog
🆑
fix: Fixed delayed viewport fitting on server join/init
/🆑
Implements https://hackmd.io/@tgstation/SkeUS7lSp , rewriting Dynamic
from the ground-up
- Dynamic configuration is now vastly streamlined, making it far far far
easier to understand and edit
- Threat is gone entirely; round chaos is now determined by dynamic
tiers
- There's 5 dynamic tiers, 0 to 4.
- 0 is a pure greenshift.
- Tiers are just picked via weight - "16% chance of getting a high chaos
round".
- Tiers have min pop ranges. "Tier 4 (high chaos) requires 25 pop to be
selected".
- Tier determines how much of every ruleset is picked. "Tier 4 (High
Chaos) will pick 3-4 roundstart[1], 1-2 light, 1-2 heavy, and 2-3
latejoins".
- The number of rulesets picked depends on how many people are in the
server - this is also configurable[2]. As an example, a tier that
demands "1-3" rulesets will not spawn 3 rulesets if population <= 40 and
will not spawn 2 rulesets if population <= 25.
- Tiers also determine time before light, heavy, and latejoin rulesets
are picked, as well as the cooldown range between spawns. More chaotic
tiers may send midrounds sooner or wait less time between sending them.
- On the ruleset side of things, "requirements", "scaling", and
"enemies" is gone.
- You can configure a ruleset's min pop and weight flat, or per tier.
- For example a ruleset like Obsession is weighted higher for tiers 1-2
and lower for tiers 3-4.
- Rather than scaling up, roundstart rulesets can just be selected
multiple times.
- Rulesets also have `min_antag_cap` and `max_antag_cap`.
`min_antag_cap` determines how many candidates are needed for it to run,
and `max_antag_cap` determines how many candidates are selected.
- Rulesets attempt to run every 2.5 minutes. [3]
- Light rulesets will ALWAYS be picked before heavy rulesets. [4]
- Light injection chance is no longer 100%, heavy injection chance
formula has been simplified.
- Chance simply scales based on number of dead players / total number
off players, with a flag 50% chance if no antags exist. [5]
[1] This does not guarantee you will actually GET 3-4 roundstart
rulesets. If a roundstart ruleset is picked, and it ends up being unable
to execute (such as "not enough candidates", that slot is effectively a
wash.) This might be revisited.
[2] Currently, this is a hard limit - below X pop, you WILL get a
quarter or a half of the rulesets. This might be revisited to just be
weighted - you are just MORE LIKELY to get a quarter or a half.
[3] Little worried about accidentally frontloading everything so we'll
see about this
[4] This may be revisited but in most contexts it seems sensible.
[5] This may also be revisited, I'm not 100% sure what the best / most
simple way to tackle midround chances is.
Other implementation details
- The process of making rulesets has been streamlined as well. Many
rulesets only amount to a definition and `assign_role`.
- Dynamic.json -> Dynamic.toml
- Dynamic event hijacked was ripped out entirely.
- Most midround antag random events are now dynamic rulesets. Fugitives,
Morphs, Slaughter Demons, etc.
- The 1 weight slaughter demon event is gone. RIP in peace.
- There is now a hidden midround event that simply adds +1 latejoin, +1
light, or +1 heavy ruleset.
- `mind.special_role` is dead. Minds have a lazylist of special roles
now but it's essentially only used for traitor panel.
- Revs refactored almost entirely. Revs can now exist without a dynamic
ruleset.
- Cult refactored a tiny bit.
- Antag datums cleaned up.
- Pre round setup is less centralized on Dynamic.
- Admins have a whole panel for interfacing with dynamic. It's pretty
slapdash I'm sure someone could make a nicer looking one.


- Maybe some other things.
See readme for more info.
Will you see a massive change in how rounds play out? My hunch says
rounds will spawn less rulesets on average, but it's ultimately to how
it's configured
🆑 Melbert
refactor: Dynamic rewritten entirely, report any strange rounds
config: Dynamic config reworked, it's now a TOML file
refactor: Refactored antag roles somewhat, report any oddities
refactor: Refactored Revolution entirely, report any oddities
del: Deleted most midround events that spawn antags - they use dynamic
rulesets now
add: Dynamic rulesets can now be false alarms
add: Adds a random event that gives dynamic the ability to run another
ruleset later
admin: Adds a panel for messing around with dynamic
admin: Adds a panel for chance for every dynamic ruleset to be selected
admin: You can spawn revs without using dynamic now
fix: Nuke team leaders get their fun title back
/🆑
(cherry picked from commit 4c277dc572)
## About The Pull Request
Implements https://hackmd.io/@tgstation/SkeUS7lSp , rewriting Dynamic
from the ground-up
- Dynamic configuration is now vastly streamlined, making it far far far
easier to understand and edit
- Threat is gone entirely; round chaos is now determined by dynamic
tiers
- There's 5 dynamic tiers, 0 to 4.
- 0 is a pure greenshift.
- Tiers are just picked via weight - "16% chance of getting a high chaos
round".
- Tiers have min pop ranges. "Tier 4 (high chaos) requires 25 pop to be
selected".
- Tier determines how much of every ruleset is picked. "Tier 4 (High
Chaos) will pick 3-4 roundstart[1], 1-2 light, 1-2 heavy, and 2-3
latejoins".
- The number of rulesets picked depends on how many people are in the
server - this is also configurable[2]. As an example, a tier that
demands "1-3" rulesets will not spawn 3 rulesets if population <= 40 and
will not spawn 2 rulesets if population <= 25.
- Tiers also determine time before light, heavy, and latejoin rulesets
are picked, as well as the cooldown range between spawns. More chaotic
tiers may send midrounds sooner or wait less time between sending them.
- On the ruleset side of things, "requirements", "scaling", and
"enemies" is gone.
- You can configure a ruleset's min pop and weight flat, or per tier.
- For example a ruleset like Obsession is weighted higher for tiers 1-2
and lower for tiers 3-4.
- Rather than scaling up, roundstart rulesets can just be selected
multiple times.
- Rulesets also have `min_antag_cap` and `max_antag_cap`.
`min_antag_cap` determines how many candidates are needed for it to run,
and `max_antag_cap` determines how many candidates are selected.
- Rulesets attempt to run every 2.5 minutes. [3]
- Light rulesets will ALWAYS be picked before heavy rulesets. [4]
- Light injection chance is no longer 100%, heavy injection chance
formula has been simplified.
- Chance simply scales based on number of dead players / total number
off players, with a flag 50% chance if no antags exist. [5]
[1] This does not guarantee you will actually GET 3-4 roundstart
rulesets. If a roundstart ruleset is picked, and it ends up being unable
to execute (such as "not enough candidates", that slot is effectively a
wash.) This might be revisited.
[2] Currently, this is a hard limit - below X pop, you WILL get a
quarter or a half of the rulesets. This might be revisited to just be
weighted - you are just MORE LIKELY to get a quarter or a half.
[3] Little worried about accidentally frontloading everything so we'll
see about this
[4] This may be revisited but in most contexts it seems sensible.
[5] This may also be revisited, I'm not 100% sure what the best / most
simple way to tackle midround chances is.
Other implementation details
- The process of making rulesets has been streamlined as well. Many
rulesets only amount to a definition and `assign_role`.
- Dynamic.json -> Dynamic.toml
- Dynamic event hijacked was ripped out entirely.
- Most midround antag random events are now dynamic rulesets. Fugitives,
Morphs, Slaughter Demons, etc.
- The 1 weight slaughter demon event is gone. RIP in peace.
- There is now a hidden midround event that simply adds +1 latejoin, +1
light, or +1 heavy ruleset.
- `mind.special_role` is dead. Minds have a lazylist of special roles
now but it's essentially only used for traitor panel.
- Revs refactored almost entirely. Revs can now exist without a dynamic
ruleset.
- Cult refactored a tiny bit.
- Antag datums cleaned up.
- Pre round setup is less centralized on Dynamic.
- Admins have a whole panel for interfacing with dynamic. It's pretty
slapdash I'm sure someone could make a nicer looking one.


- Maybe some other things.
## Why It's Good For The Game
See readme for more info.
Will you see a massive change in how rounds play out? My hunch says
rounds will spawn less rulesets on average, but it's ultimately to how
it's configured
## Changelog
🆑 Melbert
refactor: Dynamic rewritten entirely, report any strange rounds
config: Dynamic config reworked, it's now a TOML file
refactor: Refactored antag roles somewhat, report any oddities
refactor: Refactored Revolution entirely, report any oddities
del: Deleted most midround events that spawn antags - they use dynamic
rulesets now
add: Dynamic rulesets can now be false alarms
add: Adds a random event that gives dynamic the ability to run another
ruleset later
admin: Adds a panel for messing around with dynamic
admin: Adds a panel for chance for every dynamic ruleset to be selected
admin: You can spawn revs without using dynamic now
fix: Nuke team leaders get their fun title back
/🆑
Removes the blur of the escape menu so players can sorta see what's
going on in their game while going through, so they can react in case
something's happening while they're in menu.
Changes the font of the escape menu so it's no longer the LISA font
Makes the title only show up on the home menu, so being on the suicide
panel wont show you "another day on space station 13".
Moves the 'back' button on the suicide menu to the top left.


This hopes to make the escape menu a little more easy to go through,
removing some obstruction of your game's screen so you can see what's
happening around you, and give a little generic part of submenus in case
we add more (which I do have at least one planned).
🆑
qol: The escape menu no longer blurs the game in the background.
qol: The escape menu now has a more readable font, and the escape menu's
back button is now smaller and in the corner.
/🆑
This is my second contribution to the move towards removing the stat
panel (first one being https://github.com/tgstation/tgstation/pull/90572
)
This moves the info buttons at the top right of the game's screen
(Changelog, Rules, Wiki, etc) to the Escape menu, except for Fullscreen
which is now a pref instead. This means you can set Fullscreen to be on
permanently and every launch will automatically fullscreen you (the
viewport will be a little off because it only fixes it once
initialization is complete). This follows through rounds and auto
updates if you set your game to fullscreen with the OOC button or F11,
so players will learn about the pref after playing a round with
fullscreen enabled.
What the game now looks like
be a newscaster
https://github.com/user-attachments/assets/7871a226-1e0b-410d-a690-88f3616bebb0
This is something I wanted to do since the Esc menu was added but just
never got around to it, but here it is.
These buttons don't warrant being in the player's face 24/7 and since
we've want to remove the stat panel and this has to be somewhere, I
thought it would be a better fit in the Escape menu. It helps make the
Esc menu the tool players use to access their OOC tools and overall I
think improves the appearance of the game's screen to something more
like an actual game would look like, especially when our comparison is
SS14.
🆑
qol: Info buttons previously at the top right of your screen (Changelog,
wiki, forums) is now in the Escape menu.
qol: Fullscreen is now a preferences and will follow you through rounds.
/🆑
## About The Pull Request
Removes the blur of the escape menu so players can sorta see what's
going on in their game while going through, so they can react in case
something's happening while they're in menu.
Changes the font of the escape menu so it's no longer the LISA font
Makes the title only show up on the home menu, so being on the suicide
panel wont show you "another day on space station 13".
Moves the 'back' button on the suicide menu to the top left.


## Why It's Good For The Game
This hopes to make the escape menu a little more easy to go through,
removing some obstruction of your game's screen so you can see what's
happening around you, and give a little generic part of submenus in case
we add more (which I do have at least one planned).
## Changelog
🆑
qol: The escape menu no longer blurs the game in the background.
qol: The escape menu now has a more readable font, and the escape menu's
back button is now smaller and in the corner.
/🆑
## About The Pull Request
This is my second contribution to the move towards removing the stat
panel (first one being https://github.com/tgstation/tgstation/pull/90572
)
This moves the info buttons at the top right of the game's screen
(Changelog, Rules, Wiki, etc) to the Escape menu, except for Fullscreen
which is now a pref instead. This means you can set Fullscreen to be on
permanently and every launch will automatically fullscreen you (the
viewport will be a little off because it only fixes it once
initialization is complete). This follows through rounds and auto
updates if you set your game to fullscreen with the OOC button or F11,
so players will learn about the pref after playing a round with
fullscreen enabled.
What the game now looks like
##### Alt ideas for sprites: Changelog can be a newspaper and Forums can
be a newscaster
https://github.com/user-attachments/assets/7871a226-1e0b-410d-a690-88f3616bebb0
This is something I wanted to do since the Esc menu was added but just
never got around to it, but here it is.
## Why It's Good For The Game
These buttons don't warrant being in the player's face 24/7 and since
we've want to remove the stat panel and this has to be somewhere, I
thought it would be a better fit in the Escape menu. It helps make the
Esc menu the tool players use to access their OOC tools and overall I
think improves the appearance of the game's screen to something more
like an actual game would look like, especially when our comparison is
SS14.
## Changelog
🆑
qol: Info buttons previously at the top right of your screen (Changelog,
wiki, forums) is now in the Escape menu.
qol: Fullscreen is now a preferences and will follow you through rounds.
/🆑
## About The Pull Request
Winget returns 0x0 size when the window is minimized, and it can be set
to 0x0 via external tools as well. In this case we should just abort
fit_viewport to prevent runtimes.
## Changelog
Not player facing
Replaces the asset subsystem's spritesheet generator with a rust-based
implementation (https://github.com/tgstation/rust-g/pull/160).
This is a rough port of
https://github.com/BeeStation/BeeStation-Hornet/pull/10404, but it
includes fixes for some cases I didn't catch that apply on TG.
(FWIW we've been using this system on prod for over a year and
encountered no major issues.)

`/datum/asset/spritesheet_batched`: A version of the spritesheet system
that collects a list of `/datum/universal_icon`s and sends them off to
rustg asynchronously, and the generation also runs on another thread, so
the game doesn't block during realize_spritesheet. The rust generation
is about 10x faster when it comes to actual icon generation, but the
biggest perk of the batched spritesheets is the caching system.
This PR notably does not convert a few things to the new spritesheet
generator.
- Species and antagonist icons in the preferences view because they use
getFlatIcon ~~which can't be converted to universal icons~~.
- Yes, this is still a *massive* cost to init, unfortunately. On Bee, I
actually enabled the 'legacy' cache on prod and development, which you
can see in my PR. That's why I added the 'clear cache' verb and the
`unregister()` procs, because it can force a regeneration at runtime. I
decided not to port this, since I think it would be detrimental to the
large amount of contributors here.
- It is *technically* possible to port parts of this to the uni_icon
system by making a uni_icon version of getFlatIcon. However, some
overlays use runtime-generated icons which are ~~completely unparseable
to IconForge, since they're stored in the RSC and don't exist as files
anywhere~~. This is most noticeable with things like hair (which blend
additively with the hair mask on the server, thus making them invisible
to `get_flat_uni_icon`). It also doesn't help that species and antag
icons will still need to generate a bunch of dummies and delete them to
even verify cache validity.
- It is actually possible to write the RSC icons to the filesystem
(using fcopy) and reference them in IconForge. However, I'm going to
wait on doing this until I port my GAGS implementation because it
requires GAGS to exist on the filesystem as well.
IconForge generates a cache based on the set of icons used, all
transform operations applied, and the source DMIs of each icon used
within the spritesheet. It can compare the hashes and invalidate the
cache automatically if any of these change. This means we can enable
caching on development, and have absolutely no downsides, because if
anything changes, the cache invalidates itself.
The caching has a mean cost of ~5ms and saves a lot of time compared to
generating the spritesheet, even with rust's faster generation. The main
downside is that the cache still requires building the list of icons and
their transforms, then json encoding it to send to rustg.
Here's an abbreviated example of a cache JSON. All of these need to
match for the cache to be valid. `input_hash` contains the transform
definitions for all the sprites in the spritesheet, so if the input to
iconforge changes, that hash catches it. The `sizes` and `sprites` are
loaded into DM.
```json
{
"input_hash": "99f1bc67d590e000",
"dmi_hashes": {
"icons/ui/achievements/achievements.dmi": "771200c75da11c62"
},
"sizes": [
"76x76"
],
"sprites": {
"achievement-rustascend": {
"size_id": "76x76",
"position": 1
}
},
"rustg_version": "3.6.0",
"dm_version": 1
}
```
Universal icons are just a collection of DMI, Icon State, and any icon
transformation procs you apply (blends, crops, scales). They can be
convered to DM icons via `to_icon()`. I've included an implementation of
GAGS that produces universal icons, allowing GAGS items to be converted
into them. IconForge can read universal icons and add them to
spritesheets. It's basically just a wrapper that reimplements BYOND icon
procs.
Converts some uses of md5asfile within legacy spritesheets to use
rustg_hash_file instead, improving the performance of their generation.
Fixes lizard body markings not showing in previews, and re-adds eyes to
the ethereal color preview. This is a side effect of IconForge having
*much* better error handling than DM icon procs. Invalid stuff that gets
passed around will error instead of silently doing nothing.
Changes the CSS used in legacy spritesheet generation to split
`background: url(...) no-repeat` into separate props. This is necessary
for WebView2, as IE treats these properties differently - adding
`background-color` to an icon object (as seen in the R&D console) won't
work if you don't split these out.
Deletes unused spritesheets and their associated icons (condiments
spritesheet, old PDA spritesheet)
If you press "Character Setup", the 10-13sec of lag is now approximately
0.5-2 seconds.
Tracy profile showing the time spent on get_asset_datum. I pressed the
preferences button during init on both branches. Do note that this was
ran with a smart cache HIT, so no generation occurred.

Much lower worst-case for /datum/asset/New (which includes
`create_spritesheets()` and `register()`)

Here's a look at the internal costs from rustg - as you can see
`generate_spritesheet()` is very fast:

**Before**

**After**

🆑
fix: Fixed lizard body markings and ethereal feature previews in the
preference menu missing some overlays.
refactor: Optimized spritesheet asset generation greatly using rustg
IconForge, greatly reducing post-initialization lag as well as reducing
init times and saving server computation.
config: Added 'smart' asset caching, for batched rustg IconForge
spritesheets. It is persistent and suitable for use on local, with
automatic invalidation.
add: Added admin verbs - Debug -> Clear Smart/Legacy Asset Cache for
spritesheets.
fix: Fixed R&D console icons breaking on WebView2/516
/🆑
## About The Pull Request
Winget returns 0x0 size when the window is minimized, and it can be set
to 0x0 via external tools as well. In this case we should just abort
fit_viewport to prevent runtimes.
## Changelog
Not player facing
## About The Pull Request
Replaces the asset subsystem's spritesheet generator with a rust-based
implementation (https://github.com/tgstation/rust-g/pull/160).
This is a rough port of
https://github.com/BeeStation/BeeStation-Hornet/pull/10404, but it
includes fixes for some cases I didn't catch that apply on TG.
(FWIW we've been using this system on prod for over a year and
encountered no major issues.)
### TG MAINTAINER NOTE

### Batched Spritesheets
`/datum/asset/spritesheet_batched`: A version of the spritesheet system
that collects a list of `/datum/universal_icon`s and sends them off to
rustg asynchronously, and the generation also runs on another thread, so
the game doesn't block during realize_spritesheet. The rust generation
is about 10x faster when it comes to actual icon generation, but the
biggest perk of the batched spritesheets is the caching system.
This PR notably does not convert a few things to the new spritesheet
generator.
- Species and antagonist icons in the preferences view because they use
getFlatIcon ~~which can't be converted to universal icons~~.
- Yes, this is still a *massive* cost to init, unfortunately. On Bee, I
actually enabled the 'legacy' cache on prod and development, which you
can see in my PR. That's why I added the 'clear cache' verb and the
`unregister()` procs, because it can force a regeneration at runtime. I
decided not to port this, since I think it would be detrimental to the
large amount of contributors here.
- It is *technically* possible to port parts of this to the uni_icon
system by making a uni_icon version of getFlatIcon. However, some
overlays use runtime-generated icons which are ~~completely unparseable
to IconForge, since they're stored in the RSC and don't exist as files
anywhere~~. This is most noticeable with things like hair (which blend
additively with the hair mask on the server, thus making them invisible
to `get_flat_uni_icon`). It also doesn't help that species and antag
icons will still need to generate a bunch of dummies and delete them to
even verify cache validity.
- It is actually possible to write the RSC icons to the filesystem
(using fcopy) and reference them in IconForge. However, I'm going to
wait on doing this until I port my GAGS implementation because it
requires GAGS to exist on the filesystem as well.
#### Caching
IconForge generates a cache based on the set of icons used, all
transform operations applied, and the source DMIs of each icon used
within the spritesheet. It can compare the hashes and invalidate the
cache automatically if any of these change. This means we can enable
caching on development, and have absolutely no downsides, because if
anything changes, the cache invalidates itself.
The caching has a mean cost of ~5ms and saves a lot of time compared to
generating the spritesheet, even with rust's faster generation. The main
downside is that the cache still requires building the list of icons and
their transforms, then json encoding it to send to rustg.
Here's an abbreviated example of a cache JSON. All of these need to
match for the cache to be valid. `input_hash` contains the transform
definitions for all the sprites in the spritesheet, so if the input to
iconforge changes, that hash catches it. The `sizes` and `sprites` are
loaded into DM.
```json
{
"input_hash": "99f1bc67d590e000",
"dmi_hashes": {
"icons/ui/achievements/achievements.dmi": "771200c75da11c62"
},
"sizes": [
"76x76"
],
"sprites": {
"achievement-rustascend": {
"size_id": "76x76",
"position": 1
}
},
"rustg_version": "3.6.0",
"dm_version": 1
}
```
### Universal Icons
Universal icons are just a collection of DMI, Icon State, and any icon
transformation procs you apply (blends, crops, scales). They can be
convered to DM icons via `to_icon()`. I've included an implementation of
GAGS that produces universal icons, allowing GAGS items to be converted
into them. IconForge can read universal icons and add them to
spritesheets. It's basically just a wrapper that reimplements BYOND icon
procs.
### Other Stuff
Converts some uses of md5asfile within legacy spritesheets to use
rustg_hash_file instead, improving the performance of their generation.
Fixes lizard body markings not showing in previews, and re-adds eyes to
the ethereal color preview. This is a side effect of IconForge having
*much* better error handling than DM icon procs. Invalid stuff that gets
passed around will error instead of silently doing nothing.
Changes the CSS used in legacy spritesheet generation to split
`background: url(...) no-repeat` into separate props. This is necessary
for WebView2, as IE treats these properties differently - adding
`background-color` to an icon object (as seen in the R&D console) won't
work if you don't split these out.
Deletes unused spritesheets and their associated icons (condiments
spritesheet, old PDA spritesheet)
## Why It's Good For The Game
If you press "Character Setup", the 10-13sec of lag is now approximately
0.5-2 seconds.
Tracy profile showing the time spent on get_asset_datum. I pressed the
preferences button during init on both branches. Do note that this was
ran with a smart cache HIT, so no generation occurred.

Much lower worst-case for /datum/asset/New (which includes
`create_spritesheets()` and `register()`)

Here's a look at the internal costs from rustg - as you can see
`generate_spritesheet()` is very fast:

### Comparison for a single spritesheet - chat spritesheet:
**Before**

**After**

## Changelog
🆑
fix: Fixed lizard body markings and ethereal feature previews in the
preference menu missing some overlays.
refactor: Optimized spritesheet asset generation greatly using rustg
IconForge, greatly reducing post-initialization lag as well as reducing
init times and saving server computation.
config: Added 'smart' asset caching, for batched rustg IconForge
spritesheets. It is persistent and suitable for use on local, with
automatic invalidation.
add: Added admin verbs - Debug -> Clear Smart/Legacy Asset Cache for
spritesheets.
fix: Fixed R&D console icons breaking on WebView2/516
/🆑
## About The Pull Request
#89099 added Who and Adminwho to OOC, which I think is incorrect (as OOC
is for OOC chat specifically)
So I have moved them to INFO (which is, quote, "Non-urgent messages from
the game and items")
In a similar vein I typed "Tip of the Round" and "Show Server Revision"
as info rather than OOC and untyped respectively
## Why It's Good For The Game
I don't like pressing "who" and having to change to my ooc tab
## Changelog
🆑 Melbert
qol: "Who" and "Adminwho" are typed as "Info" and not "OOC" (so they no
longer go to "OOC" tabs)
qol: "Tip of the Round" and "Show Server Revision" are also typed as
"Info", rather than "OOC" and untyped respectively
/🆑
## About The Pull Request
Moved broken on 516 UI's to browser datum
They now work and have a dark theme
Most of them are admin ones (All except 1)
I tried to check all the raw HTML UI's by typing `<< browse(` into the
VSC search and going through each element, but I might have missed
something.
What worked as it was, I didn't touch, except for the Dynamic control
UI's
## Why It's Good For The Game
Admin can do their things on 516
Coders/Mappers can debug some stuff on 516

## Changelog
🆑
fix: Admin/Debug UI's (Especially the Game Panel) now work properly on
Byond 516, instead of showing raw HTML
/🆑
## About The Pull Request
`boldannounce` is NOT for use ICly it's only for OOC stuff. `bolddanger`
is identical it just doesn't carry the same baggage
## Changelog
🆑 Melbert
fix: Stuff like the SM exploding will no longer output to your OOC tab
/🆑
3591 individual conflicts
Update build.js
Update install_node.sh
Update byond.js
oh my fucking god
hat
slow
huh
holy shit
we all fall down
2 more I missed
2900 individual conflicts
2700 Individual conflicts
replaces yarn file with tg version, bumping us down to 2200-ish
Down to 2000 individual conflicts
140 down
mmm
aaaaaaaaaaaaaaaaaaa
not yt
575
soon
900 individual conflicts
600 individual conflicts, 121 file conflicts
im not okay
160 across 19 files
29 in 4 files
0 conflicts, compiletime fix time
some minor incap stuff
missed ticks
weird dupe definition stuff
missed ticks 2
incap fixes
undefs and pie fix
Radio update and some extra minor stuff
returns a single override
no more dupe definitions, 175 compiletime errors
Unticked file fix
sound and emote stuff
honk and more radio stuff
## About The Pull Request
Changes map votes to be based on a persistent tally count.
Tallies for maps are cached between rounds and are added to by map
votes.
When a map is chosen, and it wasn't the only valid one, the tallies for
said chosen map will be reset.
Refactors map vote handling and moves it from SSmapping to SSmap_vote.
Rock the Vote has been removed as a result of this refactor.
## Why It's Good For The Game
Makes it more likely that all maps will be played over the course of a
server instead of always being truly random.
Removes some clutter off of SSmapping.
🆑
refactor: Map Votes are now carried over between rounds. When a map vote
is actually a contest, the winning map will have its votes reset.
/🆑
## About The Pull Request
All usages of world.icon_size in code have been replaced with new
`ICONSIZE_X`, `ICONSIZE_Y` and `ICONSIZE_ALL` defines depending on
context
Replaces some "32" magic numbers with the defines
A few bits of code have been modified to split up x/y math as well
## Why It's Good For The Game
Magic number bad, code more readable, code more flexible and I'm told
there's an access cost to doing world.icon_size so minor performance
gains
## Changelog
🆑 tonty
code: made some code relating to the world's icon size more readable
/🆑
---------
Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
## About The Pull Request
123 changed files and multiple crashes after writing broken regex, I
replaced most remains of direct spans with macros. This cleans up the
code and makes it easier to work with in general, see justification for
the original PR. I also fixed a bunch of broken and/or unclosed spans
here too.
I intentionally avoided replacing spans with multiple classes (in most
cases) and spans in the middle of strings as it would impact readability
(in my opinion at least) and could be done later if required.
## Why It's Good For The Game
Cleaner code, actually using our macros, fixes borked HTML in some
places. See original PR.
## Changelog
Nothing player-facing
## About The Pull Request
Closes#86567
Don't think any of these were found/reported or actually could trigger
in-game
## Why It's Good For The Game
Glory to OpenDream
## Changelog
🆑
code: Fixed multiple minor logic issues with code found by OpenDream's
new pragma
/🆑
## About The Pull Request
In my effort to make the /icons/ folder cleaner and more intuitive
instead of having to rely on recalling names of stuff and looking them
up in code to find them for poor sods such as myself, plus in spurt of
complusion to organize stuff, here goes. I've tracked all changes in
commit descriptions. A lot still to be done, but I know these waves go
over dozens of files making things slow, so went lighter on it.
Destroyed useless impostor files taking up space and cleaned a stray
pixel on my way.
## Why It's Good For The Game
Cleaner /icons/ file means saner spriters, less time spent. Stray pixels
and impostor files (ones which are copies of actually used ones
elsewhere) are not good.
## Changelog
🆑
image: Cleaned a single stray pixel in a single frame of a bite
telegraphing accidentaly found while re-organizing the files.
/🆑
This uses a browser skin element to spy on the command bar and report
back to the server what verb is currently in it and how many characters
it has. it skips reporting if the text hasn't changed since the last
report.
im intentionally not providing the full text in the command bar to the
server, while designing the system so new verbs can be given typing
indicators by editing DM code, not html code.
The report rate is once a second but this could be lowered or tweaked.
Both the tgui say window being open and this system being active because
the command bar starts with `say "` is undefined behavior, mostly the
first one to end the indicator will just freeze indicators for the other
one until it too ends its current indicator session.
The system waits until something besides the `"` is in the argument to
say.
It is enabled for verbs `say`, `me`, and `whisper`.
I don't actually know if this is the case for tgui say. this is a one
line tweak anyways so let me know if this should be changed.
[(This pr closes a
bounty)](https://tgstation13.org/phpBB/viewtopic.php?p=726634#p726634)
🆑 MrStonedOne & Lilah Novi
add: Say commands typed in the command bar now trigger typing indicators
/🆑
---------
Co-authored-by: san7890 <the@ san7890.com>
This uses a browser skin element to spy on the command bar and report
back to the server what verb is currently in it and how many characters
it has. it skips reporting if the text hasn't changed since the last
report.
im intentionally not providing the full text in the command bar to the
server, while designing the system so new verbs can be given typing
indicators by editing DM code, not html code.
The report rate is once a second but this could be lowered or tweaked.
Both the tgui say window being open and this system being active because
the command bar starts with `say "` is undefined behavior, mostly the
first one to end the indicator will just freeze indicators for the other
one until it too ends its current indicator session.
The system waits until something besides the `"` is in the argument to
say.
It is enabled for verbs `say`, `me`, and `whisper`.
I don't actually know if this is the case for tgui say. this is a one
line tweak anyways so let me know if this should be changed.
[(This pr closes a
bounty)](https://tgstation13.org/phpBB/viewtopic.php?p=726634#p726634)
🆑 MrStonedOne & Lilah Novi
add: Say commands typed in the command bar now trigger typing indicators
/🆑
---------
Co-authored-by: san7890 <the@san7890.com>
* Admin Verb Datums MkIII | Now with functional command bar (#82511)
* Modular stuffs
* Put some admin jump verbs back into the context menu | sorts area jump list again (#82647)
## About The Pull Request
See title.
## Why It's Good For The Game
Some admins wanted all the jump verbs back, aswell as making them not
AGhost you.
Also make the Jump To Area verb use a sorted list again
* Hey what if admins were allowed to use the player panel (#82682)
Re-adds the player panel verb to the verb panel.
* Controller Overview UI (#82739)
* Fixes a minor spelling mistake on the admin panel/verb list (#82747)
## About The Pull Request
Corrects `inisimin` to `invisimin`. This addresses #82728, but only
fixes one of the two issues mentioned
## Why It's Good For The Game
-1 spelling mistake
## Changelog
🆑
spellcheck: 'inisimin' verb corrected to 'invisimin'
/🆑
* Player Panel-age (#82757)
* Admin Forced Mob Rename and Preference Update (#82715)
---------
Co-authored-by: Zephyr <12817816+ZephyrTFA@users.noreply.github.com>
Co-authored-by: Useroth <37159550+Useroth@users.noreply.github.com>
Co-authored-by: chel <64568243+iliyaxox@users.noreply.github.com>