Skip to content

Wardrobe Example Verification Record and Performance Baseline

This Implementation and Verification (2026-10-08)

This change provides real-model examples of texture swapping, clothing model swapping and a vanilla MC held weapon, with every outfit wearing the user-supplied No. 13 hair. The public data consists of the generic Model, Skeleton, material instances and the assembly interface.

Change Effect
WardrobeExample Connects user assets to the existing Reader and demonstrates material frame switching, model replacement, cache reuse and holding a sword at the right wrist
KsgPmxLoader Atlas texture IDs are derived as a SHA-256 over the archive, the PMX entry and the texture entry, fixing texture mix-ups between identically named assets across archives
KsgPmxTextureIdentityTest Covers different archives, different models/textures, Unicode names and old string-hash collisions
Iris vertex upload and MC render stack Explicitly fills in intermediate UVs, tangents and entity attributes, supports worker threads and partial updates, and restores the PoseStack in a finally block when rendering fails
Shader smoke entry point Optional real shader pack, verifying the active Iris pipeline; by default it only outputs pass/fail logs, with no screenshots and no performance sampling
ModelAssemblyProbe Adds RibbonDress short-sleeve/long-sleeve benchmarks with hair, recording the JVM PID, Java version and heap limit
Local asset preparation script Turns extracted assets into a local resource pack, can append the enable setting and keeps the other resource packs
Performance summary script Validates the reports of three separate JVMs and outputs Markdown only, generating no HTML and no extra summary JSON
Gradle and API documentation Adds the opt-in example/automated acceptance properties, the run steps and the performance explanation

The scope of this commit is the source, tests, scripts and API Markdown documentation listed above. Models, textures, resource packs, saves, screenshots, HTML, raw JSON reports, logs and build artifacts are not committed.

The verification environment was macOS / aarch64, Apple M3 Max, Java 21.0.3, Minecraft 1.21.1, NeoForge 21.1.203. The client loaded Iris 1.8.12 / Sodium 0.6.13. Earlier screenshots and performance observations used the disabled configuration; separately, the six-stage smoke check with an enabled shader pack was run through with BSL 8.2.04. The verification scope is limited to that environment and that shader pack.

Verification Result
renderingReleaseCheck passed
modelUnitTest 811 cases, 0 failures, 0 errors, 3 conditionally skipped
renderGlTest 13 groups passed; the 21,259 vertices of the real matching base body/long dress passed a CPU/GPU skinning comparison
Local PMX probe three JVMs each loaded all 92 PMX files successfully, 0 load failures
Real client example with hair WARDROBE_EXAMPLE_PASS, 180 frames, 61 vanilla item submissions
BSL 8.2.04 shader path WARDROBE_SHADER_SMOKE_PASS, all six stages completed, actually entering IrisRenderingPipeline with no fallback to no-shader

The client verified, in order, the original colorway, the Rose colorway, the long-sleeve model, switching back to short sleeves, holding the MC sword and wrist rotation. Texture switching keeps the same Model; after a model switch, switching back to short sleeves returns the same assembly Model from the first time, and the build counter does not increase. A new session builds two assembly models in total. Here the switch-back is served by the outfit registry reusing an already-resolved result, so the underlying assembly cache shows hits 0 and builds 2, and registry reuse is not miscounted as an underlying cache hit.

Local Performance Results (2026-10-08)

Three separate JavaExec JVMs were run, with PIDs 73993, 74024 and 74050, in the measurement order uncached-first, cached-first, uncached-first. The Java runtime was 21.0.3+10-LTS and the maximum heap was configured to 4 GiB each time.

The results below are the median of the per-JVM batch-mean p50 values. Uncached assembly includes the common geometry/skeleton assembly work; the two warm-cache columns measure, respectively, rebuilding a request and then querying the cache, and reusing an existing request and only querying the cache. File parsing, texture decoding, GPU skinning and drawing are not counted in these CPU assembly metrics.

Case Vertices Bones Uncached assembly (ms) Rebuild request + warm cache (µs) Existing request, cache query (µs)
Ayuchan matching base body + long dress 21,259 316 70.168 7.400 0.263
Base body + RibbonDress short sleeves + No. 13 hair 21,397 263 60.738 3.015 0.041
Base body + RibbonDress long sleeves + No. 13 hair 22,069 263 63.751 2.286 0.040
Base body + 1 real small garment 8,806 181 34.303 4.105 0.063
Base body + 10 duplicate small garments 12,955 694 41.089 20.289 0.093
Base body + 100 duplicate small garments 54,445 5,824 125.227 131.003 0.730

The 100-copy stress case repeats the same 461-vertex small garment and shares only the bind-compatible bones; the remaining bones are named independently. This result does not mean that 100 different garments have been fitted to the base body, and cannot be converted into a game FPS gain. The p50 of batch means is not a per-call latency distribution, and a cache query for an existing request is not a whole swap operation.

The following single-call CPU durations were also recorded in the real client; they have no warm-up statistics:

Actual operation Single-call CPU observation (ms)
Switching short sleeves to the Rose texture 0.087
Replacing with the long-sleeve model (including instance and backend mount) 289.673
Switching back to the cached short-sleeve model (including instance and backend mount) 171.290
Switching short sleeves back to the Blue texture, two observations 0.073 / 0.014

The CPU assembly cache avoids re-assembling the same geometry and skeleton, but a new ModelInstance and new backend render resources are still created. The cache in this pass did not remove that part of the cost of a whole swap operation; the single-call figures in the table above are also not GPU or whole-frame durations.

Current Fitting and Verification Boundaries

RibbonDress and this base body bind their same-named bones at different positions, so the clothing and hair keep their own separate bind skeletons. This example enables no physics and runs no full-body animation; automatic body fitting and skeleton retargeting are not implemented, so it cannot be taken as a guarantee that any garment will have no interpenetration. The Ayuchan matching long dress comparison uses strictly compatible shared bones.

The held item uses a real right-wrist Anchor and the vanilla ItemRenderer, and does not modify the player's inventory. What this client pass actually accepted is the Diamond Sword; the Iron Pickaxe is another option the example provides. The humanoid armor layer for worn armor, other shader packs and other operating systems were not accepted in this example. The BSL path only verifies that it runs; do not read it as a new image-quality acceptance or performance measurement.

The interactive commands and the standalone preview window entry point are provided; the client measurements above use automated headless Free Camera output, and are not to be read as re-verifying standalone window operation in this pass.

Smoke Verification With a Shader Pack Enabled

Use an existing local shader pack, for example BSL 8.2.04; relative paths resolve from the repository root.

./gradlew :modules:modelling:runClientHeadless \
  -PkasugaClientDirectory=/tmp/kasuga-wardrobe-client \
  -PkasugaQuickPlayWorld=wardrobe \
  -PkasugaWardrobeShaderSmoke=true \
  -PkasugaWardrobeShaderPack=/absolute/path/to/BSL_v8.2.04.zip

kasugaWardrobeShaderSmoke=true starts the same six-stage example with hair automatically and does not need kasugaWardrobeDemo to be enabled as well. During the real model and item render stages it checks that the current Iris pack is non-empty, that a shader pack is in use, and that the actual pipeline is IrisRenderingPipeline; only when all stages finish and pass the GL checks is WARDROBE_SHADER_SMOKE_PASS recorded. Failure records WARDROBE_SHADER_SMOKE_FAIL, and a successful Gradle exit is not taken as shader acceptance success.

By default this mode reads no pixels, saves no images/JSON and records no operation durations; it cannot be combined with kasugaWardrobeCapture. It uses only the camera's own shader session and does not set BSL as the main view shader pack. To see the actual result, add -PkasugaWardrobeShaderPreview=true, which saves one debug/wardrobe-example/shader-preview.png during the sword stage only and still samples no performance. The image is for local viewing and is not committed.

On this machine the BSL 8.2.04 path completed all six stages, with Iris OIT enabled normally and no model upload failure, matrix stack exception or no-shader fallback. Because the user asked to see the result, one extra sword frame was captured; the original no-shader screenshots and performance report were not overwritten. This verification confirms that the run path is clear; it does not prove that other shader packs, other platforms or final image quality all pass.

Performance Showcase

Run modelAssemblyProbe three times as separate JavaExec JVMs, alternating uncached-first, cached-first, uncached-first, with a different report path each time. Besides the matching long dress and 1/10/100 duplicate small garments, the probe also measures this example's base body, RibbonDress short sleeves/long sleeves and hair assembly. When it finishes, run:

python3 scripts/summarize-wardrobe-example.py \
  --capture /tmp/kasuga-wardrobe-client/debug/wardrobe-example \
  /tmp/wardrobe-bench-1.json /tmp/wardrobe-bench-2.json /tmp/wardrobe-bench-3.json

It writes only summary.md in the same directory; --output /path/to/report.md specifies a Markdown path. The summary file depends on no images, HTML or extra summary JSON. The table shows the median of the per-JVM batch-mean p50 values and separates uncached assembly, rebuilding a request then querying the warm cache, and querying the warm cache for an existing request. Input parsing, texture decoding, GPU skinning and drawing are not counted in these CPU assembly metrics. The 100-copy stress case repeats the same real small garment and cannot be read as 100 different garments that have been fitted.

The operations entries in the client report are CPU observations of a few actual operations; a model switch includes instance and backend mounting and must not be mixed with the warm-up benchmarks above or with GPU/whole-frame performance. Performance numbers should be taken from the report actually generated on your own machine.

This is the verification record and performance baseline page. For the recipe covering asset preparation, interactive runs and automated screenshot acceptance, see the real-asset wardrobe swap example.