feat(assets): wire builder into dispatch, shim, smoke, docs
build-binaries / build-binaries (pull_request) Has been cancelled
test-image / build-image (pull_request) Has been cancelled
test / test (pull_request) Has been cancelled

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 14:13:54 +02:00
co-authored by Claude Opus 4.8
parent dd01c96473
commit b3ed54a9f5
6 changed files with 2461 additions and 10 deletions
+11
View File
@@ -0,0 +1,11 @@
// Command crucible-assets is the standalone assets builder (equivalent to
// `crucible assets`). A single-token binary keeps consumer wrapper scripts simple.
package main
import (
"os"
"git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/dispatch"
)
func main() { os.Exit(dispatch.RunBuilder("assets", os.Args[1:])) }
+7
View File
@@ -22,6 +22,13 @@ aliases.
| `topdata` | `convert` | Convert between 2DA and native JSON/module formats. | | `topdata` | `convert` | Convert between 2DA and native JSON/module formats. |
| `wiki` | `build` | Render wiki page drafts from compiled topdata. | | `wiki` | `build` | Render wiki page drafts from compiled topdata. |
| `wiki` | `deploy` | Deploy generated wiki pages to NodeBB. | | `wiki` | `deploy` | Deploy generated wiki pages to NodeBB. |
| `assets` | `compile` | Compile ASCII `.mdl` models to binary in place. |
| `assets` | `convert` | Convert textures to/from NWN DDS (flips vertically). |
| `assets` | `upscale` | Upscale textures through an installed backend. |
| `assets` | `check-mdl` | Report uncompiled ASCII `.mdl` and model-name mismatches. |
| `assets` | `fix-mdl` | Lowercase `.mdl` names and rewrite model identity to match. |
| `assets` | `check-dupes` | Report runtime-name (basename) collisions across dirs. |
| `assets` | `clean-dupes` | Delete clean-tree files whose basename collides with primary. |
`crucible-depot` remains registered but unwired. It fails closed with exit `70` `crucible-depot` remains registered but unwired. It fails closed with exit `70`
and never emits placeholder artifacts. and never emits placeholder artifacts.
File diff suppressed because it is too large Load Diff
+26 -5
View File
@@ -17,6 +17,7 @@ import (
"os" "os"
"git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/app" "git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/app"
"git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/assets"
"git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/buildinfo" "git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/buildinfo"
"git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/depot" "git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/depot"
"git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/menu" "git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/menu"
@@ -103,6 +104,21 @@ var Registry = []Builder{
}, },
Wired: true, Wired: true,
}, },
{
Name: "assets",
Bin: "crucible-assets",
Summary: "compile/convert/upscale NWN assets + mdl/dupe integrity",
Commands: []Command{
{Name: "compile", Summary: "compile ASCII .mdl models to binary in place", Usage: "crucible assets compile [--nwn INSTALL] [--non-recursive] <dir>..."},
{Name: "convert", Summary: "convert textures to/from NWN DDS (flips vertically)", Usage: "crucible assets convert [--to dds|png|tga] [--backend PATH] [--non-recursive] <dir>..."},
{Name: "upscale", Summary: "upscale textures through an installed backend", Usage: "crucible assets upscale [--scale N] [--backend PATH] [--non-recursive] <dir>..."},
{Name: "check-mdl", Summary: "report uncompiled ASCII .mdl and model-name mismatches", Usage: "crucible assets check-mdl <path>..."},
{Name: "fix-mdl", Summary: "lowercase .mdl names and rewrite model identity to match", Usage: "crucible assets fix-mdl [--dry-run] <path>..."},
{Name: "check-dupes", Summary: "report runtime-name (basename) collisions across dirs", Usage: "crucible assets check-dupes <dir>..."},
{Name: "clean-dupes", Summary: "delete clean-tree files whose basename collides with primary", Usage: "crucible assets clean-dupes [--dry-run] <primary> <clean>"},
},
Wired: true,
},
{ {
Name: "hak", Name: "hak",
Bin: "crucible-hak", Bin: "crucible-hak",
@@ -375,11 +391,16 @@ func runBuilder(name string, args []string, out, errw io.Writer) int {
return exitOK return exitOK
} }
} }
if b.Name == "depot" && b.Wired { if b.Wired {
// depot parses its own subcommand (status/push/verify/get/pull) and // depot and assets are self-contained builders: they parse their own
// has its own richer exit contract (0/1/2/64/70), so it bypasses the // subcommands and own their exit contract, bypassing the
// b.Commands/delegateLegacy routing entirely. // b.Commands/delegateLegacy legacy routing entirely.
return depot.Run(args, out, errw, os.Getenv) switch b.Name {
case "depot":
return depot.Run(args, out, errw, os.Getenv)
case "assets":
return assets.Run(args, out, errw, os.Getenv)
}
} }
if !b.Wired { if !b.Wired {
// No migrated logic yet (depot): fail closed, never fake an artifact. // No migrated logic yet (depot): fail closed, never fake an artifact.
+5 -4
View File
@@ -151,6 +151,7 @@ func TestBuilderHelpIsOK(t *testing.T) {
func TestCanonicalCommandSurface(t *testing.T) { func TestCanonicalCommandSurface(t *testing.T) {
want := map[string][]string{ want := map[string][]string{
"depot": {"status", "push", "verify", "get", "pull"}, "depot": {"status", "push", "verify", "get", "pull"},
"assets": {"compile", "convert", "upscale", "check-mdl", "fix-mdl", "check-dupes", "clean-dupes"},
"hak": {"build", "manifest"}, "hak": {"build", "manifest"},
"module": {"build", "extract", "validate", "compare", "manifest"}, "module": {"build", "extract", "validate", "compare", "manifest"},
"topdata": {"validate", "build", "package", "compare", "convert"}, "topdata": {"validate", "build", "package", "compare", "convert"},
@@ -172,10 +173,10 @@ func TestRegistryCommandNamesAndAliasesAreUnambiguous(t *testing.T) {
for _, builder := range Registry { for _, builder := range Registry {
seen := map[string]bool{} seen := map[string]bool{}
for _, command := range builder.Commands { for _, command := range builder.Commands {
// depot parses its own subcommands and bypasses AppCommand routing // depot and assets parse their own subcommands and bypass AppCommand
// entirely (see the depot special-case in runBuilder), so its // routing entirely (see the self-contained-builder branch in
// Commands carry no AppCommand. // runBuilder), so their Commands carry no AppCommand.
requireAppCommand := builder.Name != "depot" requireAppCommand := builder.Name != "depot" && builder.Name != "assets"
if command.Name == "" || command.Summary == "" || command.Usage == "" || (requireAppCommand && command.AppCommand == "") { if command.Name == "" || command.Summary == "" || command.Usage == "" || (requireAppCommand && command.AppCommand == "") {
t.Errorf("%s has incomplete command metadata: %#v", builder.Name, command) t.Errorf("%s has incomplete command metadata: %#v", builder.Name, command)
} }
+1 -1
View File
@@ -18,7 +18,7 @@ bin=bin
# Keep in sync with internal/dispatch.Registry (Wired flag). # Keep in sync with internal/dispatch.Registry (Wired flag).
unwired=() unwired=()
wired=(depot hak module topdata wiki) wired=(assets depot hak module topdata wiki)
exit_of() { set +e; "$@" >/dev/null 2>&1; local c=$?; set -e; echo "${c}"; } exit_of() { set +e; "$@" >/dev/null 2>&1; local c=$?; set -e; echo "${c}"; }