Latest:7fde8f48 - CLEANUP: Punt the playlist editor down the road so we can get this fucker out the door

f620da78 - FIX: Add <--more--> tags to sites

34a2d820 - PERF: More JIT-friendly scripts/omw/music/actor.lua

81569240 - FIX: Recalculate page count when rebuilding the playlist editor to prevent ending up on invalid pages

e3a6882f - FIX: Correct sizing logic for playlist selection

7cb94d96 - FIX: Align left panel playlist page size with rendered height; flatten layout

157b4b35 - FIX: Improve playlist text sizing

8e55961f - FIX: Correct left panel sizing/overdraw, localize math functions

6fe833cc - FEAT: Responsive playlist editor via displayTier module + onViewportResized

ff5bd45b - CLEANUP: Don't need explicit nil returns!

75308b4e - TWEAK: Category tab hover via mouseMove (focusLoss for clear); click darkens via mousePress/mouseRelease split; selected tab always full-bright

16e4244a - REFACTOR: Split playlist editor left panel into standalone leftPanel module; promote left subtree from Layout to Element; also I forgot the event names for mouse events in UI

7561051a - CLEANUP: Run stylua across s3maphore Lua sources

2304e86d - FIX: Playlist editor gated on PlaylistLoader completion; left panel updates in place via name-chain

c514cb08 - REFACTOR: Build playlist editor once at load, hidden; show/hide toggle visibility

702815b5 - FIX: Playlist editor fails to load; header/track text centering

9d97e947 - FEAT: Single black background at menu transparency; keep left-panel inset, drop debug fills

40998de0 - FIX: Use MWUI constants for left-panel text colors with darkened inactive states

29421f0f - FEAT: Scale left-panel inset with vertical resolution (tighter on small, roomy on 4K)

e2cecb1c - FIX: Derive playlist editor page size from screen height instead of hardcoded 8

ccbfe2a9 - CLEANUP: Remove redundant manual pause/unpause for playlist editor

6aed3525 - FEAT: Close playlist editor on engine Interface-mode exit via UiModeChanged

bfc69df1 - FEAT: Rename playlisteditor -> playlistEditor to match camelCase require

78723f6f - FEAT: Open playlist editor as Interface mode with no vanilla windows

02567df0 - FEAT: Move playlist editor window to the Windows layer

a457f0b7 - FEAT: Wrap playlist editor input handlers in async:callback

baebfe5c - FEAT: Add category tabs and pagination to playlist editor

58c115d3 - CLEANUP: Meh I like fixed sizes better (for now)

e5f9501c - FEAT: Actually display the list of active playlists

32820293 - FEAT: Inset the playlist list box a bit

c52457c2 - FEAT: Add borders between main window elements

11536296 - CLEANUP: Use actual variable names

18891196 - FEAT: Add double borders on the outside of the window

b8cc3ca7 - CLEANUP: Migrate PlaylistEditor into a dedicated subdir because this is gonna get messy

4133d4ca - FEAT: Initial revision of the playlist editor window

12e22913 - FIX: Don't update PlaylistState when the world is paused

afa47f6b - FEAT: Track currently selected spell school + fighting stance in PlaylistState, also refactor requires in core.lua

e5eb6fe8 - FIX: I.S3maphore.state needs __pairs for view() to work on it

3ed870d9 - FEAT: Properly check fallback playlists for their activity state, AND take fallbacks of fallbacks into consideration for track selection with a max recursion depth of ten

e11fe21e - FEAT: Add isInCombat, getCombatTargets to I.S3maphore, improve annotations (again)

3adf738e - CLEANUP: Format playlistrules

80b31628 - CLEANUP: Minor privatization refactor for PlaylistRules to isolate cache-related functions from the PlaylistRules public API

b7a7d8d7 - CLEANUP: Remove a couple fields from PlaylistRules and localize them instead

2a5760cc - CLEANUP: Run stylua on the whole mod

b5d62ec8 - CLEANUP: Run formatter on util.lua

d680472c - FIX: Never try to set the metatable on ones that are already read-only

51afbac0 - CLEANUP: Major overhaul of S3maphore's interface including exporting PlaylistRules, PlaylistState, public combat target checking, and Cod3x-compatible completions

a818e5e3 - FIX: Make musicmanager throw angrily if a playlist override attempts to change priority buckets

d40f379c - CLEANUP: Refactor core.lua to use H3lp Yours3lf's state machine module

e6d04282 - CLEANUP: Switch from storing raw dynamic stats in PlaylistState to ONLY storing the normalized values

13eb9a80 - CLEANUP: Reove staticStrings entirely and remove further string metamethod lookups

ae827271 - CLEANUP: Migrate combat target polling into combatState.lua

51bcbf48 - CLEANUP: Migrate a bunch of stuff out of core.lua and into playlistState.lua and trackSelection.lua

5c697a17 - FEAT: Dehardcode the shit out of death track assignments

382df1e0 - CLEANUP: Flatten CellPresence fields back into PlaylistState for simplicity

6c3df577 - FEAT: Dehardcode killcounts in s3maphore and track them with global storage

3649154c - FIX: CellPresence elements are userdata and can't be iterated with next, t style loops

62bb33e9 - CLEANUP: Reformat/rearrange musicManager functions and switch queuedEvent.name sentinel from nil to empty string

3a554e62 - CLEANUP: Minor style nit

b4d91977 - FIX: Correct timing bug in silenceManager now that it's NOT called every frame, switch to randomGen, hoist locals

2de11be3 - CLEANUP: Allocate TargetChangeData in the SendEvent call instead of in the script body

95f2adba - CLEANUP: Remove lazy combatState construction, add waitingOnPresence guard, inline onSoundEnabledChanged

7ed0cf52 - FEAT: Replace isUnderwater with movementMode, add state change flags, restore handler chain round-robin

a0ee0eab - CLEANUP: Merge S3maphoreInitializationComplete into first call to S3maphoreUpdateCellPresence to simplify event-chaining and remove one more hardcoded SP semantic + minor allocation optimization

ee8f5175 - FIX: Correct throw in :subscribe handler as these can't be iterated with `next` loops

154208db - FEAT: Add combatTargetTagged and weatherType rule

132fef34 - FIX: weather is actually just a string now

65b5f69b - CLEANUP: Actually remove contentFileCount because it already exists on playlistState and we can minimize function calls this way

49038ccb - FEAT: Add total object count to PlaylistState

974619e5 - FEAT: Add contentFileCount rule to detect the number of objects placed by a specific mod

b1779b8e - FEAT: Add typeCount rule for checking bounded ranges of object counts. EG a rule could check if there are more than ten creatures or containers in a cell

6f70ab13 - CLEANUP: Remove redundant check in static collection

864a399a - FEAT: Add contentTag rule to support playlists based on tagging specific mods

186e469d - CLEANUP: Deploy caching helper across all rules, sort requires, examples for tagger rules

07df2d73 - CLEANUP: Add a caching helper to minimize cell caching boilerplate in playlistrules

7b950c8d - FEAT: Add cellHasTag playlist rule

d1378c76 - CLEANUP: Remove unused variable in actor.lua

e394eeed - FIX: Replace deepToString with a version that isn't completely terrible

ae4eb7b2 - CLEANUP: Minor optimization to debug logging so we don't call :get every time

c3e70aa5 - PERF: hoist gameSelf.id in combatState hot loop

a4c9c3ed - REFACTOR: gate debugLog format behind DebugEnable, strip redundant guards

5acd8fa4 - CLEANUP: Gigantic formatter pass on s3maphore

76358648 - REFACTOR: eliminate staticList, add deprecated wrappers, fix snapshot bug

37b843aa - FEAT: Add structured music metadata registry instead of bastardizing localizations

ba1fcd90 - CLEANUP: Correct old annotations for GameObject (where is that even coming from?)

8d574d40 - REWRITE: Migrate cell presence tracking to coroutine-driven global sweep

f52a49eb - CLEANUP: Actors who aren't in combat should't trigger combat cache clears

119ddf7d - CLEANUP: Remove unused vars in musicUtil

2802da7b - CLEANUP: Silence lifetime warning (fix in cod3x!!!)

c8435dff - CLEANUP: I guess I just committed a semi-broken state in actor.lua?

8cc6ff71 - CLEANUP: Rename self to gameSelf in actor.lua

8765df9e - FEAT: Massive refactor of playlistRules.lua

4ac87912 - FIX: Change combatTargetType rule semantics to match-any instead of match-all

f63a8338 - FEAT: Add a small helper to combatState to track whether an actor is currently in combat or not

9af480de - FEAT: Switch combatTargets to an array and use numeric-for loops to iterate it and remove next usage when interfacing with it

c967a3d2 - FEAT: Improve the combat event scheduler with a dynamic one that distributes capped batches between 4 and 16 to try ensuring every actor is updated every 1/3 of a second

a6e4744e - FEAT: Add settings to prevent combat music based on whether the player is actually being targeted, enemy health thresholds, and enemy level thresholds

4c8bb747 - CLEANUP: Change setting group names

ab1973cc - FIX: Making PlaylistRules read-only also makes all of the inner fields read-only and it dies lol

2b7b643d - FIX: Correct inconsistency in S3maphoreClearTargetCache eventData

30ca0dbe - CLEANUP: Add a dedicated clear function so I don't have to rewrite it 100 times for non-rubic0n users

ba42eecc - FIX: Correct annotations for PlaylistState.combatTargets

b9a61f1f - FIX: Strict read-only metatable was broken

a99f1c88 - CLEANUP: Correct variable shadowing in combatTargetMatch by renaming the per-actor result

649cf338 - CLEANUP: Remove combat state mutation from dynamicstatthreshold rule

b6f446bf - FIX: Send the object reference in S3maphoreClearTargetCache instead of the id

23d23803 - CLEANUP: Correct typo in S3maphoretypes

17cfca78 - REVERT: Actually I prefer hardcoding to 4

cd4b261b - CLEANUP: Run formatter, fix lint warning

8cad4c1f - FEAT: Instead of a hardcoded batch size, shrink dynamically according to frameTime (except when paused)

0bc3b291 - CLEANUP: Run formatter

f9b11ee7 - FIX: Route death tracks through the S3maphoreSpecialTrack event instead of playing them directly

4dfa1422 - FIX: Don't resolve playlists or update combat state if the player is dead

4a6a973c - CLEANUP: Remove unintentionally-committed log

3e99eca7 - FIX: Re-add S3maphoreTrackChanged and ensure all track changes are routed through it

5b1537b5 - FEAT: Major refactor of playlist loader to sort playlists at load-time, enabling much faster deterministic ordering of playlist resolution

aba9885b - CLEANUP: My mama said while loops are the devil

388beb50 - CLEANUP: Reformat core.lua and localize string functions

5650b3e9 - CLEANUP: Remove unnecessary check & explicit nil check

01c898d9 - CLEANUP: No explicit nil params ! ! !

a620079d - CLEANUP: Further narrow optimization of staticCollection.lua, less locals, more BRRR

a0160f4d - CLEANUP (S3maphore): Generalized and extreme refuckulation of the whole thing for performance and fanciness

8783e912 - CLEANUP: Slight refactor and rearrangement of staticCollection.lua

ce0c5458 - FEAT: Actors clear their s3maphore cache on death

dcf5bf21 - FEAT: Major overhaul of core.lua to be almost entirely event-based. Playlist resolution now no longer happens onUpdate but instead during relevant state changes such as (newly-added) hourly events, cell changes, combat state changes, and target death

5e3ff924 - CLEANUP: No explicit nil declarations ! ! !

282eca01 - CLEANUP: Formatting in musicSettings.lua

1a176642 - FIX: Copy playlists when dumping them from the interface so the originals don't become read-only

0e15a232 - CLEANUP: Run formatter, fix placement of realUpdateActorChain

fc55004b - CLEANUP: Don't process the combat chain until the coroutine loader is finished running

57eb4b69 - FEAT: Major architectural rewrite of actor.lua. Use a bounded queue to check for actors in combat

16f5d8a9 - FIX: Use the correct loop counter when updating the targets table

bc13acca - FIX: Don't reassign a new table when updating targets but instead manually kill-refill

620c1b23 - CLEANUP: Even narrower refactor of actor.lua to minimize C++ calls and early-outs

16bdec59 - FEAT: Refactor actor builtin for less allocations, lookups, and locals

9b6e6c2c - FIX: Correct non-existent import in S3maphore core

d1ed43fc - CLEANUP: Remove s3maphore dependency on non-existent H3 modules

22b8ad2a - CLEANUP: Place priority for General Dungeon playlist *above* MUSE ones

61091681 - CLEANUP: Remove interior-only filter from MUSE Dwemer and Daedric

377f668d - CLEANUP: Remove useless file

7a6b49d9 - CLEANUP(S3maphore): Localize and annotate some stuff in omw/music/actor.lua

ac02eca5 - CLEANUP: Assert `self.cell` exists in s3maphore after cell transition

73c712ea - FIX: Re-add `require` to playlistenv

90280ded - CLEANUP (s3maphore): scripts/s3/music/util is player-context

f274c998 - FIX: Actually fix it

15557613 - FIX: Add `table` to PlaylistEnv

d6a45c62 - CLEANUP: Remove isOpenMW.lua from S3maphore so it can be pulled from H3

8269960e - FIX: Add context annotations to S3maphore

a6f99997 - FIX: Correct old S3maphore playlists still utilizing native engine types

a3198dd6 - FIX: Don't require playlistPriority in playlists

7e2e185d - CLEANUP: Run formatter on s3maphore core

558618a1 - CLEANUP: Don't allocate a new closure in deepToString

e14a4aec - 7.2: What to withold

8486fecc - C-02: Documentation Is Not Optional

da6944b2 - CLEANUP: Integrate silence track handling into the onUpdate call chain

48455ca3 - FIX: When constructing an updatingSettingsTable, copy all values out of the original table and into the shadow table, if an original table was provided

9e9054c7 - CLEANUP: Improve handling in MusicManager.skipTrack

3b18afed - CLEANUP: Localize more stuff in updateCellHasCombatTargets

a9ae2546 - CLEANUP: Reduce userdata lookups in updateCellHasCombatTargets

215a5d6e - CLEANUP: Rearrange and simplify the onUpdate handler chain for readability and SPEEEEEEEEED

e485c601 - FEAT: Switch from using direct events for track change handling to the explicit handler pattern the OpenMW API uses

622d946d - FEAT: Add new reason states for playlist and track changes

f8570e5c - CLEANUP: Make a dedicated EventData table and start importing H3 modules

037b099a - CLEANUP: Shut up the LLS

8f69d9dc - CLEANUP: Further optimize door iterations by using localized squared lengths and not even bothering to execute any real code during cell transitions

7eb88b00 - FIX: Correct string replacement throw lol

845076e7 - CLEANUP: Migrate the implementation of combat target detection into the player script so that we can use the `nearby` module

41f29745 - CLEANUP: Consolidate all the parts of the global onUpdate into one big function

be876db8 - CLEANUP: Remove useless interface functions

3c0baa74 - FEAT: (Slightly) further optimize the main onFrame by integrating sound enablement checks into the dispatcher system

3a6187ff - CLEANUP: Further optimize global script with preallocated tables

c6bcea5f - CLEANUP: We can use onUpdate now since it runs when the world is paused; update variable names to reflect that and use the onUpdate handler

84b05158 - CLEANUP: Refactor since this does literally nothing else

8e32406e - CLEANUP: This is no longer necessary since cell transitions are handled by the global script

3395023f - CLEANUP: Don't do cell change tracking in the player script at all anymore and only do it in the global script

6fd1f3de - FIX: Add a unified makeReadOnly impl with better support for recursion and also allows optionally copying the table and throwing on invalid keys

8f5ac17f - FEAT: Add a deep copy function to music utils

142535f0 - FIX: Un-break the builtin playlists since they no longer rely on api specific code

4e9781f2 - FEAT: Add a new, `isExploring` setting, for explore playlists to use which allows the separate disablement of explore playlists, just like battle ones

9fc7f208 - CLEANUP: Correct type annotations for combatTargets

7986f7a2 - CLEANUP: Register the special playlist differently, so the LLS recognizes it

cb67eab1 - FIX: Since we don't immediately play music anymore, we have to return when events are queued, otherwise we play music twice when turning playback back ON after it having been disabled

1f9e2b33 - CLEANUP: Flush old vars/forceSkip earlier to ensure toggling music doesn't accidentally preserve old state

9ad98fb8 - CLEANUP: Simplify the boolean logic around switching tracks a bit

78c267f4 - CLEANUP: Only actually stream music as a final response to the S3maphoreTrackChanged eventHandler, for better compatibility

f08b902f - CLEANUP: Do cell change tracking in the global script

3e154971 - CLEANUP: Clear the queued event data after it's been emitted

3828c1ed - CLEANUP: This is no longer needed as upon death a special track plays and switches out the onFrame function

0505d091 - CLEANUP: Hoist queued events and handle music enable/disablement entirely in the async handler

7f0ee292 - FIX: Always load the special playlist as it is the only one which MUST exist

c6a5f85e - FIX: Correct a bad reference to the local playlistsTracksOrder

d4717111 - CLEANUP: So I guess that whole thing can just go away

959b2c12 - CLEANUP: So I guess we never actually return the original table then

93a75a7a - CLEANUP: Further optimize updating settings tables and fix MWSE behavior again

7f18c086 - CLEANUP: Further optimize updating settings groups and fix the MWSE behavior as well

60066f4e - FIX: Improve write behavior of updating settings tables

93dbae40 - FIX: Don't presume the existence of Explore and Battle playlists anymore

fc1b9819 - CLEANUP: Use rawget instead

c14d45db - FIX: Correct a throw when writing values to auto-updating setting tables that aren't in the settings group

541bd34c - CLEANUP: Gate all debug logs behind an explicit check that debug logging is enabled to avoid unnecessary and expensive :format calls

a944e017 - CLEANUP: There's a DebugEnable setting too

2ca5088b - FIX: Wrong variable in failed lookup error for makeStrictReadOnly __index function. Also, error on level 2

adc742b4 - FIX: Move @class annotation so the assignment on construction doesn't cause complaints

c50f9936 - CLEANUP: Fix type annotations for interrupt modes

b358cb51 - FIX: Remove fightingActors as it now only exists as a property of the PlaylistState

1f18efd2 - CLEANUP: Major refactor of the core module to remove a bunch of stuff and account for it all being in dedicated files

e3501182 - CLEANUP: Refactor PlaylistRules to not require constructor arguments and instead directly import and assign relevant stuff

c8f12793 - FIX: Un-break the silence manager by constructing the auto-settings table with a prebuilt one that includes the updateSilenceParams function instead of trying to update it after the fact which doesn't work

b57c252b - FIX: Un-break the playlist loader by importing the music manager and constructing our own Playback table

29b5d2c9 - CLEANUP: Useless comment

68b8c941 - CLEANUP: Hoist the print override into its own function

2a33023e - FEAT: Migrate MusicManager into its own file separate from the core

4a1d9c1e - FEAT: Add a dedicated file for the core settings, which is API agnostic and cacheable

962f59bb - CLEANUP: Add a dedicated file for Playlist state

4a027a83 - FIX: Correct stack overflow in makeStrictReadOnly __index function lol

c9ca6c2c - FEAT: Add some bad annotations for GameObject, which don't actually have anything

829759fe - CLEANUP: Queued event name is optional, and will now be used to determine whether to send one as the main table is cached

2246f817 - CLEANUP: Improve type annotations and also make the state change reasons enum strict read only

882e40ad - FEAT: When assigning values to auto-updating setting tables, update the associated setting group as well (OpenMW only)

1eefa94a - CLEANUP: None of these actually get used

cac75d94 - CLEANUP: Hide tes3 global somewhere

e172fc2f - FIX: But we actually have to give an MCM path

14367139 - FEAT: Make the updating settings table constructor accept an (optional) input table for better ergonomics

5968e34a - CLEANUP: This is now API agnostic

e3f12582 - FEAT: Make auto updating table constructor work in MWSE too

86675d49 - CLEANUP: Actually the playlist loader really only needs to be a single file

ed9b5602 - CLEANUP: Add a dedicated module for checking if we're in OpenMW or not and use it when needed

053d1206 - CLEANUP: Improve type annotations for read only tables, and make an updating settings table constructor

f74292cf - CLEANUP: Migrate the rest of the coroutine loader into its own file, which returns the core loader function

92052801 - CLEANUP: Refactor silence manager not to have external dependencies and be prepped for MWSE deployment

cec95881 - CLEANUP: Simplify the silenceManager setup since none of the necessary stuff is actually stateful, that whole constructor function thing was pretty dumb

eb8f2f53 - CLEANUP: Migrate the enums into their own files

e41cbaa2 - CLEANUP: Start migrating the loader into its own directory, starting with the environment

cc73405b - CLEANUP: Rename helpers to musicUtil in core

f3b43faa - CLEANUP: Seriously, fix alphabetization in musicUtil

492f6afc - CLEANUP: Alphabetize, organize, and rearrange musicUtil

fb6582d0 - CLEANUP: Make a new StrictReadOnly function and don't throw on regular read-only tables.

db828cf8 - CLEANUP: Fix the _ENV annotations as that doesn't work in Lua 5.1

d0aed0c4 - CLEANUP: Nullify the contents of the original `helpers` module and migrate it to scripts/s3/music/util

e118ca9f - CLEANUP: Switch core to use `@module` for types instead of an actual require

23845787 - FIX: Correct a throw caused by not passing a prefix to deepToString

bef3c247 - FIX: We start with 0 playlists now because the builtins are dehardcoded

450d9eec - CLEANUP: More consistent style of require statements

9612eb2a - FEAT: Add lua-based makeReadOnly for cross-platform-ness

28004034 - CLEANUP: Remove some unused vars in core

Version 0.63:8b455d46 - FEAT: Bump s3maphore version for GH St4sh

a869eabf - FEAT: Covert all template content to st4sh content. Based on old squashed commits

f432a618 - CLEANUP: Migrate to zola site structure