Skip to the content.

Release validation checklist

Release validation is split into three layers:

  1. MIT addon package — the production runtime that users can ship in commercial/non-commercial projects under MIT.
  2. Mixed-license demo package — a consumer of that exact addon build plus the CC BY-NC 4.0 CT2Hair/GodotHair demo grooms.
  3. Demo release media — deterministic videos recorded from the validated demo package.

The production algorithms are already validated on development. Release validation should catch packaging, path, import, license-boundary, and presentation regressions without rerunning every historical numerical/performance benchmark.

Current development validation status

Already validated on development:

The release package therefore does not need another full production benchmark before each RC unless packaging or shader math changes again.

Gate A — standalone MIT addon

A0. Generated shader drift check

The six production .gdshader wrappers and the shared Approx body include are generated from the canonical templates in tools/templates/. Run the drift check before any packaging step:

python3 tools/generate_hair_shaders.py --check

Expected success text:

ok: 7 generated shader files match their templates

Any missing: or drift: line means the checked-in addon shaders no longer match their templates; regenerate with python3 tools/generate_hair_shaders.py and re-run the check before continuing.

A1. Package from the current production sources

The release addon should contain only the runtime surface:

addons/marschner_hair/
  hair_groom_data.gd
  hair_material_profile.gd
  hair_marschner_lut_adapter.gd
  hair_coverage_policy.gd
  hair_coverage_controller.gd
  internal/
    hair_shader_utils.gd
  shaders/
    hair_approx.gdshader
    hair_approx_a2c.gdshader
    hair_marschner_unity_fast.gdshader
    hair_marschner_unity_fast_a2c.gdshader
    hair_marschner_cinematic.gdshader
    hair_marschner_cinematic_a2c.gdshader
    ...shared production includes (hair_common.gdshaderinc, hair_card_common.gdshaderinc, ...)
  luts/
    unity_azimuthal_64.res
    cinematic_longitudinal_kernel_128x128x64.res
    README.md

Every addon .gd, .gdshader, and .gdshaderinc file must ship with its matching Godot .uid sidecar. These sidecars are part of the addon identity and must be committed and included in the archive.

The standalone addon release should also carry the project MIT LICENSE and applicable MIT third-party notice text at the archive/release level.

Do not ship benchmark raw LUT resources, raw-data reconstruction fixtures, generated result sets, development scenes, experimental shader variants, Windows Zone.Identifier artifacts, demo models, groom maps, or generated demo videos in the MIT addon archive.

A2. Static path audit

After packaging, search the distributed addon for stale development-only paths. The addon should not reference:

res://assets/hair/
res://benchmark/
res://demos/

Every production preload/LUT path should resolve under res://addons/marschner_hair/, while shader #include paths should remain relative inside the packaged shader directory.

A3. Fresh-project import smoke

Create a fresh Godot 4.7 project containing only the release addon plus a tiny validation scene/script. Open it once in the editor before judging import errors.

Check that the global classes register:

HairMaterialProfile
HairGroomData
HairMarschnerLUTAdapter
HairCoveragePolicy
HairCoverageController

There should be no missing preload, missing include, parser, or class-registration errors.

A4. Package-level material smoke

In the fresh project, construct one complete HairGroomData using known-valid groom textures and one HairMaterialProfile.

For each quality tier, create both compiled coverage shader families:

3 quality tiers x 2 compiled coverage families = 6 variants

For every variant verify:

Run this with a normal rendering context. On the validated Godot 4.7 setup, do not rely on --headless for direct ImageTexture3D validation.

A5. Force a real draw/compile of all six variants

Material/RID creation alone is not a complete shader-compile test. Render at least one frame with each of the six production variants on a small card/quad mesh under a light.

The goal is not image-quality benchmarking. Confirm only that:

A small sequential harness is preferable to opening six separate editor windows.

A6. Auto coverage smoke

In Forward+:

MSAA off, TAA off -> Static Bayer
MSAA off, TAA on  -> TAA Bayer
MSAA on           -> A2C

In Mobile:

MSAA on  -> A2C
MSAA off -> Static Bayer

Compatibility should resolve Auto to Static Bayer.

This is a small regression check; the full coverage performance benchmark does not need to be rerun unless the coverage math changes.

A7. Editor tooltip smoke

Open one normal shader material and one A2C shader material from the packaged addon in Godot 4.7 and hover representative parameters:

wetness
wet_film_roughness
longitudinal_roughness
coords_texture
unity_azimuthal_lut         (Fast)
cinematic_longitudinal_lut  (Cinematic)
bayer_phase_index           (normal Bayer variant)

Confirm the Inspector displays the intended descriptions and that repackaging did not strip or break the comments.

A8. Dry/wet visual sanity check

A full recalibration is unnecessary. Render one representative groom at:

wetness = 0.0
wetness = 1.0

under a broad light and a narrower/high-contrast light. Confirm:

A9. Addon metadata

Before publishing:

Gate B — mixed-license demo package

Build the demo only after Gate A passes. The demo should consume the exact validated addon build, not a separately repathed copy.

B1. Addon identity

Compare the embedded demo addons/marschner_hair/** tree against the standalone addon staging tree. File names and bytes must match.

If the demo requires a different runtime source file, that difference belongs in the addon first; do not patch the demo’s private copy.

B2. License boundary audit

The demo archive must contain prominent license/notices:

LICENSE                         # project/addon MIT license
THIRD_PARTY_NOTICES.md
assets/hair/models/LICENSE.md   # demo groom CC BY-NC 4.0 notice

Confirm:

B3. Fresh demo import

Extract the demo archive into a new directory and open it in Godot 4.7.

There should be no missing resources, stale development paths, parser errors, shader include errors, or broken imports.

The demo should resolve runtime shader/profile/LUT dependencies through the embedded addons/marschner_hair/ copy.

B4. Demo groom smoke

The source demo contains the following ten CT2Hair/GodotHair hairstyle directories:

bangs
blowout
bob
curly
jewfro
jheri
moptop
pixie
wavy
wings

Cycle each groom once and confirm its mesh and associated groom textures import and render without missing-resource errors. One production tier is sufficient for this all-groom asset smoke; the six-variant shader matrix is already covered by Gate A.

Use Blowout as the standardized release-validation/media groom because the current profile and groom-data resources are already calibrated around it.

B5. Interactive demo controls

Confirm the interactive demo still supports its intended camera/light/hairstyle controls after packaging. In particular, verify the inherited GodotHair-style orbit camera can rotate and zoom around the head without losing framing.

Then confirm the packaged profile workflow can switch quality tiers and move wetness from 0 to 1 on the Blowout groom.

B6. Demo visual regression

With Blowout selected, compare at minimum:

Approx dry
Fast dry
Cinematic dry
Fast wetness 1.0
Cinematic wetness 1.0

This is a presentation sanity check, not a numerical revalidation. The expected direction of change must match the development results.

Gate C — release media

Record media only from the validated demo staging/extracted package, so the videos demonstrate the same addon and resources users receive.

The canonical workflow is documented in release_media.md.

Run:

python benchmark/tools/generate_release_gifs.py \
  --godot /path/to/godot \
  --project .
bash docs/images/make_composite_mp4s.sh
The current release media set is the five composite previews, each a horizontal 3-panel strip (Approx Fast Marschner Cinematic Marschner):
demo-video-quality-composite.mp4
demo-video-wetness-000-composite.mp4
demo-video-wetness-033-composite.mp4
demo-video-wetness-067-composite.mp4
demo-video-wetness-100-composite.mp4

The capture harness uses deterministic Movie Maker timing and the original GodotHair camera-orbit convention. Release videos use Static Bayer coverage so camera/shader comparisons are deterministic; coverage behavior itself remains covered by Gate A.

Historical note (pre-composite workflow): earlier releases captured three full-resolution clips (quality-tiers.mp4, fast-wetness.mp4, cinematic-wetness.mp4) at 1920x1080 / 60 fps with durations of about 18 s and 10 s via benchmark/tools/capture_release_media.py. Those clips remain attached to the PR13 demo media release as full-resolution downloads but are no longer the current capture set.

C1. Automated media checks

The GIF capture runner must reach:

RELEASE_GIFS_OK

Each composite preview is assembled from three source GIFs by docs/images/make_composite_mp4s.sh. When ffprobe is installed, verify every composite:

resolution = 1440 x 270
fps = 15
frames = 90
duration = 6 s

Source GIFs and composite MP4s live under docs/images/ and are tracked by Git; the MovieWriter OGV intermediates under benchmark/results/release_media/ are intentionally ignored by Git.

C2. Human video review

Review the final MP4s rather than only the MovieWriter intermediate.

Confirm:

Project-published videos that visibly reproduce the supplied demo groom are demo media and should be released under the CC BY-NC 4.0 demo terms, not bundled inside the MIT addon archive.

Tests that do not need to be repeated

Unless the underlying shader/coverage/LUT math changes again, the following expensive investigations are already sufficient evidence and do not need to block the next RC:

Final release gate

MIT addon

[ ] tools/generate_hair_shaders.py --check passes ("ok: 7 generated shader files match their templates")
[ ] current production files are canonical under addons/marschner_hair
[ ] no development-only resource paths remain in the addon
[ ] fresh-project import has no parser/preload/include errors
[ ] all 6 quality/coverage variants create and render at least one frame
[ ] Fast and Cinematic direct LUTs validate in the packaged paths
[ ] Auto coverage resolves correctly for Forward+/Mobile/Compatibility smoke
[ ] one normal and one A2C shader show the expected Inspector parameter tooltips
[ ] dry/wet package-level visual sanity check passes
[ ] VERSION / CHANGELOG / release README are current
[ ] MIT LICENSE and applicable third-party MIT notice are present
[ ] no CC BY-NC demo groom/media is inside the addon archive

Demo package

[ ] embedded addon is byte-identical to the validated standalone addon
[ ] mixed-license README / LICENSE / THIRD_PARTY_NOTICES / groom LICENSE are present
[ ] fresh extracted demo project imports without missing resources
[ ] all 10 bundled hairstyle assets can be selected/rendered
[ ] interactive orbit/zoom and demo controls work
[ ] Blowout tier/wetness presentation smoke passes

Release media

[ ] five composite previews (quality + wetness 0.00/0.33/0.67/1.00) built from the validated demo package
[ ] RELEASE_GIFS_OK reached by generate_release_gifs.py
[ ] 1440x270 / 15 fps / 90 frames / 6 s metadata checks pass (when ffprobe is available)
[ ] final MP4 visual review passes
[ ] CC BY-NC attribution is visible in-frame and repeated in release text

When all three sections pass, the matching addon and demo releases are ready to publish.