From c6186c4a0c97eaaca83f65e1b57cac8b004aa3d3 Mon Sep 17 00:00:00 2001 From: DaymareOn Date: Wed, 24 Jun 2026 13:27:04 +0200 Subject: [PATCH 1/3] feat: add SMP macro grammar to the physics XSD FSMP expands / macros before validation, but an external XML editor validating a pattern-bearing file against this schema would flag the new elements. Add them so editors accept such files: - system now allows pattern, pattern-default, and repeat. - pattern-default: name (required) plus optional author (namespace) and version; holds param* then one body. - param: name (required) plus optional default. - body / repeat content is lax (xsd:any skip) because templates carry ${placeholders} the strict element types would reject; FSMP validates the expanded output strictly. - pattern (use): name (required) plus optional version plus any other attribute (the parameter values). Schema version 2.1.0 -> 2.2.0. Verified with .NET XmlSchema: the schema compiles, a pattern-bearing sample validates, and invalid docs (missing name, unknown element) still error. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../hdtSkinnedMeshConfigs/hdtSMP64.xsd | 88 ++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.xsd b/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.xsd index c87ef44..3713c4a 100644 --- a/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.xsd +++ b/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.xsd @@ -6,7 +6,7 @@ targetNamespace="FSMP-Validator" elementFormDefault="qualified" attributeFormDefault="unqualified" - version="2.1.0" + version="2.2.0" > @@ -118,6 +118,9 @@ element. + + + @@ -206,6 +209,89 @@ element. + + + + + + + Defines a reusable pattern macro: a parameterized cluster of bones and +constraints. name is required; author (optional) namespaces it so its full name is author.name; +version (optional) lets several versions of one pattern coexist. Holds zero or more param +declarations followed by one body template. FSMP expands every pattern (and removes every +pattern-default) before the document is validated against the rest of this schema, so a body may +contain ${placeholders} that the strict element types would otherwise reject -- the body content is +therefore left unvalidated here. + + + + + + + + + + + + + + + + Declares a pattern parameter. name is required; default (optional) is the +value used when a use site omits the parameter -- a parameter without a default is required. + + + + + + + + + + + The template a pattern expands to: ordinary system-level elements plus +nested repeat and pattern uses, with ${param}/${loopVar}/${loopVar+/-N} placeholders. Left +unvalidated (lax) because of the placeholders; FSMP validates the expanded result strictly. + + + + + + + + + + + + Emits its content once per index, binding var to from .. from+count-1 +(from defaults to 0). count and from are strings because they may be ${placeholders}. Nest two for a +2-D grid. Content is left unvalidated (lax) for the same reason as body. + + + + + + + + + + + + + + + Instantiates a pattern. name is required and is the pattern's full name +(author.name when namespaced); version (optional) pins a version. Every other attribute supplies a +parameter value, so arbitrary attributes are permitted here. + + + + + + + + + From da42538f0442583349b3774b8c0fd9a6551bf78a Mon Sep 17 00:00:00 2001 From: DaymareOn Date: Wed, 24 Jun 2026 19:09:17 +0200 Subject: [PATCH 2/3] feat: add library root + pattern Schematron rules Completes editor support for pattern files: - hdtSMP64.xsd gains a root element so shared pattern-library files (patterns/*.xml) validate, not just files that use patterns. - hdtSMP64.sch gains a pattern-macros rule set: repeat count must be a non-negative integer or a placeholder, repeat from must be an integer or a placeholder, count="0" warns (emits nothing), and param / pattern-default names must not contain spaces. Written without XPath-2.0 regex so they never error in the runtime validator (which only ever sees expanded docs) yet fire in an authoring editor. Verified with .NET XmlSchema: the schema compiles; a system doc and a patterns library both validate; a bone in a patterns root is rejected; the count rule flags only the malformed repeats. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../hdtSkinnedMeshConfigs/hdtSMP64.sch | 31 +++++++++++++++++++ .../hdtSkinnedMeshConfigs/hdtSMP64.xsd | 15 +++++++++ 2 files changed, 46 insertions(+) diff --git a/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.sch b/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.sch index 7980aa8..fe91495 100644 --- a/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.sch +++ b/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.sch @@ -23,6 +23,37 @@ + + Pattern macro authoring rules + + + + + + repeat count must be a non-negative integer or a ${...} placeholder. + + + + + repeat from must be an integer or a ${...} placeholder. + + + + + repeat count is 0; this repeat emits nothing. + + + + + param name must not contain spaces. + + + pattern-default name must not contain spaces. + + + Static rigid body constraints (mass=0) diff --git a/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.xsd b/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.xsd index 3713c4a..e63c7db 100644 --- a/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.xsd +++ b/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.xsd @@ -292,6 +292,21 @@ parameter value, so arbitrary attributes are permitted here. + + + Root element of a shared pattern-library file, the kind that lives in +data/skse/plugins/hdtSkinnedMeshConfigs/patterns/. FSMP loads every such file once at startup and +makes its pattern-default definitions available to every physics XML. A library holds only pattern +definitions (no bones, constraints, or shapes); those live in the consuming system file. + + + + + + + + + From 44d695e82b951bcf38ecbe2ed92fa1ee88ba2aa1 Mon Sep 17 00:00:00 2001 From: DaymareOn Date: Sun, 28 Jun 2026 10:42:19 +0200 Subject: [PATCH 3/3] docs(examples): add Crossed Cloak FSMP pattern example mod A worked example for the modder guide: rebuilds one Cloaks of Skyrim cloak as a crossed 2-D tissue using the FSMP pattern feature. Ships only XML config (cloak.xml consumer + shared patterns/DaydreamingDay.xml library), no art assets; requires Cloaks of Skyrim + Artesian Cloaks of Skyrim for the rigged NIF. Includes a README (requirements, install, credits, permissions) and a reproducible package.ps1 that builds the versioned zip (attached to a GitHub Release, not committed). Co-Authored-By: Claude Opus 4.8 (1M context) --- examples/crossed-cloak/.gitignore | 2 + examples/crossed-cloak/README.md | 92 ++++++++++++++ .../Plugins/hdtSkinnedMeshConfigs/cloak.xml | 98 +++++++++++++++ .../patterns/DaydreamingDay.xml | 118 ++++++++++++++++++ examples/crossed-cloak/package.ps1 | 29 +++++ 5 files changed, 339 insertions(+) create mode 100644 examples/crossed-cloak/.gitignore create mode 100644 examples/crossed-cloak/README.md create mode 100644 examples/crossed-cloak/SKSE/Plugins/hdtSkinnedMeshConfigs/cloak.xml create mode 100644 examples/crossed-cloak/SKSE/Plugins/hdtSkinnedMeshConfigs/patterns/DaydreamingDay.xml create mode 100644 examples/crossed-cloak/package.ps1 diff --git a/examples/crossed-cloak/.gitignore b/examples/crossed-cloak/.gitignore new file mode 100644 index 0000000..704c612 --- /dev/null +++ b/examples/crossed-cloak/.gitignore @@ -0,0 +1,2 @@ +# Built zips are attached to GitHub Releases, not committed. +dist/ diff --git a/examples/crossed-cloak/README.md b/examples/crossed-cloak/README.md new file mode 100644 index 0000000..d7be42c --- /dev/null +++ b/examples/crossed-cloak/README.md @@ -0,0 +1,92 @@ +# Crossed Cloak — FSMP Pattern Example + +**Version 0.1.0** · an FSMP (Faster HDT-SMP) **pattern** demonstration mod. + +This is a worked example for the [FSMP modder guide](https://github.com/DaymareOn/FSMP-Validator/wiki): it rebuilds the +physics of one Cloaks of Skyrim cloak as a **crossed 2-D tissue** using the FSMP *pattern* +macro feature, instead of hand-writing every bone and constraint. It is meant to be read +alongside the two source files, which are heavily commented. + +> ⚠️ **Requires an FSMP build with pattern support, which is not yet released.** Until then +> this mod only works with the test DLL handed to testers. It is published here as a +> versioned reference, not yet as a public Nexus release. + +--- + +## What it demonstrates + +The required Cloaks of Skyrim cloak NIF is a 3×6 grid of `CB` cloth bones (`CB 01 1` … +`CB 03 6`) wired as three independent vertical chains. This example keeps the **same real NIF +bones** but reorganises the physics: + +- **`patterns/DaydreamingDay.xml`** — a shared pattern library. It defines one reusable + pattern, `DaydreamingDay.cloak`, that emits the whole cloth structure: + - the CB grid (parameterised by `cols`/`rows`), + - **warp** constraints chaining each column top-to-bottom (locked near the neck, lightly + limited lower down), + - **weft** constraints tying adjacent columns at the same row — a deliberately loose, + non-Hookean spring so the cloak drapes as a sheet without going rigid. + - The weft tuning is exposed as parameters (`weftStiffness`, `weftLinearLimit`, …) with + sensible defaults. +- **`cloak.xml`** — the consumer. It declares only what is specific to *this* NIF (the + collision shapes, the body skeleton bones those shapes skin to) and invokes the pattern + with one line, overriding two weft knobs: + + ```xml + + ``` + +At load time FSMP expands that single tag into 18 bones and 25 constraints. Dropping +`DaydreamingDay.xml` into the global `patterns/` folder makes the pattern available to every +physics file, so other cloaks can reuse the same structure with their own tuning. + +The expanded document passes the FSMP `smp report` validator with **0 errors, 0 warnings**. + +--- + +## Requirements + +Install **all** of these first, in this order. This mod ships **only XML config** — no +meshes or textures — so the cloak itself comes entirely from the mods below. + +1. **[SKSE64](http://skse.silverlock.org/)** +2. **Faster HDT-SMP (FSMP) with pattern support** — the test build (pattern support is + unreleased as of this version). A stock HDT-SMP/FSMP without pattern support will fail to + load this file. +3. **[Cloaks of Skyrim](https://www.nexusmods.com/skyrimspecialedition/mods/6369)** (Nikinoodles + & Nazenn) — the base cloak items, meshes and textures. +4. **[Artesian Cloaks of Skyrim](https://www.nexusmods.com/skyrimspecialedition/mods/17416)** — + the SMP-enabled cloak NIFs (`Meshes\Clothes\cloaksofskyrim\`) whose `CB` bones this config + binds to. + +## Installation + +Install with a mod manager (MO2/Vortex) **after** the cloak mods above, and let it +**overwrite**. This mod replaces the SMP cloak patch's `cloak.xml` with the pattern-based +version and adds the shared `patterns/DaydreamingDay.xml`: + +``` +SKSE/Plugins/hdtSkinnedMeshConfigs/cloak.xml (replaces the patch's) +SKSE/Plugins/hdtSkinnedMeshConfigs/patterns/DaydreamingDay.xml (new, shared library) +``` + +It only changes the **cloak** physics. The patch's `cape.xml` is left untouched. + +--- + +## Credits + +- **Cloaks of Skyrim** — Nikinoodles & Nazenn. Base cloak meshes, textures and items. +- **Artesian Cloaks of Skyrim** — Zeridian and Rosent. The SMP-enabled cloak NIFs and the + original `cloak.xml` this example is derived from. +- **HDT-SMP / Faster HDT-SMP** — HydrogensaysHDT and the FSMP maintainers. The physics engine + and the pattern feature. +- **DaydreamingDay** — the pattern, this reimplementation, and the example. + +## Permissions + +This mod contains only original XML configuration authored by DaydreamingDay, derived from +Artesian Cloaks of Skyrim's `cloak.xml`. It **redistributes no art assets** (no meshes, no +textures) — those remain with their original authors and are obtained by installing the +required mods above. Artesian Cloaks of Skyrim's permissions explicitly forbid redistributing +its art assets, which this example respects by depending on it rather than bundling it. diff --git a/examples/crossed-cloak/SKSE/Plugins/hdtSkinnedMeshConfigs/cloak.xml b/examples/crossed-cloak/SKSE/Plugins/hdtSkinnedMeshConfigs/cloak.xml new file mode 100644 index 0000000..92d1527 --- /dev/null +++ b/examples/crossed-cloak/SKSE/Plugins/hdtSkinnedMeshConfigs/cloak.xml @@ -0,0 +1,98 @@ + + + + + + + + 5 + ground + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + body2 + body + + + + 0 + 0 + Feet + body + + + + 0 + cloth + + + diff --git a/examples/crossed-cloak/SKSE/Plugins/hdtSkinnedMeshConfigs/patterns/DaydreamingDay.xml b/examples/crossed-cloak/SKSE/Plugins/hdtSkinnedMeshConfigs/patterns/DaydreamingDay.xml new file mode 100644 index 0000000..aabe6c3 --- /dev/null +++ b/examples/crossed-cloak/SKSE/Plugins/hdtSkinnedMeshConfigs/patterns/DaydreamingDay.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + 1 + + + 0.02 + + 0.8 + 0.8 + 0.9 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/crossed-cloak/package.ps1 b/examples/crossed-cloak/package.ps1 new file mode 100644 index 0000000..ba86181 --- /dev/null +++ b/examples/crossed-cloak/package.ps1 @@ -0,0 +1,29 @@ +# Builds the installable, versioned zip for the "Crossed Cloak - FSMP Pattern Example" mod. +# +# The zip root mirrors a Skyrim Data install (SKSE/Plugins/...), so a mod manager can install +# it directly, plus the README at the root. Output goes to dist/ (git-ignored); attach the +# resulting zip to a GitHub Release as the versioned asset, then upload that same file to Nexus. +# +# Usage: pwsh ./package.ps1 [-Version 0.1.0] +param([string]$Version = "0.1.0") + +$ErrorActionPreference = "Stop" +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$dist = Join-Path $here "dist" +New-Item -ItemType Directory -Force -Path $dist | Out-Null + +$zip = Join-Path $dist ("FSMP-Crossed-Cloak-Pattern-Example-{0}.zip" -f $Version) +if (Test-Path $zip) { Remove-Item $zip -Force } + +# Contents: the installable data tree + the readme. Nothing else (no art assets). +$items = @( + (Join-Path $here "SKSE"), + (Join-Path $here "README.md") +) +Compress-Archive -Path $items -DestinationPath $zip -CompressionLevel Optimal + +$size = [math]::Round((Get-Item $zip).Length / 1KB, 1) +Write-Output ("Built {0} ({1} KB)" -f $zip, $size) +Write-Output "Contents:" +Add-Type -AssemblyName System.IO.Compression.FileSystem +[System.IO.Compression.ZipFile]::OpenRead($zip).Entries | ForEach-Object { Write-Output (" " + $_.FullName) }