Imported from nicokatsu/cs2-smooth-LHT (
AGENTS.md). Install upstream withnpx skills add nicokatsu/cs2-smooth-LHT. Copyright stays with the author.
Agent Guide
This workspace contains a Cities: Skylines II mod project. This file is for future agents working in the repo. Do not assume it is tracked by git.
Global Workflow
Follow the user's global $cs2-mod-workflow for coordination and lane
ownership. This file only records project-specific context for SmoothLHT.
Project
The main project is SmoothLHT, short for Smooth Left-Hand Traffic.
Business goal:
- Make Cities: Skylines II left-hand traffic saves behave better for buildings with internal roads, driveways, and transport paths.
- Flip supported building internal networks so entrances and exits align with LHT traffic flow.
- Give the player a toolbar toggle to control inversion per asset and per upgrade.
- Persist the player's per-prefab choices between loads.
Important user-facing behavior:
- The mod changes prefab-level data, not individual placed building instances.
- Changing a toggle affects all already placed instances of that prefab or upgrade.
- Temporary pathfinding conflicts can happen after switching inversion; replacing the affected building or upgrade usually rebuilds the paths.
- The mod does not support left-hand and right-hand versions of the same asset at the same time.
- The mod is intended for LHT saves. RHT saves should be unaffected.
Core Business Logic
The mod uses the game's built-in subnet inversion mechanism:
- It edits
ObjectSubNets.m_InvertWhen. - Enabled/default inverted state is
NetInvertMode.LefthandTraffic. - Disabled/non-inverted state is
NetInvertMode.Never. - This is a prefab-wide setting. It is not an instance-level mirror flip.
Supported internal network detection:
- The scanner only considers
BuildingPrefabandBuildingExtensionPrefab. - A prefab must have
ObjectSubNets. - Each subnet's
m_NetPrefabis inspected when it is aNetGeometryPrefab. - Supported lane types are:
- active
CarLanewithRoadTypes.Car - active
CarLanewithRoadTypes.Bicycle - active
TrackLanewithTrackTypes.Tram
- active
- Pure pedestrian paths, train tracks, subway tracks, power lines, and other unsupported network types should not qualify by themselves.
Ignored prefab prefixes live in InvertPrefabLHTSystem:
Aquaculture Area Placeholder -WaterOffshore Oil Industry PlaceholderOpenwater Fish Farm EntranceOpenwater Fishing Area EntrancePack10-OHSignature02_Ext02
Default non-inverted prefabs:
BusStation01 Extra PlatformsBusStation01 Taxi StopBusStation01
Data Flow
Startup/load flow:
InvertPrefabLHTSystem.OnWorldReadyandOnGamePreloadcallInvertAllPrefabs.InvertPreferenceStore.Loadreads persisted non-inverted prefab names.InvertiblePrefabScanner.Scanscans building and extension prefab entities.- Scanner returns:
Prefabs: invertible prefabs to updateInvertibleAssets: names of prefabs that qualifyBuildingUpgrades: host building name to qualifying upgrade prefabs
PrefabInvertService.ApplyPreferredInvertModesapplies persisted modes to all scanned invertible prefabs.
Manual toggle flow:
InvertPrefabUISystemreceives tool/prefab change events.- It asks
InvertPrefabLHTSystem.TryGetInvertModewhether the selected prefab should show the toggle. - Toggle visibility is true when the selected prefab is itself invertible or when it has invertible upgrades.
- Toggle state comes from
InvertPreferenceStore.GetDesiredInvertModeusing the selected prefab name. Do not infer UI state from an upgrade's currentm_InvertWhen; that caused edge-case complexity. - When the user toggles,
InvertPrefabLHTSystem.InvertPrefabcallsPrefabInvertService.InvertPrefabAndUpgrades. - The service applies the mode recursively to the selected prefab and mapped upgrades, updates persisted preferences, and saves once.
Upgrade edge case:
- Some host buildings have no qualifying
ObjectSubNets, but their upgrades do. - The host building should still show the toggle if
BuildingUpgradesmaps it to at least one invertible upgrade. - Applying the toggle to that host may not update the host itself if it has no
ObjectSubNets, but it should update and persist the qualifying upgrades.
Important Files
Backend:
SmoothLHT/Mod.cs: mod load/unload and UI module registration entry point.SmoothLHT/Systems/InvertPrefabLHTSystem.cs: orchestrates scanning, preference loading, applying modes, and UI-facing state queries.SmoothLHT/Services/InvertiblePrefabScanner.cs: finds invertible building and extension prefabs and maps host buildings to upgrades.SmoothLHT/Services/PrefabInvertService.cs: appliesNetInvertModeto prefabs and mapped upgrades, then saves preferences.SmoothLHT/Services/InvertPreferenceStore.cs: reads/writesnon_inverted_assets.jsonunder user data.SmoothLHT/Tools/InvertPrefabUISystem.cs: C# UI bindings exposed to the frontend.
Frontend:
SmoothLHT/UI/SmoothLHT/src/index.tsx: UI extension registration.SmoothLHT/UI/SmoothLHT/src/mods/InvertLHTTool.tsx: injects the LHT invert tool section into the game's tool options UI.SmoothLHT/UI/SmoothLHT/imgs/button.svg: toolbar button image.
Publishing:
SmoothLHT/Properties/PublishConfiguration.xml: Paradox Mods metadata, version, changelog, screenshots, tags, and external links.SmoothLHT/Properties/Thumbnail.png: primary thumbnail.SmoothLHT/Properties/Screenshots/*: publish screenshots.README.md: public project README.
Build/package:
SmoothLHT.sln: normal solution build entry.SmoothLHT/SmoothLHT.csproj: CS2 mod project file and references.SmoothLHT/UI/SmoothLHT/package.json: frontend build scripts.SmoothLHT/UI/SmoothLHT/webpack.config.js: frontend bundle config.
UI Notes
Frontend bindings:
IsShowing: boolean, controls whether the tool section is injected.IsInverted: int, currentNetInvertMode.ToggleInverted: trigger with the selected next mode.
Frontend mode constants currently used:
LEFT_HAND_TRAFFIC_MODE = 1DEFAULT_MODE = 0
The frontend appends a Section and ToolButton to the vanilla
MouseToolOptions UI. Be careful with focus keys; earlier UI logs showed
duplicate focus-key warnings when injected controls are mishandled.
As of 2026-06-03, the frontend resolves vanilla Section and ToolButton
directly through the official ModuleRegistry.get/extend APIs during
registration. Do not reintroduce a feature-local vanilla component resolver or
local SCSS for the toggle button unless the game's vanilla ToolButton can no
longer render the icon/selected state correctly.
Persistence
Persisted file:
ModsData/SmoothLHT/non_inverted_assets.jsonunder the CS2 user data path.
Model:
- The store records names of prefabs that should not be inverted.
- Missing from the set means desired mode is
NetInvertMode.LefthandTraffic. - Present in the set means desired mode is
NetInvertMode.Never. - If loading/parsing fails, the store falls back to the default non-inverted set and logs the error.
Key risk:
- Preferences are keyed by
prefab.name. If an asset author renames a prefab, the stored preference may no longer apply.
Publishing Notes
When updating publish metadata:
- Keep
ModVersion,ChangeLog, README, screenshots, and public description consistent. - Do not change
GameVersionunless the user explicitly asks for that field. - The project previously hit a Paradox/Skyve warning for bundling
Colossal.PSI.Common.dll. Game assemblies must not be shipped with the mod. Check references inSmoothLHT.csprojand use<Private>false</Private>for game assemblies.
Current known release line:
- Recent release moved to
0.5.0. - Recent changelog covered game version
1.5.7f1compatibility and the upgrade-only internal network toggle fix.
Logs And Troubleshooting
Logging policy:
- Route code through
Mod.LogEssential,Mod.LogDiagnostic, orMod.LogException; avoid directMod.log.Info/local logger calls outside the wrapper. - Release builds should only emit essential lifecycle, user action, and failure logs. Debug builds emit diagnostic logs through
[Conditional("DEBUG")]LogDiagnosticcalls. - Keep all mod diagnostics at
Infolevel. Use[ERROR]in the message/wrapper for failure severity instead of warning/error logger levels.
Useful log locations:
C:\Users\glydd\AppData\LocalLow\Colossal Order\Cities Skylines II\Logs\SmoothLHT.logC:\Users\glydd\AppData\LocalLow\Colossal Order\Cities Skylines II\Player.logC:\Users\glydd\AppData\LocalLow\Colossal Order\Cities Skylines II\Logs\SceneFlow.logC:\Users\glydd\AppData\LocalLow\Colossal Order\Cities Skylines II\Logs\UI.logC:\Users\glydd\AppData\LocalLow\Colossal Order\Cities Skylines II\Logs\Modding.log
Past observed non-SmoothLHT noise:
FindItgenerated duplicate vehicle/prop prefab IDs.RoadBuildergenerated duplicate net piece prefab IDs.SmartTransportationpreviously shippedNewtonsoft.Json.dll, an in-game assembly warning.- Base-game save/load migration can log unknown statistic prefab IDs.
When diagnosing CTDs:
- First separate native crash from managed exception.
- Look for the final meaningful managed stack before
Native Crash Reporting. - Do not blame
SmoothLHTunless its own log or stack frames point there. - Check exact timestamps across
Player.log,SceneFlow.log,Modding.log,UI.log, andSmoothLHT.log.
Reference And Research Notes
Reference repos that have been useful:
ZessonsDE/TrafficToolEssentialsbruceyboy24804/Cities2-TrafficLightsEnhancementlucarager/CS2-NetworkToolslucarager/CS2-Platterlucarager/CS2-LucaModsCommon
Local temporary clones may exist:
_tmp_tle/_tmp_tte/
These are reference-only scratch directories. Do not commit them.
Lessons already taken from references:
- Keep game assemblies non-private in project references.
- Keep publish/docs and compatibility notes explicit.
- Use structured services for scanner, store, and apply logic.
- Prefer original implementation based on observed APIs and behavior, not copied source.
- 2026-06-03 UI/backend officialization references:
https://cs2.paradoxwikis.com/Modding_Toolchainoldid 6258: UI mods use the official React project/template,index.tsxinjection points, webpack build, and--uiDeveloperModedebugger flow.https://cs2.paradoxwikis.com/Creating_UI_And_Code_Modsoldid 5526: UImod.jsonid should match the code mod target, and code/UI builds can be wired through the project file.https://cs2.paradoxwikis.com/Creating_a_Settings_Fileoldid 3723 andhttps://cs2.paradoxwikis.com/Options_UIoldid 6261: official.cocsettings exist, but SmoothLHT keeps dynamic per-prefab inversion choices in the existingModsData/SmoothLHT/non_inverted_assets.jsonfile by design.
Build And Verification
Default verification:
- Run
dotnet build SmoothLHT.slnafter C# changes. - The build also runs the frontend webpack build through the project tooling.
- A Sass legacy JS API warning from
sass-loaderis known. It is not usually a regression unless new warnings or errors appear.
Useful git checks:
git status --shortgit diff --statgit diff -- <path>
Do not stage or commit unless the user asks.
Editing Rules
- Prefer
rgandrg --filesfor search. - Use
apply_patchfor manual edits. - Do not revert user changes.
- Avoid broad refactors during release metadata or bug-fix tasks.
- Keep temporary reference repos and generated scratch files out of commits.
- Use ASCII unless the file already requires non-ASCII or user-facing text needs it.
Common User Replies
Broken pathfinding after toggling:
That is usually broken pathfinding. Bulldozing and rebuilding the building or upgrade should fix it.
Request for always-available mirror flip:
Not easily, because this mod changes the prefab, not individual building instances. It uses the game's built-in subnet inversion setting (`m_InvertWhen`), which can be tied to left-hand traffic and other game conditions. I may look into expanding this in the future, but it would make the project design quite a bit more complex.
Paradox/Skyve game-assembly warning appeal:
Hello,
The warning was caused by an older package that accidentally included `Colossal.PSI.Common.dll`. This has already been fixed in the latest release, and the game assembly is no longer included in the published package.
Could you please re-check the latest version and clear the stability warning?
Thank you.