Repository navigation
feat(objects): route art/skin edits to war3mapSkin.w3* + unit vertex tint - #33
Merged
Merged
Conversation
Custom units that set Art - Tinting Color now render with that tint in the viewport, completing the custom-skin render parity with doodads (which already carry tint). Maps to red/green/blue and only applies when the override is present; the renderer already treats an all-zero tint as "no tint" (leaves the instance default-white), so untinted units are never darkened. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HdJMLPgZf25tirfS26CobN
…d-trip) Object-Editor field edits now land in the table the Reforged World Editor would use, so a saved map round-trips through the vanilla editor without art fields collapsing into the plain war3map.w3* table. v1.0.5 taught wc3-forge to READ the war3mapSkin.w3* companion; this adds the WRITE half. The classifier is the MetaData.slk `netsafe` column (the exact mechanism the vanilla editor uses), NOT `category`: netsafe != "" && != "0" => art/skin field => war3mapSkin.w3*. Parsed into a new ObjectFieldMeta.NetSafe. Routing (routeSkinTable): a field already present on disk stays in its table (presence beats netsafe, so an old all-in-one map that keeps unam in war3map.w3u is edited in place, never migrated); a brand-new override goes to the skin table iff the field is netsafe. A custom object's art edit materializes a matching Custom row (id + baseID) in the skin companion, and deleting a custom drops its skin mirror too (restored on undo). - objects_route.go: fileFieldPresent / objectIdentity / routeSkinTable / targetModFile / readBaseOverride / putBaseOverride / putLevelOverride / markObjectDirty. - setObjectField / clearObjectField / setObjectFieldLevel / clearObjectFieldLevel now return which table they touched; undo commands + public mutators flip the matching dirty flag via markObjectDirty. Replay is deterministic (presence/netsafe are stable), so no per-command skin flag. - Session.dirtySkinMods + ensureSkinModsLocked / setSkinDirtyLocked / anySkinDirtyLocked; Save encodes war3mapSkin.w3* per dirtied kind (clean companions still ride the lossless copy-through). - 8 routing tests in objects_route_test.go (netsafe split, presence-stays-put, custom skin-row creation, clear-from-skin, leveled routing, delete mirror cleanup, save round-trip, undo/redo table fidelity). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HdJMLPgZf25tirfS26CobN
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HdJMLPgZf25tirfS26CobN
StephenSHorton
marked this pull request as ready for review
July 7, 2026 03:23
…p read
Two low-severity defects caught by an adversarial pre-ship review of the
edit-routing change:
- clearBaseOverrideFromFile / clearLevelOverrideFromFile dropped an emptied
OriginalEdit row but never an emptied Custom row. Undoing an art/skin edit on
a CUSTOM object therefore left a phantom empty {id,baseID} mirror row in the
war3mapSkin.w3* companion, so edit->undo->save was no longer a disk no-op and
the skin file fell off the lossless copy-through. Now the skin-table clear
prunes a now-empty custom mirror row; the PRIMARY custom row (the object's
definition, legitimately empty for an "inherit everything" custom) is never
dropped. Threaded via a pruneEmptyCustom flag set only for the skin call.
- SetObjectFieldLevel read the prior value per-(fourCC,level) across both tables
while routeSkinTable resolves per-fourCC, so on a map that splits one field
across tables by level the no-op/oldVal read could consult a different table
than the write, corrupting undo bookkeeping (exotic legacy input; user value
stayed correct). The leveled read now consults the routed table, matching the
base path.
Regression tests for both in objects_route_test.go.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HdJMLPgZf25tirfS26CobN
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.
What
Completes the two deferred items from the v1.0.5 Reforged-skin work (the Discord round-trip feedback):
war3mapSkin.w3*; this adds the write half so a saved map round-trips through the vanilla editor with art fields intact.uclr/uclg/uclb → Red/Green/Blue) applied in the 3D render merge (commit 5a5c807).How routing decides
The classifier is the
netsafeMetaData.slk column — the exact mechanism the vanilla editor uses — notcategory(e.g.unamis categorytextbut netsafe → skin;ucbsis categoryartbut netsafe0→ main). Parsed into a newObjectFieldMeta.NetSafe.routeSkinTable(non-allocating):Presence beats netsafe, so an older all-in-one map that keeps
unaminwar3map.w3uis edited in place and never migrated between files. A custom object's art edit materializes a matchingCustomrow (id + baseID) in the skin companion; deleting a custom drops its skin mirror too (restored on undo).Key changes
objects_route.go(new):fileFieldPresent/objectIdentity/routeSkinTable/targetModFile/readBaseOverride/putBaseOverride/putLevelOverride/markObjectDirty.setObjectField/clearObjectField/setObjectFieldLevel/clearObjectFieldLevelreturn which table they touched; undo commands + public mutators flip the matching dirty flag viamarkObjectDirty. Replay re-routes deterministically (presence/netsafe are stable) so no per-command skin flag is stored.Session.dirtySkinMods+ensureSkinModsLocked/setSkinDirtyLocked/anySkinDirtyLocked; Save encodeswar3mapSkin.w3*per dirtied kind. Clean companions still ride the lossless copy-through, so untouched skin tables round-trip byte-for-byte.Tests
8 new tests in
objects_route_test.go(all green): netsafe split, presence-stays-put, custom skin-row creation, clear-from-skin, leveled routing, delete-mirror cleanup, public Save round-trip (only skin dirtied + reload from skin table), undo/redo table fidelity.go build ./...,go vet ./..., and the fullinternal/forgesuite pass. Backend-only change (no frontend/binding deltas).🤖 Generated with Claude Code