Model compilation contract
This commit is contained in:
@@ -0,0 +1,770 @@
|
|||||||
|
# Model Compilation Build-Time Contract
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Expand the toolkit so authored ASCII Neverwinter Nights `.mdl` assets can be
|
||||||
|
compiled into binary `.mdl` build artifacts during `sow-toolkit build-haks`
|
||||||
|
without changing the authored source tree.
|
||||||
|
|
||||||
|
This contract covers:
|
||||||
|
|
||||||
|
- `toolkit/` as the implementation owner
|
||||||
|
- `sow-assets/` as the primary consumer repository
|
||||||
|
- any future consumer repo that packages NWN model assets through the same HAK
|
||||||
|
build pipeline
|
||||||
|
|
||||||
|
This contract does not cover:
|
||||||
|
|
||||||
|
- replacing authored ASCII `.mdl` files in source control
|
||||||
|
- introducing a separate standalone shell-only build system as the primary path
|
||||||
|
- downloading or redistributing copyrighted NWN game data
|
||||||
|
- building an in-game runtime compiler workflow
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Make model compilation a first-class, deterministic, configurable build stage in
|
||||||
|
the Go toolkit so generated HAKs contain compiled binary models while authored
|
||||||
|
repositories continue to keep ASCII `.mdl` files as source of truth.
|
||||||
|
|
||||||
|
## Why This Exists
|
||||||
|
|
||||||
|
Repo-local context already shows that ASCII `.mdl` files are an intentional part
|
||||||
|
of authored asset workflows:
|
||||||
|
|
||||||
|
- validation reports ASCII/decompiled models as informational, not erroneous
|
||||||
|
- topdata autogen manifest generation scans authored `.mdl` paths directly
|
||||||
|
- part discovery derives IDs from authored `.mdl` filenames
|
||||||
|
- HAK building already has other generated-asset stages such as NWScript and
|
||||||
|
music conversion
|
||||||
|
|
||||||
|
Upstream NWN documentation also supports compiling before release:
|
||||||
|
|
||||||
|
- `nwn.wiki` documents that ASCII models are slower because the game compiles
|
||||||
|
them at load time and recommends shipping compiled models
|
||||||
|
- `nwnmdlcomp` usage docs confirm ASCII-to-binary compilation as a standard flow
|
||||||
|
- community documentation shows `nwnmdlcomp.exe` expects old NWN registry/data
|
||||||
|
layout and may need a 1.69-era data root
|
||||||
|
|
||||||
|
Therefore the correct implementation is not “replace asset scanning with binary
|
||||||
|
models everywhere”. The correct implementation is:
|
||||||
|
|
||||||
|
1. keep authored ASCII `.mdl` in `paths.assets`
|
||||||
|
2. preserve current validation and manifest derivation behavior against source
|
||||||
|
3. compile only the build payload that becomes generated HAK content
|
||||||
|
|
||||||
|
## Ownership Split
|
||||||
|
|
||||||
|
- `toolkit/`
|
||||||
|
- owns config schema, validation, discovery, cache policy, build staging, and
|
||||||
|
HAK integration
|
||||||
|
- owns Wine execution and NWN data preparation logic
|
||||||
|
- owns tests and user-facing CLI behavior
|
||||||
|
- `sow-assets/`
|
||||||
|
- owns opting into the feature through `nwn-tool.yaml`
|
||||||
|
- owns providing local tool binaries and any fake/minimal NWN data tree it
|
||||||
|
wants to use
|
||||||
|
- owns wrapper scripts and CI invocation details
|
||||||
|
- `sow-module/`
|
||||||
|
- is an indirect consumer only when it consumes generated HAKs
|
||||||
|
- must not need authored-source changes for this feature to work
|
||||||
|
|
||||||
|
## Current Project-Needs Analysis
|
||||||
|
|
||||||
|
## Existing architecture
|
||||||
|
|
||||||
|
Current HAK packaging is Go-native and centered on:
|
||||||
|
|
||||||
|
- `cmd/nwn-tool/main.go`
|
||||||
|
- `internal/app/app.go`
|
||||||
|
- `internal/pipeline/build.go`
|
||||||
|
- `internal/project/*`
|
||||||
|
- `internal/validator/*`
|
||||||
|
|
||||||
|
`build-haks` already does all asset discovery, planning, manifest writing, HAK
|
||||||
|
reuse, and generated-asset preparation inside the toolkit. Adding model
|
||||||
|
compilation as a disconnected shell script would duplicate the existing build
|
||||||
|
graph and create drift.
|
||||||
|
|
||||||
|
## Existing relevant behavior
|
||||||
|
|
||||||
|
- `project.AssetExtensions` already includes `.mdl`
|
||||||
|
- `validator.ValidateProject` detects text `.mdl` and reports them via
|
||||||
|
`Report.DecompiledModels`
|
||||||
|
- `emitValidatorReport` already surfaces that information in CLI output
|
||||||
|
- `collectAssetResources` packages raw files from `paths.assets` directly into
|
||||||
|
HAK resources
|
||||||
|
- `BuildHAKs` already supports generated assets and temporary staging through the
|
||||||
|
music pipeline
|
||||||
|
- script compilation already establishes a precedent for:
|
||||||
|
- config-backed tool resolution
|
||||||
|
- environment discovery
|
||||||
|
- cache directories under `.cache`
|
||||||
|
- build-time generated resources that do not modify authored sources
|
||||||
|
|
||||||
|
## Constraints the plan must preserve
|
||||||
|
|
||||||
|
- authored `.mdl` files remain canonical inputs
|
||||||
|
- autogen and topdata logic must continue to see source `.mdl` paths
|
||||||
|
- validation must still be able to detect ASCII source models
|
||||||
|
- HAK duplicate detection and manifest generation must remain deterministic
|
||||||
|
- build output reuse must not falsely reuse HAKs built from stale compiled model
|
||||||
|
content
|
||||||
|
- consumer repos must be able to opt out cleanly
|
||||||
|
|
||||||
|
## Architecture Decision
|
||||||
|
|
||||||
|
Implement model compilation as a generated-asset overlay inside the existing HAK
|
||||||
|
build pipeline, not as a separate top-level shell build.
|
||||||
|
|
||||||
|
That means:
|
||||||
|
|
||||||
|
- source scanning still indexes authored `.mdl`
|
||||||
|
- a model preparation stage produces compiled build artifacts in cache/staging
|
||||||
|
- `collectAssetResources` substitutes compiled `.mdl` bytes for eligible source
|
||||||
|
models during HAK packaging
|
||||||
|
- non-model assets continue to flow unchanged
|
||||||
|
- the written HAK manifest still records the authored relative asset path, not a
|
||||||
|
cache path
|
||||||
|
|
||||||
|
This mirrors the existing music pipeline more than the script pipeline:
|
||||||
|
|
||||||
|
- source asset exists in `paths.assets`
|
||||||
|
- generated build artifact lives in cache/staging
|
||||||
|
- HAK includes the generated bytes under the original logical resource name
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
- no automatic decompilation
|
||||||
|
- no mutation of files under `paths.assets`
|
||||||
|
- no requirement to compile every `.mdl` in every repo by default
|
||||||
|
- no requirement to support Windows-only shell wrappers as the primary interface
|
||||||
|
- no requirement to support arbitrary model compilers on day one beyond
|
||||||
|
`nwnmdlcomp.exe` via Wine
|
||||||
|
- no attempt to “fix” malformed source models automatically
|
||||||
|
|
||||||
|
## Config Contract
|
||||||
|
|
||||||
|
Add a new top-level config section:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
models:
|
||||||
|
compile:
|
||||||
|
enabled: true
|
||||||
|
source_extensions: [.mdl]
|
||||||
|
include: []
|
||||||
|
exclude: []
|
||||||
|
cache: "{paths.cache}/models"
|
||||||
|
compiler:
|
||||||
|
path: ""
|
||||||
|
env:
|
||||||
|
path: SOW_NWN_MODEL_COMPILER
|
||||||
|
wine: SOW_NWN_WINE
|
||||||
|
winepath: SOW_NWN_WINEPATH
|
||||||
|
prefix: SOW_NWN_WINEPREFIX
|
||||||
|
nwn_data: [SOW_NWN_MODEL_DATA, SOW_NWN_ROOT, NWN_ROOT]
|
||||||
|
search:
|
||||||
|
- "{paths.tools}/nwnmdlcomp/nwnmdlcomp.exe"
|
||||||
|
- "{paths.tools}/nwnmdlcomp.exe"
|
||||||
|
runtime:
|
||||||
|
mode: wine
|
||||||
|
jobs: 1
|
||||||
|
force: false
|
||||||
|
setup_registry: true
|
||||||
|
compiler_cwd: "{paths.tools}/nwnmdlcomp"
|
||||||
|
data:
|
||||||
|
root: ""
|
||||||
|
require_keys: [chitin.key, nwn_base.key, data/nwn_base.key]
|
||||||
|
writable_aliases: [chitin.key, nwn_base.key]
|
||||||
|
policy:
|
||||||
|
compile_ascii_only: true
|
||||||
|
preserve_binary_sources: true
|
||||||
|
fail_on_missing_output: true
|
||||||
|
fail_on_zero_byte_output: true
|
||||||
|
fail_on_warning: false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Config design rules
|
||||||
|
|
||||||
|
- `models.compile.enabled` must default to `false`
|
||||||
|
- the feature must be opt-in
|
||||||
|
- config must be repository-driven, not hardcoded per consumer
|
||||||
|
- search/discovery semantics should match `scripts.compiler` where practical
|
||||||
|
- path templating should reuse the existing effective config path expansion model
|
||||||
|
- include/exclude filters apply to asset-relative paths under `paths.assets`
|
||||||
|
- default jobs should be serial unless parallel execution is explicitly enabled
|
||||||
|
|
||||||
|
## Environment precedence
|
||||||
|
|
||||||
|
Use this precedence for the compiler executable:
|
||||||
|
|
||||||
|
1. `models.compile.compiler.path`
|
||||||
|
2. env var named by `models.compile.compiler.env.path`
|
||||||
|
3. configured search paths
|
||||||
|
|
||||||
|
Use this precedence for NWN data root:
|
||||||
|
|
||||||
|
1. `models.compile.data.root`
|
||||||
|
2. env vars listed in `models.compile.compiler.env.nwn_data`
|
||||||
|
3. no implicit auto-discovery unless explicitly implemented and documented
|
||||||
|
|
||||||
|
Wine tools should resolve as:
|
||||||
|
|
||||||
|
1. env override for `wine`
|
||||||
|
2. `PATH:wine`
|
||||||
|
|
||||||
|
and:
|
||||||
|
|
||||||
|
1. env override for `winepath`
|
||||||
|
2. `PATH:winepath`
|
||||||
|
|
||||||
|
Do not silently guess a fake NWN data tree.
|
||||||
|
|
||||||
|
## Build Contract
|
||||||
|
|
||||||
|
## Eligibility rules
|
||||||
|
|
||||||
|
A model is eligible for build-time compilation when all of the following are
|
||||||
|
true:
|
||||||
|
|
||||||
|
- asset extension is `.mdl`
|
||||||
|
- `models.compile.enabled` is true
|
||||||
|
- path passes include/exclude filters
|
||||||
|
- file is detected as ASCII when `compile_ascii_only` is true
|
||||||
|
|
||||||
|
If a `.mdl` file is already binary and `preserve_binary_sources` is true:
|
||||||
|
|
||||||
|
- do not recompile it
|
||||||
|
- package it as-is
|
||||||
|
- optionally log a verbose skip reason
|
||||||
|
|
||||||
|
## Integration point
|
||||||
|
|
||||||
|
Insert model preparation into `planOrBuildHAKs` before `collectAssetResources`
|
||||||
|
finalizes HAK resource content, alongside other generated-asset preparation.
|
||||||
|
|
||||||
|
Expected flow:
|
||||||
|
|
||||||
|
1. validate project
|
||||||
|
2. prepare music assets if enabled
|
||||||
|
3. prepare compiled model assets if enabled
|
||||||
|
4. collect asset resources using source inventory plus prepared overlays
|
||||||
|
5. plan/write HAKs as today
|
||||||
|
|
||||||
|
## Prepared asset representation
|
||||||
|
|
||||||
|
Introduce a prepared-model result similar in spirit to `preparedMusicAssets`,
|
||||||
|
for example:
|
||||||
|
|
||||||
|
- generated assets keyed by original asset-relative path
|
||||||
|
- skipped source-relative paths
|
||||||
|
- summary counts and loggable actions
|
||||||
|
- cleanup hook for temporary staging if needed
|
||||||
|
|
||||||
|
The HAK layer should continue to see:
|
||||||
|
|
||||||
|
- resource name from the original source path stem
|
||||||
|
- resource type `mdl`
|
||||||
|
- relative manifest path equal to the original source path
|
||||||
|
|
||||||
|
Only the file bytes should differ.
|
||||||
|
|
||||||
|
## Cache and staging contract
|
||||||
|
|
||||||
|
Compiled models are generated artifacts and must live outside the authored asset
|
||||||
|
tree.
|
||||||
|
|
||||||
|
Default cache root:
|
||||||
|
|
||||||
|
```text
|
||||||
|
{paths.cache}/models
|
||||||
|
```
|
||||||
|
|
||||||
|
Suggested cache layout:
|
||||||
|
|
||||||
|
```text
|
||||||
|
.cache/models/
|
||||||
|
compiled/<asset-relative-path>.mdl
|
||||||
|
logs/<asset-relative-path>.log
|
||||||
|
metadata.json
|
||||||
|
wine-prefix/ # only if the repo chooses a toolkit-managed prefix
|
||||||
|
nwn-data-links/ # only if alias files/symlinks are staged
|
||||||
|
```
|
||||||
|
|
||||||
|
The exact layout may differ, but it must support:
|
||||||
|
|
||||||
|
- deterministic mapping from source asset path to compiled output path
|
||||||
|
- per-model logging for failures
|
||||||
|
- incremental stale detection
|
||||||
|
- cleanup without touching authored content
|
||||||
|
|
||||||
|
## Incremental contract
|
||||||
|
|
||||||
|
The plan must support incremental recompilation for persistent cache dirs.
|
||||||
|
|
||||||
|
At minimum, recompile when any of these change:
|
||||||
|
|
||||||
|
- source file content hash changed
|
||||||
|
- source file modtime is newer than compiled output
|
||||||
|
- compiler executable path or content fingerprint changed
|
||||||
|
- NWN data root fingerprint changed if the implementation tracks it
|
||||||
|
- relevant config changed
|
||||||
|
- `--force` or config `force: true` is active
|
||||||
|
- compiled output is missing or zero bytes
|
||||||
|
|
||||||
|
Recommended metadata key:
|
||||||
|
|
||||||
|
- source asset relative path
|
||||||
|
- source SHA-256
|
||||||
|
- output SHA-256
|
||||||
|
- compiler path
|
||||||
|
- compiler binary mtime or hash
|
||||||
|
- NWN data root path
|
||||||
|
- build config fingerprint
|
||||||
|
- compiled timestamp
|
||||||
|
|
||||||
|
If metadata is unavailable or invalid, fail open to recompilation, not reuse.
|
||||||
|
|
||||||
|
## NWN Data Contract
|
||||||
|
|
||||||
|
`nwnmdlcomp.exe` requires a classic NWN data/registry environment.
|
||||||
|
|
||||||
|
The implementation must:
|
||||||
|
|
||||||
|
- validate the configured NWN data root exists
|
||||||
|
- require at least one of:
|
||||||
|
- `chitin.key`
|
||||||
|
- `nwn_base.key`
|
||||||
|
- `data/nwn_base.key`
|
||||||
|
- never download game data
|
||||||
|
- never synthesize copyrighted content
|
||||||
|
|
||||||
|
If only `data/nwn_base.key` exists, the implementation may create a staged alias
|
||||||
|
view for the compiler by:
|
||||||
|
|
||||||
|
- symlinking inside a cache-owned staging dir, or
|
||||||
|
- copying just the key file names into a cache-owned staging dir when symlinks
|
||||||
|
are unavailable
|
||||||
|
|
||||||
|
Do not mutate the user’s real install directory unless they explicitly pointed
|
||||||
|
the config at a writable fake data tree and the implementation clearly documents
|
||||||
|
that behavior.
|
||||||
|
|
||||||
|
## Wine Registry Contract
|
||||||
|
|
||||||
|
When `runtime.setup_registry` is enabled, prepare the Wine registry keys needed
|
||||||
|
by `nwnmdlcomp.exe`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HKLM\Software\WOW6432Node\BioWare\NWN\Neverwinter
|
||||||
|
Location = <wine path>
|
||||||
|
Path = <wine path>
|
||||||
|
Version = 1.69
|
||||||
|
GUID = 00000000-0000-0000-0000-000000000000
|
||||||
|
Language = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Implementation rules:
|
||||||
|
|
||||||
|
- convert Linux paths with `winepath -w`
|
||||||
|
- target the Wine prefix selected by config/env
|
||||||
|
- make registry setup idempotent
|
||||||
|
- separate “prepare registry” failure messages from “compile model” failures
|
||||||
|
|
||||||
|
The implementation may either:
|
||||||
|
|
||||||
|
- manage a dedicated toolkit prefix, or
|
||||||
|
- use a user-provided existing prefix
|
||||||
|
|
||||||
|
The contract should prefer an isolated prefix to avoid polluting unrelated Wine
|
||||||
|
state.
|
||||||
|
|
||||||
|
## Compiler Invocation Contract
|
||||||
|
|
||||||
|
Primary supported compiler:
|
||||||
|
|
||||||
|
- `nwnmdlcomp.exe`
|
||||||
|
|
||||||
|
Expected invocation shape:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
WINEDEBUG=-all wine "$COMPILER" -c "$INPUT_WIN" "$OUTPUT_WIN" -e
|
||||||
|
```
|
||||||
|
|
||||||
|
Implementation requirements:
|
||||||
|
|
||||||
|
- invoke through `os/exec`, not shell string concatenation
|
||||||
|
- convert input/output paths with `winepath -w`
|
||||||
|
- allow compiler working directory configuration if supermodel resolution or
|
||||||
|
relative lookup requires it
|
||||||
|
- capture combined stdout/stderr per compile attempt
|
||||||
|
|
||||||
|
## Supermodel and sibling dependency considerations
|
||||||
|
|
||||||
|
The plan must explicitly account for model compilation not being a pure
|
||||||
|
single-file transform.
|
||||||
|
|
||||||
|
Potential dependencies include:
|
||||||
|
|
||||||
|
- `setsupermodel` references
|
||||||
|
- sibling helper models in the same asset family
|
||||||
|
- textures or material references that may not block compilation but do matter
|
||||||
|
for diagnostics
|
||||||
|
|
||||||
|
Minimum contract:
|
||||||
|
|
||||||
|
- compile against a staging layout that preserves original relative structure
|
||||||
|
- ensure each model’s directory context exists in staging
|
||||||
|
- document that some supermodel cases may require staging additional authored
|
||||||
|
`.mdl` siblings into the compiler-visible workspace
|
||||||
|
|
||||||
|
Do not assume “compile one file in isolation” is always sufficient.
|
||||||
|
|
||||||
|
## Staging strategy
|
||||||
|
|
||||||
|
Preferred strategy:
|
||||||
|
|
||||||
|
1. keep source inventory rooted in `paths.assets`
|
||||||
|
2. materialize a compiler workspace under cache that mirrors required relative
|
||||||
|
directories for eligible `.mdl`
|
||||||
|
3. compile outputs into a sibling compiled tree
|
||||||
|
4. package compiled output bytes while preserving source-relative logical names
|
||||||
|
|
||||||
|
This avoids:
|
||||||
|
|
||||||
|
- polluting source trees
|
||||||
|
- output collisions between models with the same basename in different folders
|
||||||
|
- broken relative assumptions inside asset packs
|
||||||
|
|
||||||
|
## Logging Contract
|
||||||
|
|
||||||
|
Progress output should fit existing `ProgressFunc` style.
|
||||||
|
|
||||||
|
Minimum messages:
|
||||||
|
|
||||||
|
- `Preparing model compiler...`
|
||||||
|
- `Preparing model compilation workspace...`
|
||||||
|
- `Compiling model 12/324: part/belt/pfa0_belt018.mdl`
|
||||||
|
- `Reusing compiled model: vfxs/head_accessories/hfx_bandana.mdl`
|
||||||
|
- `Skipping binary model source: creatures/foo.mdl`
|
||||||
|
- `FAILED model compile: placeables/broken_model.mdl`
|
||||||
|
|
||||||
|
At summary level, surface:
|
||||||
|
|
||||||
|
- models considered
|
||||||
|
- compiled
|
||||||
|
- reused
|
||||||
|
- skipped binary
|
||||||
|
- skipped filtered
|
||||||
|
- failed
|
||||||
|
|
||||||
|
Verbose/debug output may include:
|
||||||
|
|
||||||
|
- chosen compiler path
|
||||||
|
- chosen Wine/Wineprefix/Winepath tools
|
||||||
|
- chosen NWN data root
|
||||||
|
- per-model log file location
|
||||||
|
|
||||||
|
## Error Handling Contract
|
||||||
|
|
||||||
|
Model compilation is a build-critical stage once enabled.
|
||||||
|
|
||||||
|
The build must fail when an eligible model compile:
|
||||||
|
|
||||||
|
- exits non-zero
|
||||||
|
- does not produce the expected output file
|
||||||
|
- produces a zero-byte output file
|
||||||
|
- cannot be staged or fingerprinted
|
||||||
|
|
||||||
|
Warnings from the compiler do not fail the build unless future config enables
|
||||||
|
that policy.
|
||||||
|
|
||||||
|
Failure output must include:
|
||||||
|
|
||||||
|
- original source-relative asset path
|
||||||
|
- compiler command context sufficient for debugging
|
||||||
|
- log file path if written
|
||||||
|
|
||||||
|
At the end of a failed run, include a concise failed-path summary.
|
||||||
|
|
||||||
|
## CLI Contract
|
||||||
|
|
||||||
|
The canonical user-facing entrypoint remains:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sow-toolkit build-haks
|
||||||
|
```
|
||||||
|
|
||||||
|
Add targeted flags only if they materially improve operability and match
|
||||||
|
existing CLI patterns. Recommended flags:
|
||||||
|
|
||||||
|
- `build-haks --force-models`
|
||||||
|
- `build-haks --skip-models`
|
||||||
|
- `build-haks --model <glob-or-prefix>` optional, only if there is a real use
|
||||||
|
case for scoped rebuilds
|
||||||
|
|
||||||
|
Flag rules:
|
||||||
|
|
||||||
|
- flags override config for the current invocation only
|
||||||
|
- `--skip-models` must bypass model preparation even if config is enabled
|
||||||
|
- `--force-models` must invalidate incremental reuse for model outputs only
|
||||||
|
|
||||||
|
Do not introduce a parallel standalone command as the only way to use the
|
||||||
|
feature. A helper/diagnostic subcommand is acceptable later, but build-haks must
|
||||||
|
own the real integration.
|
||||||
|
|
||||||
|
## Validation Contract
|
||||||
|
|
||||||
|
Current validation already reports authored ASCII `.mdl` as informational.
|
||||||
|
That behavior should remain, but the plan should extend validation to cover model
|
||||||
|
compilation readiness when the feature is enabled.
|
||||||
|
|
||||||
|
Recommended validation additions:
|
||||||
|
|
||||||
|
- compiler path resolves or search candidates exist
|
||||||
|
- `wine` and `winepath` are resolvable on non-Windows platforms when mode is
|
||||||
|
`wine`
|
||||||
|
- NWN data root exists and has required keys
|
||||||
|
- cache path is safe and repo-relative unless explicitly documented otherwise
|
||||||
|
- include/exclude glob config is well-formed
|
||||||
|
- jobs is >= 1
|
||||||
|
|
||||||
|
Validation severity:
|
||||||
|
|
||||||
|
- config/discovery failures that make enabled compilation impossible should be
|
||||||
|
errors
|
||||||
|
- authored ASCII `.mdl` detection remains info
|
||||||
|
- binary `.mdl` sources alongside enabled compilation are not errors
|
||||||
|
|
||||||
|
## Manifest And Reuse Contract
|
||||||
|
|
||||||
|
Generated `haks.json` must continue to describe authored asset-relative paths,
|
||||||
|
not cache file paths.
|
||||||
|
|
||||||
|
HAK reuse logic must account for compiled model output bytes. Reuse must not
|
||||||
|
mistakenly treat an unchanged authored path as unchanged content if the compiled
|
||||||
|
artifact changed.
|
||||||
|
|
||||||
|
That means one of:
|
||||||
|
|
||||||
|
- the existing asset `ContentID` for `.mdl` must reflect compiled output hash
|
||||||
|
when compilation is enabled, or
|
||||||
|
- chunk hashing must incorporate the prepared compiled output fingerprint
|
||||||
|
|
||||||
|
Without this, stale HAK reuse would be incorrect.
|
||||||
|
|
||||||
|
## Parallelism Contract
|
||||||
|
|
||||||
|
Support configurable parallel compilation with conservative defaults.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- default jobs is `1`
|
||||||
|
- jobs > 1 must not corrupt outputs or logs
|
||||||
|
- each compile must write to a unique output path and unique log path
|
||||||
|
- shared registry/prefix setup must happen before parallel work or under safe
|
||||||
|
synchronization
|
||||||
|
|
||||||
|
If `nwnmdlcomp.exe` or Wine proves unstable under parallel execution:
|
||||||
|
|
||||||
|
- keep serial default
|
||||||
|
- document the risk
|
||||||
|
- allow opting into higher parallelism with clear caveats
|
||||||
|
|
||||||
|
## Testing Contract
|
||||||
|
|
||||||
|
Implementation is not complete without automated tests. Use fake executables and
|
||||||
|
temporary directories rather than requiring a real Wine/NWN environment in unit
|
||||||
|
tests.
|
||||||
|
|
||||||
|
## Unit tests
|
||||||
|
|
||||||
|
Add tests for:
|
||||||
|
|
||||||
|
- config loading and effective config defaults for `models.compile`
|
||||||
|
- compiler path/env/search resolution
|
||||||
|
- Wine tool resolution
|
||||||
|
- NWN data root validation and alias handling
|
||||||
|
- ASCII-vs-binary `.mdl` eligibility logic
|
||||||
|
- include/exclude filtering
|
||||||
|
- incremental reuse decisions
|
||||||
|
- metadata invalidation on source/config/compiler changes
|
||||||
|
- content hashing based on compiled output, not source text alone
|
||||||
|
|
||||||
|
## Pipeline tests
|
||||||
|
|
||||||
|
Add or extend `internal/pipeline/pipeline_test.go` coverage for:
|
||||||
|
|
||||||
|
- HAK build packages compiled `.mdl` bytes instead of source ASCII bytes
|
||||||
|
- non-`.mdl` assets remain unchanged
|
||||||
|
- binary source `.mdl` passes through unchanged
|
||||||
|
- failed model compile aborts build
|
||||||
|
- missing output aborts build
|
||||||
|
- zero-byte output aborts build
|
||||||
|
- `--skip-models` bypasses compilation
|
||||||
|
- `--force-models` rebuilds compiled outputs
|
||||||
|
- unchanged compiled output allows HAK reuse
|
||||||
|
- changed compiled output forces HAK rewrite
|
||||||
|
- duplicate asset collision rules still behave as before
|
||||||
|
|
||||||
|
## Validator tests
|
||||||
|
|
||||||
|
Keep and extend tests proving:
|
||||||
|
|
||||||
|
- ASCII source models are still reported as informational
|
||||||
|
- enabled compilation readiness errors surface when config is invalid
|
||||||
|
|
||||||
|
## Integration/manual acceptance tests
|
||||||
|
|
||||||
|
Document real-world manual checks for a workstation with Wine and
|
||||||
|
`nwnmdlcomp.exe` available.
|
||||||
|
|
||||||
|
### Test 1: readiness validation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sow-toolkit validate
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
|
||||||
|
- no model-compiler readiness errors when config/env are correct
|
||||||
|
- ASCII `.mdl` info line still appears for source models
|
||||||
|
|
||||||
|
### Test 2: single-model HAK packaging
|
||||||
|
|
||||||
|
Given an authored ASCII model under `paths.assets`, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sow-toolkit build-haks --force-models
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
|
||||||
|
- build succeeds
|
||||||
|
- resulting HAK contains a binary `.mdl`
|
||||||
|
- authored source file remains unchanged
|
||||||
|
|
||||||
|
### Test 3: incremental reuse
|
||||||
|
|
||||||
|
Run `build-haks` twice without changes.
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
|
||||||
|
- second run reports model reuse
|
||||||
|
- unchanged HAKs are reused when manifest/chunk fingerprints match
|
||||||
|
|
||||||
|
### Test 4: source change invalidation
|
||||||
|
|
||||||
|
Edit one ASCII `.mdl` source and rebuild.
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
|
||||||
|
- only affected compiled output is rebuilt
|
||||||
|
- affected HAK chunk is rewritten
|
||||||
|
- unrelated HAK chunks remain reusable
|
||||||
|
|
||||||
|
### Test 5: missing NWN data
|
||||||
|
|
||||||
|
Misconfigure `models.compile.data.root` and run build.
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
|
||||||
|
- validation/build fails clearly before or during preparation
|
||||||
|
- error names the missing key/data requirement
|
||||||
|
|
||||||
|
## Documentation Contract
|
||||||
|
|
||||||
|
Update `README.md` to include:
|
||||||
|
|
||||||
|
- why compiled models are shipped even though ASCII remains authored source
|
||||||
|
- the new `models.compile` config section
|
||||||
|
- required local dependencies:
|
||||||
|
- `nwnmdlcomp.exe`
|
||||||
|
- `wine`
|
||||||
|
- `winepath`
|
||||||
|
- NWN data root
|
||||||
|
- environment override behavior
|
||||||
|
- cache/staging behavior
|
||||||
|
- `build-haks` flags related to model compilation
|
||||||
|
- limitations and troubleshooting notes
|
||||||
|
|
||||||
|
Optional but recommended:
|
||||||
|
|
||||||
|
- `tools/nwnmdlcomp/README.md` for consumer repo operators
|
||||||
|
|
||||||
|
## Suggested Implementation Breakdown
|
||||||
|
|
||||||
|
1. Schema and config foundation
|
||||||
|
- add `models.compile` config structs to `internal/project/project.go`
|
||||||
|
- add effective config defaults and path expansion in
|
||||||
|
`internal/project/effective.go`
|
||||||
|
- add project helper methods for compiler/cache/env resolution
|
||||||
|
|
||||||
|
2. Validation foundation
|
||||||
|
- extend project/config validation for enabled compilation readiness
|
||||||
|
- extend CLI validation reporting where needed
|
||||||
|
|
||||||
|
3. Preparation layer
|
||||||
|
- add model compiler resolution, Wine resolution, NWN data staging, registry
|
||||||
|
setup, and incremental metadata logic
|
||||||
|
- add a `preparedModelAssets` representation
|
||||||
|
|
||||||
|
4. Pipeline integration
|
||||||
|
- plug model preparation into `planOrBuildHAKs`
|
||||||
|
- teach `collectAssetResources` to substitute compiled model payloads
|
||||||
|
- ensure `ContentID` and HAK reuse reflect compiled output
|
||||||
|
|
||||||
|
5. CLI polish
|
||||||
|
- add build-haks flags if approved
|
||||||
|
- add progress messages and summary output
|
||||||
|
|
||||||
|
6. Tests
|
||||||
|
- config tests
|
||||||
|
- validator tests
|
||||||
|
- pipeline tests with fake compiler/Wine shims
|
||||||
|
|
||||||
|
7. Docs
|
||||||
|
- repo README updates
|
||||||
|
- optional operator README under `tools/`
|
||||||
|
|
||||||
|
## Open Design Decisions To Resolve Before Coding
|
||||||
|
|
||||||
|
These should be decided explicitly during implementation, not left implicit:
|
||||||
|
|
||||||
|
1. Should the toolkit own a dedicated Wine prefix under cache by default, or
|
||||||
|
require a user-provided prefix?
|
||||||
|
2. Should NWN data alias files be created in a separate staging tree, or should
|
||||||
|
the configured data root be used directly when already compatible?
|
||||||
|
3. Is parallel model compilation stable enough to expose beyond best-effort?
|
||||||
|
4. Do we want dedicated `build-haks` flags now, or only config for the first
|
||||||
|
implementation?
|
||||||
|
5. Should per-model logs always be persisted, or only on failure / debug mode?
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
The feature is complete when all of the following are true:
|
||||||
|
|
||||||
|
- `sow-toolkit build-haks` can package compiled binary `.mdl` content from
|
||||||
|
authored ASCII `.mdl` source when `models.compile.enabled: true`
|
||||||
|
- authored `.mdl` files remain unchanged on disk
|
||||||
|
- validation and topdata/autogen behaviors that depend on authored `.mdl` source
|
||||||
|
still work
|
||||||
|
- generated HAK manifests still reference authored asset-relative paths
|
||||||
|
- HAK reuse logic correctly tracks compiled output changes
|
||||||
|
- the feature is opt-in, deterministic, and tested
|
||||||
|
- failures are actionable and name the offending model paths
|
||||||
|
- README/config docs are updated
|
||||||
|
|
||||||
|
## Relevant References
|
||||||
|
|
||||||
|
- Repo code:
|
||||||
|
- `internal/pipeline/build.go`
|
||||||
|
- `internal/validator/validator.go`
|
||||||
|
- `internal/topdata/autogen.go`
|
||||||
|
- `internal/topdata/parts_discovery.go`
|
||||||
|
- `README.md`
|
||||||
|
- Upstream docs consulted for this contract:
|
||||||
|
- `nwn.wiki` MDL page: compiled models are recommended for release because
|
||||||
|
ASCII loads more slowly
|
||||||
|
- Neverwinter Vault `nwnmdlcomp` page: standard compile/decompile CLI usage
|
||||||
|
- Neverwinter Vault forum discussions: `nwnmdlcomp` expects old NWN registry
|
||||||
|
keys/data layout and some supermodel cases need additional care
|
||||||
Reference in New Issue
Block a user