Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions .codex/runtime-proofs/mod-pair-compatibility/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Mod Pair Compatibility Proof

This proof shape is for checking whether two client-side mods can load together without a targeted startup or UI bring-up failure. It is intentionally generic: the current Eclipse/BloodCraftHub check is one profile of this workflow, not the name or owner of the workflow.

## Labels

- `pairLabel`: stable lowercase label for the two-mod pairing, such as `eclipse-bloodcrafthub`.
- `subjectMod`: the mod being changed or evaluated in this repository.
- `peerMod`: the other mod in the compatibility pair.
- `supportMod`: an additional staged mod needed to make the scenario realistic without redefining the pair under test.
- `proofMode`: one of `subject-only`, `peer-only`, `combined-control`, or `combined-candidate`.
- `runId`: timestamped identifier for one staging and evidence collection attempt.

Use these names in receipts, folders, and summaries so future community troubleshooting can reuse the same packet shape.

## Stop Rules

Stop and mark the run inconclusive when:

- the staged plugin inventory contains unrelated mods;
- two staged artifacts would land with the same plugin filename;
- the proof requires mutating the live game profile without a restorable backup;
- the client/server build, BepInEx pack, or mod versions cannot be recorded;
- the observed result is only a manual impression with no retained log or receipt;
- the failure moves outside the named compatibility lane.

## Recommended Matrix

| proofMode | Staged mods | Purpose |
| --- | --- | --- |
| `subject-only` | subject mod only | Prove the changed mod still loads by itself. |
| `peer-only` | peer mod only | Prove the peer mod is not already failing alone. |
| `combined-control` | subject + peer with suspected risky setting enabled | Optional negative control when safe and quick. |
| `combined-candidate` | subject + peer with candidate mitigation enabled | Main compatibility proof. |

For the Eclipse/BloodCraftHub case, the useful candidate profile is `combined-candidate` with Eclipse `UIOptions.AttributeBuffs=false`. A useful optional control is `combined-control` with `UIOptions.AttributeBuffs=true`.

## Receipt Requirements

Each run should retain:

- `receipt.json` with `pairLabel`, `proofMode`, mod names, artifact hashes, git commit, config overrides, and timestamp;
- `plugin-inventory.txt` listing the staged plugins;
- copied config overrides or a plain text description of them;
- `LogOutput.log` or the closest available BepInEx log from the run;
- `summary.md` naming the pass/fail/inconclusive result and the observed markers.

Expected success markers should be named before the run. For the current compatibility lane, useful markers are:

- subject mod loaded;
- peer mod loaded;
- no fatal or repeated exception around `CanvasService`;
- no fatal or repeated exception around the targeted DOTS attribute-buffer path;
- client reaches the agreed checkpoint, ideally world entry or a timed post-UI-bring-up survival window.

## Dry-Run Staging

Use the generic helper to create a proof packet before launching the game:

```powershell
.\.codex\scripts\New-ModPairCompatibilityProof.ps1 `
-PairLabel eclipse-bloodcrafthub `
-ProofMode combined-candidate `
-SubjectModName Eclipse `
-SubjectModArtifact .\bin\Release\net6.0\Eclipse.dll `
-PeerModName BloodCraftHub `
-PeerModArtifact C:\Path\To\BloodCraftHub.dll `
-ConfigOverride "Eclipse:io.zfolmt.Eclipse.cfg:UIOptions.AttributeBuffs=false"
```

The helper creates a run folder under `.codex/runs/mod-pair-compatibility/`, stages only the named mod artifacts, and writes the initial receipt. Runtime launch and log collection remain explicit follow-up steps.

When the pair needs a realistic companion mod, keep the pair labels stable and add support artifacts explicitly:

```powershell
.\.codex\scripts\New-ModPairCompatibilityProof.ps1 `
-PairLabel eclipse-bloodcrafthub `
-ProofMode combined-candidate `
-SubjectModName Eclipse `
-SubjectModArtifact .\bin\Release\net6.0\Eclipse.dll `
-PeerModName BloodCraftHub `
-PeerModArtifact C:\Path\To\BloodCraftHub.dll `
-SupportModArtifact C:\Path\To\Bloodcraft.dll `
-ConfigOverride "Eclipse:io.zfolmt.Eclipse.cfg:UIOptions.AttributeBuffs=false"
```

## GitHub Release Assets

When the peer mod is distributed through GitHub Releases, fetch the exact release asset into the local artifact cache first:

```powershell
.\.codex\scripts\Save-GitHubReleaseAsset.ps1 `
-ReleaseUrl "https://github.com/KDavidP1987/BloodCraftHub/releases/latest" `
-AssetPattern "*.dll"
```

For ZIP assets:

```powershell
.\.codex\scripts\Save-GitHubReleaseAsset.ps1 `
-Repository KDavidP1987/BloodCraftHub `
-Tag latest `
-AssetPattern "*.zip" `
-ExtractZip
```

The downloader requires the asset pattern to match exactly one GitHub release asset. It writes a receipt beside the downloaded file under `.codex\artifacts\mod-releases\`; use the downloaded DLL, or the extracted DLL if the release asset was a ZIP, as `-PeerModArtifact` for the proof packet.

## Client Sandbox Setup

Use `VRisingCodex` as the client proof sandbox when available:

```powershell
$run = ".\.codex\runs\mod-pair-compatibility\eclipse-bloodcrafthub\combined-candidate\<runId>"

.\.codex\scripts\Use-ModPairClientProofProfile.ps1 `
-Action Install `
-ClientRoot "C:\Program Files (x86)\Steam\steamapps\common\VRisingCodex" `
-ProofRunDirectory $run
```

The install action backs up the sandbox's current `BepInEx\plugins` and `BepInEx\config`, clears the plugin directory, copies the staged proof plugins, and applies config overrides recorded in the proof packet.

After the client run:

```powershell
.\.codex\scripts\Use-ModPairClientProofProfile.ps1 `
-Action Collect `
-ClientRoot "C:\Program Files (x86)\Steam\steamapps\common\VRisingCodex"

.\.codex\scripts\Use-ModPairClientProofProfile.ps1 `
-Action Restore `
-ClientRoot "C:\Program Files (x86)\Steam\steamapps\common\VRisingCodex"
```

`Collect` copies the current client logs and config into the proof run. `Restore` puts the previous sandbox plugin/config state back from the generated backup.

## Server Support Profiles

When the client pair needs a server mod loaded to produce meaningful replies, create a separate BepInEx plugin-set proof for the server sandbox instead of adding server mods to the client pair:

```powershell
.\.codex\scripts\New-BepInExPluginSetProof.ps1 `
-ProfileLabel bloodcraft-server-support `
-TargetRole server `
-PluginArtifact C:\Path\To\Bloodcraft.dll, C:\Path\To\VampireCommandFramework.dll
```

Install it into the dedicated-server Codex sandbox with the same profile installer, using the server executable name:

```powershell
.\.codex\scripts\Use-ModPairClientProofProfile.ps1 `
-Action Install `
-ClientRoot "C:\Program Files (x86)\Steam\steamapps\common\VRisingDedicatedServerCodex" `
-ExpectedExecutableName VRisingServer.exe `
-ProofRunDirectory ".\.codex\runs\bepinex-plugin-set\bloodcraft-server-support\server\<runId>"
```
147 changes: 147 additions & 0 deletions .codex/scripts/New-BepInExPluginSetProof.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidatePattern('^[a-z0-9][a-z0-9-]*$')]
[string]$ProfileLabel,

[Parameter(Mandatory = $true)]
[ValidateSet('client', 'server')]
[string]$TargetRole,

[Parameter(Mandatory = $true)]
[string[]]$PluginArtifact,

[string[]]$ConfigOverride = @(),

[string]$RunRoot = ".codex\runs\bepinex-plugin-set",

[string]$RunId = ""
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

function Resolve-ExistingFile {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)

$resolved = @(Resolve-Path -LiteralPath $Path -ErrorAction Stop)
if ($resolved.Count -ne 1) {
throw "Expected one file for '$Path', found $($resolved.Count)."
}

$item = Get-Item -LiteralPath $resolved[0].Path
if (-not $item.PSIsContainer) {
return $item
}

throw "Expected a file path, got directory '$Path'."
}

function Assert-UniqueArtifactFileNames {
param(
[Parameter(Mandatory = $true)]
[System.IO.FileInfo[]]$Artifacts
)

$duplicates = @($Artifacts | Group-Object -Property { $_.Name.ToLowerInvariant() } | Where-Object { $_.Count -gt 1 })
if ($duplicates.Count -eq 0) {
return
}

$messages = @()
foreach ($duplicate in $duplicates) {
$messages += "{0}: {1}" -f $duplicate.Group[0].Name, (($duplicate.Group | ForEach-Object { $_.FullName }) -join "; ")
}

throw "Duplicate staged plugin filenames are not supported because BepInEx plugin staging would overwrite files. Rename or wrap one artifact, or stage this scenario manually. Duplicates: $($messages -join " | ")"
}

if ([string]::IsNullOrWhiteSpace($RunId)) {
$RunId = Get-Date -Format "yyyyMMdd-HHmmss"
}

$artifacts = @($PluginArtifact | ForEach-Object { Resolve-ExistingFile -Path $_ })
Assert-UniqueArtifactFileNames -Artifacts $artifacts

$runDirectory = Join-Path $RunRoot (Join-Path $ProfileLabel (Join-Path $TargetRole $RunId))
$stagePluginDirectory = Join-Path $runDirectory "stage\BepInEx\plugins"
New-Item -ItemType Directory -Path $stagePluginDirectory -Force | Out-Null

$stagedArtifacts = @()
foreach ($artifact in $artifacts) {
$hash = Get-FileHash -LiteralPath $artifact.FullName -Algorithm SHA256
$target = Join-Path $stagePluginDirectory $artifact.Name
Copy-Item -LiteralPath $artifact.FullName -Destination $target -Force

$stagedArtifacts += [ordered]@{
name = [System.IO.Path]::GetFileNameWithoutExtension($artifact.Name)
sourcePath = $artifact.FullName
stagedPath = (Resolve-Path -LiteralPath $target).Path
sha256 = $hash.Hash
length = $artifact.Length
lastWriteTimeUtc = $artifact.LastWriteTimeUtc.ToString("o")
}
}

$inventoryPath = Join-Path $runDirectory "plugin-inventory.txt"
$stagedArtifacts | ForEach-Object {
"{0}`t{1}`t{2}" -f $_.name, $_.sha256, $_.stagedPath
} | Set-Content -LiteralPath $inventoryPath -Encoding utf8

$configPath = Join-Path $runDirectory "config-overrides.txt"
if ($ConfigOverride.Count -gt 0) {
$ConfigOverride | Set-Content -LiteralPath $configPath -Encoding utf8
}
else {
"No config overrides recorded." | Set-Content -LiteralPath $configPath -Encoding utf8
}

$gitCommit = ""
try {
$gitCommit = (& git rev-parse HEAD).Trim()
}
catch {
$gitCommit = "unavailable"
}

$receipt = [ordered]@{
schema = "bepinex-plugin-set-proof.v1"
runId = $RunId
profileLabel = $ProfileLabel
targetRole = $TargetRole
createdAtUtc = (Get-Date).ToUniversalTime().ToString("o")
repository = (Resolve-Path -LiteralPath ".").Path
gitCommit = $gitCommit
stagedPluginDirectory = (Resolve-Path -LiteralPath $stagePluginDirectory).Path
configOverrides = $ConfigOverride
artifacts = $stagedArtifacts
result = "not-run"
resultNotes = "Staging receipt only. Install this plugin set into an isolated BepInEx profile, then collect logs into this run directory."
}

$receiptPath = Join-Path $runDirectory "receipt.json"
$receipt | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $receiptPath -Encoding utf8

$summaryPath = Join-Path $runDirectory "summary.md"
@(
"# BepInEx Plugin Set Proof"
""
"- Profile: ``$ProfileLabel``"
"- Target role: ``$TargetRole``"
"- Result: ``not-run``"
""
"## Next Steps"
""
"1. Install this staged plugin set into the isolated BepInEx target."
"2. Run the target to the agreed checkpoint."
"3. Collect logs into this run directory."
"4. Update ``result`` and ``resultNotes`` in ``receipt.json``."
) | Set-Content -LiteralPath $summaryPath -Encoding utf8

Write-Host "Created BepInEx plugin set proof packet:"
Write-Host " $runDirectory"
Write-Host "Receipt:"
Write-Host " $receiptPath"
Loading
Loading