Mac Magenta issue and other bugs - #15
Merged
halestom15 merged 20 commits intoAug 16, 2026
Merged
Conversation
added 3 commits
August 15, 2026 02:45
Replace the win32-only guard in createSymlink/destroySymlink with a defaultTTSHomeDir() dispatch on os.platform(). Resolves the TODO comment in both functions. - macOS: ~/Library/Tabletop Simulator - Linux: ~/.local/share/Tabletop Simulator - Windows: unchanged (steam.homeDir.win32) Verified on macOS: npm start creates the TTSDevLink symlink under ~/Library/Tabletop Simulator/Saves/ pointing to ./dist correctly.
silhouetteState is saved with the game, but the silhouette objects attached to the minis are not. Loading a save made with silhouettes shown therefore leaves the state true with nothing attached, and removeAttachments() returns an empty list. clearSilhouette() then called destruct() on nil and the script errored out. Guard the three call sites (Unit_Leader, POI_Token, BombCarts) so the destruct is skipped when there is nothing to remove. No behaviour change when the attachment is present.
Oblong bases (long, snail) are longer than they are wide, so the Z offset computed from baseRadius places the SIL and LCK buttons inside the model instead of just past its edge. The real depth can only be read from the mesh, which is not loaded yet when the buttons are created, so it is read back and the two buttons are re-placed shortly after. Round bases are untouched.
ironsquadronfr-hub
force-pushed
the
isq-qol
branch
from
August 15, 2026 21:49
6fa4385 to
96e2761
Compare
The import field was wired to onEndEdit, which fires whenever the field is deselected. Clicking anywhere outside it reimported the list and cleared the zone, with no way to decline, and an empty or invalid field made _loadArmyFromJson error out on a nil value. TTS has no submit event on an InputField, and onEndEdit looks the same whether the player pressed Enter or clicked away, so the two cannot be separated there. Switching the field to MultiLineNewLine changes that: Enter no longer ends the edit, it inserts a newline, which arrives through onValueChanged as a single character appended to the text. That is now the import signal, and the newline is taken back out of the field. Pasting adds many characters at once and is never mistaken for it. Clicking away now does nothing, pressing Enter loads the list, and loading the same text twice still works, which matters when a player deletes a model by mistake and wants it back. Decoding is also defensive now, so invalid text reports itself instead of erroring. The button that opens the field now doubles as the submit, for players who do not think of pressing Enter: it reads "Import JSON" while the field is closed and "Load JSON" once there is something to load, and still closes the field when it is left empty. Its open state is tracked in the script rather than read back from the panel, because setAttribute is not applied by the time the next getAttribute runs. The field also carried a "prefferedHeight" typo, so Unity ignored it and only the minHeight was left to hold the layout together. The field grew with the pasted text and, having no minimum of its own, the button above it was squeezed down to a sliver. The attribute is spelled correctly now, the button has a minimum height, and the layout is tall enough for both.
ironsquadronfr-hub
force-pushed
the
isq-qol
branch
from
August 15, 2026 22:13
96e2761 to
31fdf10
Compare
Adds a second renderer for the range, cohesion and max-move overlays, built on real Unity Projectors instead of the original AssetBundles. It is OFF by default and changes nothing until a player turns it on with the Iron Squadron button in the bottom-right floating menu, which applies to the whole table. Why: the original overlay bundles ship shaders with no Metal variant, so they render magenta on macOS. Projectors work on every platform. The module also fixes two long-standing behaviours: cohesion stays visible and follows a figure while it is dragged, and max-move stays anchored where the move started rather than trailing the model. The renderer lives in the new !/IsqOverlays include, required after !/RangeRulers and !/Cohesion because it captures their original functions. !/IsqBridge exists because the files that reach the module are included both by Global and by object scripts, where a Global function is not directly visible. Upstream functions delegate only when the module is on, and are otherwise untouched: showRangeOnHoveredModel, showCohesionOnHoveredModel, and the toggleRangeRuler of the tokens, POI and bomb carts. Assets are committed under mod/data/mac-fallback-assets and referenced from CustomUIAssets. The base URL still points at our fork and needs to be flipped before this ships; it appears in IsqOverlays.ttslua and in CustomUIAssets. Not yet ported: the order token buttons and the max-move wiring.
changeSpeed1/2/3 clear the templates then call moveUnit() with no argument, so isDeploy is nil rather than false. The ring was spawned under a strict equality against false, so it was destroyed on every speed change and never came back, and maxMoveTemplate kept pointing at a destroyed object. Spawn it whenever the move is not a deployment, guard the destroy so a ring already removed elsewhere does not abort the rest of the cleanup, and clear the reference afterwards.
…module The optional Iron Squadron module already covered the hover hotkeys and the range button on objective, condition, POI and bomb cart tokens. The order token's own buttons were still missing, so with the module on, COHESION and RANGE drew the original rulers while everything around them drew the module's, and pressing RANGE then ATTACK left two rulers on the table at once. Two call sites now delegate, and only while the module is on: - COHESION clears before toggling on, so a ruler already drawn by the hover hotkey cannot invert the button. - RANGE and ATTACK share one entry point, and every exit from that chain (targeting, attack, clear templates) clears the module's ruler alongside the original one. The original spawnRangeRuler and clearRangeRulers are deliberately left alone on this object and only their callers adapted; overriding them crashed Tabletop Simulator on macOS whenever a figure hotkey spawned a range bundle. The maximum move ring is left with the original code on purpose. Routing it through the module gains nothing visible, since it renders the very same bundle, and its spawn runs ahead of every button the move menu creates, so a fault there costs the player FULL and START. With the module off, which is the default, every path above runs the original code unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ironsquadronfr-hub
force-pushed
the
isq-qol
branch
2 times, most recently
from
August 15, 2026 22:16
e3fe74b to
aef5fa2
Compare
Assigning an order token to a unit from the other side produced a token that looked right and did nothing, without a word to the player. update_token builds the token from two sources: the command type of the unit, and the side of the player who clicked. getEligibleUnit then looks for a mini matching both, so a token pointed at the other side's unit describes a combination no mini on the table has. It matches nothing, selectedUnitObj stays nil, and the token gets the standby button instead of ACT while every control that needs the unit quietly does nothing. Nothing raises: a search that finds no object is an ordinary outcome. Refusing the assignment keeps the existing behavior, which is the right one, and says so. Rank changes are guarded too, and there the check runs before anything is written, since promoting a unit edits the unit itself. This blocks nothing that used to work. getEligibleUnit already required the sides to match, so these tokens were inert from the moment they were assigned. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The projector took its yaw from basePos.y, which is the unit's height above the table, not baseRot.y, its facing. It was therefore always laid down at about one degree in world space and never followed the vehicle. A disc looks the same at every yaw, so this went unnoticed for years. It stops being harmless the moment the projected shape is not round, which is the case for the units on 100x175 and 100x200 bases. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The range band shown above an enemy unit is derived from a centre-to-centre distance, which then has base radii subtracted to turn it into the edge-to-edge distance the game actually uses. The enemy's radius was subtracted twice and the measuring unit's not at all. The band was therefore wrong whenever the two units sat on different bases, by the difference between the two radii: 2.4 inches between a trooper and an AAT, on bands six inches wide, which is enough to show the wrong band. templateInfo.baseRadius holds diameters, hence the halves; the file says so itself a few lines above the table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every asset URL in the mod points at cloud-3.steamusercontent.com, which now answers 403 Forbidden to every request, for existing files as much as for deleted ones. TTS receives the nginx error page instead of an image and reports "Load image failed unsupported format: UNKNOWN", so cards, mats and meshes spawn blank. Existing players do not see it: TTS caches what it downloaded months ago and never asks again. A fresh install has nothing cached and gets a table of white cards. The same /ugc/<id>/<hash>/ paths are served by steamusercontent-a.akamaihd.net, which the mod already uses for 695 of its official card faces. Fifteen files were fetched from there and compared against the local TTS cache of the cloud-3 downloads: identical, byte for byte. Of the 1530 distinct cloud-3 URLs in the repository, 803 come back to life this way and 727 are gone from both hosts, checked twice. Nothing regresses: no cloud-3 URL resolves for anyone today, so a rewritten one can only go from broken to working. The 727 that stay dead are almost entirely community homebrew cards whose authors removed their uploads. On the mod's own assets, one texture is lost - the Operative Token diffuse - along with three template meshes in StarWarsLegion.lua and one playmat in mats.json.
Same rewrite as the previous commit, kept separate because this file is community data rather than the mod's own assets, and because it repairs far less: 121 of its 897 URLs come back, 776 are gone from both hosts. Of the 93 homebrew units, one still has its card face - Resistance Trench Fighters - and three of the 210 upgrades do. Side loading this file today therefore fills the army builder with units that spawn as blank cards. The rewrite does not change that; it only stops the surviving handful from failing for the wrong reason.
npm run validate failed on main. The thirteen map entries under "objects" in
official.json are shaped {"card": {"image": ..., "back": ...}}, which is what
Shelf.ttslua reads at spawn time, but GenericObject.json described a bare
{"image", "back"} with no wrapper - a shape no file has ever used. The schema
was behind the data, so it is the schema that moves.
Two things sat next to that reader and cancelled each other out:
The back image was read into a local and then never passed on, so _spawnCard
fell back to reusing the front for both faces. It goes unnoticed because every
map card repeats its front as its back, and that in turn looks fine because
nothing could have used a different back anyway. The field works now.
The card was named from spawn.name, which does not exist: spawn is the
definition table, and the name is the key it was stored under - already this
function's parameter. Map cards spawned unnamed.
The Experimental Side Load field came pre-filled with contrib/cards/homebrew.json, so one click was enough to load it. That file's card faces, meshes and textures were uploaded to individual contributors' Steam cloud and are almost all gone: 92 of its 93 units and 207 of its 210 upgrades are unreachable, checked twice against the host that still answers. Loading it fills the army builder with units that spawn as a blank card and no model at all. The field now starts empty and the player supplies the file. The default is gone from the Lua as well, since leaving it there would have meant a click on Load still fetching the dead file behind an empty-looking field. An empty URL now says so in the chat instead of firing a request at "". The panel text follows: it describes the tool rather than advertising content, and says what the format costs - the file has to host its own images and models, and nothing survives a reload of the mod. None of that was written down anywhere, so contrib/cards/README.md now covers it: where to host assets so they outlive their author, the shape of the file, annotated unit, upgrade and command entries, and how to validate one. It also warns that a side loaded file has to carry its units inline - official.json writes them as paths to other files, but that indirection is resolved by the build, not by the loader.
The panel has four physical option slots. Every entry past the fourth was drawn
on the fourth slot's own object at position {-11, 0.3, i}, so consecutive labels
sat one local unit apart on an object scaled to 0.22 - a fifth of the 0.98 that
separates two real rows. Five options were enough to overlap; side loading
brings the faction list to ten and it came out as one unreadable stack.
Widening the gap alone could not work: correctly spaced, six extra entries run
off the panel and float over the table. So the fourth slot becomes a pager
instead - MORE (1/4), three entries per page - and the fourth slot's hack goes
away with it. Menus of four or fewer are untouched.
The factions are sorted on the way in. They come out of a keyed table in no set
order, which did not show while they all fit on one screen; spread over pages,
a faction would have moved from one page to another between loads.
ui gains clearOptions, which repaints the slots without clearing the BACK
button, and is declared before its table literal so its entries can call each
other.
Two faults met in the battlefield section, and either one alone was enough to make the deck unusable. Opening the Conditions submenu errored out and drew nothing. The choice list is eleven rows printed on the model, and nilChoices only ever prepared eleven entries, but the standard scenario has fourteen condition cards: the twelfth indexed past the end of selectionEntry and setChoiceAttributes died on it. It is the only list in the mod that overflows - objectives and deployments have ten, blitz eight, skirmish three, and no command card category more than seven. The rows cannot be spaced any other way, since they are texture rather than geometry, so a list that does not fit is paged: ten cards and a pager on the last row, which is drawn as a PREV / NEXT pair sized to half the row. Toggling a card keeps the page it was on instead of jumping back to the first. Separately, the builder started with twelve cards already selected, written out by hand, one list per scenario. All of them had drifted: none of the twelve names under "standard" exists in the standard scenario, none of the twelve under "skirmish" exists in skirmish, and blitz fell through to an empty selection. A name the scenario does not have still spawns, as an unrecognized card, so a fresh deck builder produced twelve of those alongside whatever the player had actually picked. Nothing is selected to begin with now - which is what blitz already did, and what a player expects: pick nine cards, get nine cards.
ironsquadronfr-hub
force-pushed
the
isq-qol
branch
from
August 15, 2026 22:18
aef5fa2 to
9817bfb
Compare
The global script declared onsave(), all lowercase. Tabletop Simulator only calls onSave(): unlike onload, there is no lowercase alias for it, so the function had never run and the global script had never saved anything. Any save file shows it side by side, in the same file: the clock object, which spells onSave, carries a script state; the global, which spelled onsave, carries an empty one. onload therefore always received an empty state, always read the clocks as hidden, and always took the branch that reset both clocks to 01:30:00. An undo reloads the whole mod, so that reset also fired on every rewind, and ClockPersist, which restores the saved value a frame earlier, was overwritten every single time. Initialization no longer touches the values at all: a new game already starts at 01:30:00, because that is what the two clock objects hold in the mod. While the table is loading, or once a player has left their seat, nobody should be losing time, so both clocks are stopped in those two cases and the banner is refreshed to say so. Showing the clocks now says what it wants instead of toggling whatever is on screen, which is what an initialization that can run twice requires. The banner also reports the running clock. Tabletop Simulator only ever reports whole seconds, so a once-a-second refresh slides against them and shows one second twice before skipping the next; it is read four times a second instead and written only when it reads differently. Its label is white for good now, and the paused button turned dark so white reads on all three states. Lastly, the color table behind the "on the clock" message held a single "diffuse" key, so every lookup returned nil and the message always came out white. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An undo reloads the whole mod, and the welcome dialog is declared active in the XML, so it reappeared in the middle of the game on every rewind. Declaring it closed is not enough on its own, since the reload would just open it again. The global script now remembers whether the dialog is on screen, next to the chess clock UI it already remembered, and reopens it only when the player had left it open. A fresh table is still greeted as before, and a table where the dialog was dismissed stays clear of it, through undos and reloads alike. Saves written before this have no such key and are treated as wanting the greeting. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rulebooks on the table were pinned to the versions that were current when they were added: the core rulebook to 2.5.7 of May 2024, skirmish and special operations to 2023 files. A player reading them was reading rules that have since been superseded, with nothing on the object to say so. The three PDF objects become interchangeable lecterns, and a new control panel decides what each one shows: five documents -- core rulebook, errata reference, battle forces, recon, tours of duty -- each in English and, where a translation exists, in French. The panel is a clone of the game controller's hardware, the same console mesh, label plates, square buttons and BACK, all already hosted, so nothing new had to be built. It reads like the panels already on the table because it is made of the same parts. A small box under each lectern jumps to a page. The documents are served from the Google Drive the Iron Squadron translation team maintains. Its file ids are stable: a new edition is uploaded over the existing file rather than beside it, so these links keep serving the current version with no further edits here. Three things about Tabletop Simulator shaped the implementation, all confirmed in game rather than assumed: setCustomObject does nothing on a Custom PDF. getCustomObject returns an empty table on these objects, and Custom PDF is absent from the types the API documents, so writing a url into it changes nothing and the reload that follows restores the original document. The url is read and written through getJSON and spawnObjectJSON instead. destroyObject cancels the timers of the script that calls it, so a lectern cannot respawn itself: it would kill its own spawn before it ran. The panel performs the swap. Because a respawn can hand the object a fresh guid, the lecterns are found by a mark in their GMNotes, which travels with the data that is reinjected. A button cannot be anchored to a Custom PDF. getBoundsNormalized reports a box that does not match the visible artwork, so there is no placement to hold to. That is why the panel and the page boxes are real objects. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
isqSpawnMaxMove and isqClearMaxMove were written when the overlay module was expected to take over the maximum move ring as well. It does not: routing the ring through the module was tried and withdrawn, because the block runs ahead of the buttons that moveUnit creates and FULL and START are the last two of them, so anything going wrong there takes the move controls with it. Both helpers have no callers. Leaving them in place would be worse than noise: they are a ready-made way to wire up precisely the path that was removed on purpose. The engine keeps its own maximum move functions, which are coherent within the module and reachable only from it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Based on
Mandos+MacFix, so this should merge without a rebase on your side.Six commits, each self-contained, so you can take them one by one or cherry-pick
just the ones you want.
Five of them are fixes to bugs that are in the mod right now, on every platform,
and none of them depend on our module:
clearSilhouette()crashes after a reload.silhouetteStateis saved but thesilhouettes themselves are not, so
removeAttachments()returns an empty listand
destruct()is called on nil. Three call sites: Unit_Leader, POI_Token,BombCarts.
longandsnailbases, because their Zoffset comes from
baseRadiuswhile the mesh isn't loaded yet when thebuttons are created.
onEndEdit, so clicking anywhere outside the fieldreimports the list and clears the zone, with no way to decline. Empty or
invalid text also errors out in
_loadArmyFromJson.changeSpeed1/2/3callmoveUnit()with no argument, soisDeployis nil,and the ring only spawns under
isDeploy == false.npm startdoesn't run on macOS or Linux (that's Add macOS and Linux support to the dev symlink helpers swlegion/tts#601, included here so thebranch is usable as is).
The sixth commit is the optional overlay module. It is off by default and
changes nothing until a player turns it on with a button in the floating menu,
and it only touches three upstream functions, each of which simply delegates
when the module is on. With it off, the original code path runs unchanged.
Its assets are committed under
mod/data/, so merging this puts them in yourrepository and the URL only needs repointing at yours to drop any dependency on
our fork.
Separate from this PR, and the one thing that actually needs a decision from
you: the 152 repaired bundles for the magenta rendering on macOS are ready to
download here.
https://github.com/ironsquadronfr-hub/tts/releases/tag/metal-fix-2026.08
That part can't be finished from our side. The bundles have to be uploaded to
Steam UGC from the account that publishes the mod, and the mod repointed at the
new URLs, since UGC entries are immutable. Everything else on that front is
done and tested on both platforms.
One last thing, which lives outside this branch and needs no decision: the macOS
toolkit is on swlegion#600. It holds the save patcher we developed against,
the Metal grafting chain with its inventory of every bundle the mod references,
and a small Unity kit that merges a Windows build and a macOS build of the same
shader into one bi-platform bundle. None of it is part of the mod and none of it
has to be merged - it is there so whoever meets the next macOS rendering problem
has somewhere to start. The overlay module is no longer in it: that was ported
into
mod/srcand is in this branch, with the same fifteen assets, byte forbyte, under
mod/data/isq-overlay-assets/.No rush on any of it, and happy to split things differently if that's easier.
Update, 15 August. Four more upstream fixes have been added to this branch,
all found while auditing how faithfully the mod reproduces the tabletop
distances. Every distance below was measured out of saved games, not estimated.
table rather than its facing, so it never turned with the vehicle. A disc
looks the same at every angle, which is why this went unnoticed; it stops
being harmless the moment the projected shape is not round.
twice instead of one radius from each unit. The band was therefore wrong
whenever the two units sat on different bases, by up to 2.4 in between a
trooper and an AAT, on bands six inches wide.
looked right and did nothing at all, without a word to the player. It now
refuses the assignment and says why. Nothing that used to work is blocked:
getEligibleUnitalready required both sides to match, so those tokens wereinert from the moment they were assigned.
changeSpeed1/2/3callmoveUnit()with no argument and the spawn wasguarded by
isDeploy == false, which is false whenisDeployis nil.What the audit found to be correct, for the record: range bands land within
0.02 in of 6/12/18/24/30 in from the base edge on all six round bases, and the
cohesion projectors are exactly base radius + 3 in.
The rendering side of that same audit could not be fixed here, because it lives
in asset bundles rather than in Lua. Those are ready as well:
https://github.com/ironsquadronfr-hub/tts/releases/tag/projector-fix-2026.08
28 bundles: the maximum move projectors, whose white base footprint was drawn
smaller than the base and changed with the speed; the speed 1 template collider,
whose tip overlapped the base and made the physics engine shove the miniature
back at every spawn; and the three shared assets carrying the speed tints. Same
constraint as the macOS bundles above: they have to be re-uploaded to Steam UGC
from the publishing account.
Update, 15 August (evening). Six more commits. One of them is not a polish
item: more than half the mod's assets no longer load. 1530 of the 2783
distinct asset URLs in the repository point at
cloud-3.steamusercontent.com,and that host now answers
403 Forbiddento every request, for files that stillexist as much as for files that don't. TTS receives the nginx error page instead
of an image and reports
Load image failed unsupported format: UNKNOWN, so thosecards, mats and meshes spawn blank.
It is easy to miss from the inside. TTS caches what it downloaded months ago and
never asks again, so an established player only runs into it on something their
cache never held. A fresh install has nothing cached and gets a table of white
cards.
The other 1253 URLs already point at
steamusercontent-a.akamaihd.net, whichserves the same
/ugc/<id>/<hash>/paths. Rewriting the dead host to that onebrings 803 of the 1530 back to life. That the two hosts serve the same bytes
is not an assumption: fifteen files were fetched from Akamai and compared by
SHA-256 against a local TTS cache of the old cloud-3 downloads - identical, byte
for byte. And nothing can regress, because no cloud-3 URL resolves for anyone
today, so a rewritten one only ever goes from broken to working.
The remaining 727 are gone from both hosts, checked twice. Almost all of them
are community homebrew: 92 of the 93 units and 207 of the 210 upgrades in
contrib/cards/homebrew.jsonare unreachable - card faces, meshes andtextures alike - because the uploads went away with their authors' Steam
accounts. Side loading that file today fills the army builder with units that
spawn as a blank card and no model. That is worth a decision on your side:
re-host what is left, or retire the file. On the mod's own assets the losses are
four: the Operative Token diffuse, three template meshes in
StarWarsLegion.lua, and one playmat inmats.json.The homebrew rewrite is a separate commit from the rest, so you can take one
without the other.
The other five commits are bugs found while testing that one:
The choice list is eleven rows printed on the model and
nilChoicesonly everprepared eleven entries, but the standard scenario has fourteen condition
cards: the twelfth indexed past the end of
selectionEntry. It is the onlylist in the mod that overflows. Longer lists are now paged, with a PREV / NEXT
pair on the last row; toggling a card keeps the page it was on.
by hand, one list per scenario - and all of them had drifted. None of the
twelve names under
standardexists in the standard scenario, none of thetwelve under
skirmishexists in skirmish, and blitz fell through to an emptyselection. A name the scenario does not have still spawns, as an unrecognized
card, so a fresh deck builder produced twelve of those alongside whatever the
player had actually picked. Nothing is selected to begin with now.
Options beyond the fourth were all drawn on the fourth slot's object one fifth
of a row apart, so five options were enough to overlap - and side loading
brings the list to ten. The four slots are paged now.
npm run validatefailed onmain. The thirteen map entries underobjectsinofficial.jsonare shaped{"card": {...}}, which is whatShelf.ttsluareads, butGenericObject.jsondescribed a bare{"image", "back"}with no wrapper - a shape no file has ever used, and theodd one out among its own siblings, which are all keyed by a wrapper property.
The schema now describes the data. Two things sat next to that reader and
cancelled each other out: the back image was read into a local and never
passed on, so a card always reused its front for both faces, and the card was
named from
spawn.name, which does not exist - map cards spawned unnamed.one click was enough to load the dead file. The field starts empty now, the
default is gone from the Lua as well, and an empty URL says so in the chat
instead of firing a request at
"". The panel text describes the tool ratherthan advertising content, and the tab is named for what it does.
None of that was written down anywhere, so
contrib/cards/README.mdnow coversit: where to host assets so they outlive their author, the shape of the file,
annotated unit, upgrade and command entries, and how to validate one. It also
warns that a side loaded file has to carry its units inline -
official.jsonwrites them as paths to other files, but that indirection is resolved by the
build, not by the loader.
All six were tested in game on macOS, on a save compiled from this branch.
Update, 16 August. Two more upstream fixes, both found while testing the
chess clock.
onsave(), alllowercase; Tabletop Simulator only calls
onSave(), and unlikeonloadthereis no lowercase alias. Any save file shows it in one place: the clock object,
which spells
onSave, carries a script state; the global, which spelledonsave, carries an empty one. The consequence was thatonloadalways readthe clocks as hidden and always took the branch resetting both to 01:30:00 -
and since an undo reloads the whole mod, that reset also fired on every
rewind.
ClockPersistrestores the right value a frame earlier and wasoverwritten every time, so it had been dead code since it was added. The
clocks also stop on their own while the table loads and when a player leaves
their seat.
XML. Whether it is on screen is now remembered alongside the chess clock UI,
so a dismissed dialog stays dismissed.
One addition rather than a fix, the chess clock banner now shows the running
clock, counting down.
The
projector-fix-2026.08package was rebuilt the same night. The three 27 mmmaximum move bundles were 2.21 mm short on the table:
deployMod.smallhad beenadded to the material but not to the projector's extent, and it is the extent
that places the ring. Both carry it now, and all 24 were re-checked against the
measured distances. The package also gained a small
unity-source/folder, sothe shader work behind these bundles is not lost the day one of them is rebuilt.
Both were tested in game on macOS, on a save compiled from this branch.
One thing to change when you merge. The optional overlay module loads its 15
assets over HTTP, and the two places that hold the base URL still point at the
branch this pull request comes from:
local ISQ_ASSETSinmod/src/includes/IsqOverlays.ttslua, and theisqLogoentry of
CustomUIAssetsinmod/src/StarWarsLegion.json. The files themselvesare in this pull request, under
mod/data/isq-overlay-assets/, so once it ismerged they exist in your repository too and the two URLs should be repointed at
it. Left as they are, your mod would be fetching them from a branch on our fork,
which is ours to delete.