chore(plugins): remove the Smoke gate plugin; MossTank shelf icon 0x06002C41
Owner direction 2026-09-06: the owner visually accepted all three connected
gates from the plugin-shelf/DAT-icon work — the movable plugin shelf
(Slice A), the DAT icon markup (Slice B), and the retained-UI outline-order
fix (761a7519f). With that gate passed, the Smoke plugin's job as the gate
artifact is done, so it is deleted outright rather than merely hidden:
src/AcDream.Plugins.Smoke/ (SmokePlugin.cs, SmokeIconPanel.cs, csproj, lock
files).
Reference sites cleaned:
- AcDream.slnx: removed the project entry.
- src/AcDream.App/AcDream.App.csproj: removed the Smoke ProjectReference and
the CopySmokePluginToBuildOutput/CopySmokePluginToPublishOutput targets;
the MossTank equivalents are untouched.
- .github/workflows/headless-portability.yml: the Linux package-contract
step no longer asserts a Smoke plugin.dll/plugin.json pair — repointed at
MossTank's, since the step's job was to prove the plugin-copy packaging
mechanism works end to end, not specifically to prove Smoke.
- tests/AcDream.Core.Tests/Plugins/PluginManifestTests.cs: the inline JSON
fixture used Smoke's manifest values as arbitrary test data; swapped for
MossTank's so the parser test still proves the same thing.
- tests/AcDream.App.Tests/Rendering/LinuxPlatformBoundaryTests.cs: the
shipped-plugin-copy shape test counted 4 GetTargetPath targets (Smoke +
MossTank, build + publish); now 2 (MossTank only).
- tests/AcDream.App.Tests/Plugins/AppAutomationSurfaceIconInstalledDatTests.cs:
reworded a doc comment that named the now-deleted SmokeIconPanel.
- README.md, docs/plugin-ui-markup.md: dropped Smoke-specific mentions,
kept the icon markup example/grammar (now citing MossTank's own real
IconSurfaceId).
- docs/plans/2026-09-06-plugin-shelf-and-dat-icons.md: recorded the owner's
2026-09-06 acceptance and the Smoke removal in the review ledger; deleted
the now-moot "before shipment" Smoke-in-release-zip warning.
- docs/reviews/coverage-ledger.md, docs/reviews/findings-ledger.md: left
untouched — both are frozen audit snapshots ("complete for baseline
<hash>"), so their Smoke rows are historical record, not live claims.
- docs/ISSUES.md: left untouched — its Smoke mentions are inside closed
issue #193's historical write-up of a past investigation.
MossTank plugin shelf icon: MossTankPlugin.cs's PluginPanelDescriptor now
sets IconSurfaceId = 0x06002C41 (IconText = "MT" remains the fallback).
Verified against the installed retail DAT with a new InstalledDat-lane test,
tests/AcDream.App.Tests/UI/MossTankIconInstalledDatTests.cs, mirroring
RetailMarkupIconResolverInstalledDatTests's convention: confirms the id is a
real Portal/HighRes RenderSurface and that RetailMarkupIconResolver.ResolveDid
returns a non-zero texture for it.
Verified: dotnet build AcDream.slnx -c Release green; a stale
plugins/AcDream.Plugins.Smoke output folder from a prior build was deleted
and a fresh build does not recreate it. Full App suite: 7,363 passed / 97
skipped / 36 failed (was 7,362/97/36) — the failing set is unchanged and
none are plugin-related; the one new pass is the MossTank DAT-icon test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
ce05c4fb03
commit
ece2104189
17 changed files with 102 additions and 359 deletions
4
.github/workflows/headless-portability.yml
vendored
4
.github/workflows/headless-portability.yml
vendored
|
|
@ -211,8 +211,8 @@ jobs:
|
|||
test -f "$root/libglfw.so.3"
|
||||
test -f "$root/libopenal.so"
|
||||
test -f "$root/Rendering/Shaders/mesh_modern.vert"
|
||||
test -f "$root/plugins/AcDream.Plugins.Smoke/AcDream.Plugins.Smoke.dll"
|
||||
test -f "$root/plugins/AcDream.Plugins.Smoke/plugin.json"
|
||||
test -f "$root/plugins/AcDream.Plugins.MossTank/AcDream.Plugins.MossTank.dll"
|
||||
test -f "$root/plugins/AcDream.Plugins.MossTank/plugin.json"
|
||||
|
||||
test "$(grep -RIl --include='*.cs' 'LibraryImport(\"kernel32.dll\"' \
|
||||
src/AcDream.App | wc -l)" -eq 1
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
<Project Path="src/AcDream.Platform/AcDream.Platform.csproj" />
|
||||
<Project Path="src/AcDream.Plugin.Abstractions/AcDream.Plugin.Abstractions.csproj" />
|
||||
<Project Path="src/AcDream.Plugins.MossTank/AcDream.Plugins.MossTank.csproj" />
|
||||
<Project Path="src/AcDream.Plugins.Smoke/AcDream.Plugins.Smoke.csproj" />
|
||||
<Project Path="src/AcDream.Runtime/AcDream.Runtime.csproj" />
|
||||
<Project Path="src/AcDream.UI.Abstractions/AcDream.UI.Abstractions.csproj" />
|
||||
</Folder>
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ src/
|
|||
AcDream.UI.Abstractions/ shared UI/input models and contracts
|
||||
AcDream.UI.ImGui/ developer-tool presentation
|
||||
AcDream.Plugin.Abstractions/ BCL-only plugin contracts
|
||||
AcDream.Plugins.Smoke/ example plugin
|
||||
AcDream.Plugins.MossTank/ example plugin
|
||||
|
||||
tests/
|
||||
AcDream.*.Tests/ layer-matched xUnit projects
|
||||
|
|
|
|||
|
|
@ -290,22 +290,16 @@ all four places, list icon column aligned with rows.
|
|||
now throws at `Build` instead of silently ignoring the attribute; stale
|
||||
`SampleData.cs:64` citations corrected to `:69` (Melee Defense's real
|
||||
line).
|
||||
- **Before shipment:** the Smoke plugin's "Icon Smoke" panel
|
||||
(`src/AcDream.Plugins.Smoke/SmokeIconPanel.cs`) keeps `StartVisible=true`
|
||||
on purpose, for the owner's connected gate — flip it to
|
||||
`StartVisible=false` (or drop `AcDream.Plugins.Smoke` from the release
|
||||
payload entirely) once that gate is done. Checked whether the LAUNCHER
|
||||
release payload actually ships it: `tools/publish-bin.ps1`'s
|
||||
`New-PayloadZip` zips App's ENTIRE publish output directory
|
||||
(`[IO.Compression.ZipFile]::CreateFromDirectory`, no file filtering
|
||||
beyond checking `$RequiredFiles` exist) into `client-<rid>.zip`, and
|
||||
`AcDream.App.csproj`'s `CopySmokePluginToPublishOutput` target
|
||||
(`AfterTargets="Publish"`, gated only on `IsCrossTargetingBuild`, not on
|
||||
`Configuration`) copies `plugins/AcDream.Plugins.Smoke/` straight into
|
||||
that same publish directory during `dotnet publish`. **The Smoke plugin
|
||||
— auto-open panel included — ships in today's release zip.** (The
|
||||
MossTank plugin has the equivalent `CopyMossTankPluginToPublishOutput`
|
||||
target and ships the same way; a quick grep shows several of its own
|
||||
panels also set `StartVisible = true` — worth the same "before shipment"
|
||||
look, but out of scope for this residual round, which only covers the
|
||||
Smoke plugin per N9.)
|
||||
- **Owner acceptance (2026-09-06):** the owner visually accepted all three
|
||||
connected gates — the plugin shelf (Slice A), the DAT icon markup
|
||||
(Slice B), and the retained-UI outline-order fix (`761a7519f`, landed
|
||||
alongside this plan's residual round). With the visual gate passed, the
|
||||
Smoke plugin's job as the gate artifact is done. Per owner direction,
|
||||
`src/AcDream.Plugins.Smoke/` (including its "Icon Smoke" proof panel,
|
||||
`SmokeIconPanel.cs`) was deleted outright rather than merely hidden —
|
||||
the shipped-in-the-release-zip risk this ledger flagged above is now
|
||||
moot because the plugin no longer exists to ship. Every build/CI/test/doc
|
||||
reference to it was removed or repointed at the MossTank plugin in the
|
||||
same commit; see `chore(plugins): remove the Smoke gate plugin; MossTank
|
||||
shelf icon 0x06002C41`. MossTank's own panels defaulting to
|
||||
`StartVisible = true` remains open — same look, still out of scope here.
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ icon, and the parser that turns markup into a live `UiElement` tree
|
|||
host.Ui.AddPanel(
|
||||
new PluginPanelDescriptor("main", "MossTank")
|
||||
{
|
||||
IconText = "MT", // fallback initials if IconSurfaceId is 0
|
||||
IconSurfaceId = 7735, // Decal-style bare index OR a full DID — both normalize
|
||||
IconText = "MT", // fallback initials if IconSurfaceId is 0
|
||||
IconSurfaceId = 0x06002C41, // Decal-style bare index OR a full DID — both normalize
|
||||
StartVisible = true,
|
||||
ShowInSidePanel = true,
|
||||
},
|
||||
|
|
@ -32,9 +32,8 @@ host.Ui.AddPanel(
|
|||
|
||||
`RegisterPanel` (same signature, returns `IDisposable`) removes the window
|
||||
independently of the plugin's own lifetime. `RegisterPanelContent` takes an
|
||||
in-memory KSML string instead of a file path — the route `AcDream.Plugins.Smoke`
|
||||
uses for its icon-surface proof panel (`SmokeIconPanel.cs`), when a panel is
|
||||
small enough not to need its own shipped `.xml` asset.
|
||||
in-memory KSML string instead of a file path — the route to reach for when a
|
||||
panel is small enough not to need its own shipped `.xml` asset.
|
||||
|
||||
Every registered window gets a stable persisted key
|
||||
(`plugin:{pluginId}:{windowId}`), drag, resize (where the markup opts in),
|
||||
|
|
@ -263,9 +262,7 @@ There is no way to mix kinds within a single list. Two consequences:
|
|||
that wants the badge look has no `did`-space escape hatch.
|
||||
|
||||
MosswartMassacre-style example — a list column composited from spell ids,
|
||||
with the spell's own raw art DID printed alongside the name for comparison
|
||||
(`AcDream.Plugins.Smoke`'s own proof panel, `SmokeIconPanel.cs`, is exactly
|
||||
this pattern):
|
||||
with the spell's own raw art DID printed alongside the name for comparison:
|
||||
|
||||
```csharp
|
||||
// iconkind="spell": the values MUST be spell ids (what ResolveSpell composites
|
||||
|
|
|
|||
|
|
@ -100,62 +100,7 @@
|
|||
<EmbeddedResource Include="..\..\assets\icons\acdream-client-256.png"
|
||||
LogicalName="AcDream.App.Rendering.Icons.acdream-client-256.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- Build the smoke plugin first and copy it into plugins/AcDream.Plugins.Smoke/ -->
|
||||
<ProjectReference Include="..\AcDream.Plugins.Smoke\AcDream.Plugins.Smoke.csproj">
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
<SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Target
|
||||
Name="CopySmokePluginToBuildOutput"
|
||||
AfterTargets="Build"
|
||||
Condition="'$(IsCrossTargetingBuild)' != 'true'">
|
||||
<PropertyGroup>
|
||||
<_SmokePluginDestDir>$(OutputPath)plugins/AcDream.Plugins.Smoke</_SmokePluginDestDir>
|
||||
</PropertyGroup>
|
||||
<MSBuild
|
||||
Projects="$(MSBuildProjectDirectory)/../AcDream.Plugins.Smoke/AcDream.Plugins.Smoke.csproj"
|
||||
Targets="GetTargetPath"
|
||||
Properties="Configuration=$(Configuration);TargetFramework=$(TargetFramework);RuntimeIdentifier=$(RuntimeIdentifier);OutputPath=$(OutputPath)">
|
||||
<Output TaskParameter="TargetOutputs" ItemName="_SmokePluginBuildTarget" />
|
||||
</MSBuild>
|
||||
<MakeDir Directories="$(_SmokePluginDestDir)" />
|
||||
<Copy
|
||||
SourceFiles="@(_SmokePluginBuildTarget)"
|
||||
DestinationFolder="$(_SmokePluginDestDir)"
|
||||
SkipUnchangedFiles="true" />
|
||||
<WriteLinesToFile
|
||||
File="$(_SmokePluginDestDir)/plugin.json"
|
||||
Overwrite="true"
|
||||
Lines="{ "id": "acdream.smoke", "displayName": "Smoke Plugin", "version": "0.1.0", "entryDll": "AcDream.Plugins.Smoke.dll", "apiVersion": 1 }" />
|
||||
</Target>
|
||||
|
||||
<Target
|
||||
Name="CopySmokePluginToPublishOutput"
|
||||
AfterTargets="Publish"
|
||||
Condition="'$(IsCrossTargetingBuild)' != 'true'">
|
||||
<PropertyGroup>
|
||||
<_SmokePluginPublishDestDir>$(PublishDir)plugins/AcDream.Plugins.Smoke</_SmokePluginPublishDestDir>
|
||||
</PropertyGroup>
|
||||
<MSBuild
|
||||
Projects="$(MSBuildProjectDirectory)/../AcDream.Plugins.Smoke/AcDream.Plugins.Smoke.csproj"
|
||||
Targets="GetTargetPath"
|
||||
Properties="Configuration=$(Configuration);TargetFramework=$(TargetFramework);RuntimeIdentifier=$(RuntimeIdentifier);OutputPath=$(OutputPath)">
|
||||
<Output TaskParameter="TargetOutputs" ItemName="_SmokePluginPublishTarget" />
|
||||
</MSBuild>
|
||||
<MakeDir Directories="$(_SmokePluginPublishDestDir)" />
|
||||
<Copy
|
||||
SourceFiles="@(_SmokePluginPublishTarget)"
|
||||
DestinationFolder="$(_SmokePluginPublishDestDir)"
|
||||
SkipUnchangedFiles="true" />
|
||||
<WriteLinesToFile
|
||||
File="$(_SmokePluginPublishDestDir)/plugin.json"
|
||||
Overwrite="true"
|
||||
Lines="{ "id": "acdream.smoke", "displayName": "Smoke Plugin", "version": "0.1.0", "entryDll": "AcDream.Plugins.Smoke.dll", "apiVersion": 1 }" />
|
||||
</Target>
|
||||
|
||||
<!-- MossTank ships the same way as the smoke plugin, plus its panel markup:
|
||||
<!-- MossTank ships as a copy-only plugin, plus its panel markup:
|
||||
MossTankPlugin resolves mosstank.xml relative to its own assembly, so the
|
||||
two files must land in the same plugin directory. -->
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -41,6 +41,11 @@ public sealed class MossTankPlugin : IAcDreamPlugin
|
|||
new PluginPanelDescriptor("main", "MossTank")
|
||||
{
|
||||
IconText = "MT",
|
||||
// A real installed portal.dat RenderSurface — see
|
||||
// MossTankIconInstalledDatTests for the DAT-presence proof.
|
||||
// IconText remains the fallback if this id is ever absent
|
||||
// from an install.
|
||||
IconSurfaceId = 0x06002C41u,
|
||||
StartVisible = true,
|
||||
ShowInSidePanel = true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<!-- Plugin DLLs are copied to plugins/<id>/ at build of AcDream.App.
|
||||
They must NOT bring AcDream.Plugin.Abstractions.dll with them;
|
||||
the host already owns it. -->
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AcDream.Plugin.Abstractions\AcDream.Plugin.Abstractions.csproj">
|
||||
<Private>false</Private>
|
||||
<ExcludeAssets>runtime</ExcludeAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
using AcDream.Plugin.Abstractions;
|
||||
|
||||
namespace AcDream.Plugins.Smoke;
|
||||
|
||||
/// <summary>
|
||||
/// Slice B proof panel (<c>docs/plans/2026-09-06-plugin-shelf-and-dat-icons.md</c>
|
||||
/// item 9): exercises every new plugin-markup icon surface in one place —
|
||||
/// registered from in-memory KSML via <see cref="IUiRegistry.RegisterPanelContent"/>
|
||||
/// (no plugin-side XML file), with the descriptor's own
|
||||
/// <see cref="PluginPanelDescriptor.IconSurfaceId"/> also set to the bare
|
||||
/// Decal-style index used below, so the shelf button and the panel's own
|
||||
/// <c><icon did="0x165"></c> prove
|
||||
/// <see cref="PluginIcons.Normalize"/> the same way at both sinks.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Review fix round (2026-09-06): <see cref="BareIndexIconId"/> was
|
||||
/// <c>7735</c> (an unverified literal) and is now retail's Melee Defense
|
||||
/// skill icon index <c>0x165</c> — <c>SampleData.cs:69</c> attests
|
||||
/// <c>0x06000165</c> is a real installed-DAT RenderSurface, so the
|
||||
/// descriptor and the bare-index <c><icon></c> both draw ART a tester
|
||||
/// can actually verify against retail, not a guess. The list's
|
||||
/// <c>iconkind="spell"</c> column also had a wiring bug: it fed
|
||||
/// <see cref="PluginSpellInfo.IconId"/> (a raw RenderSurface DID) to the
|
||||
/// SPELL-id resolver, which composites a badge from a SpellTable entry
|
||||
/// looked up by SPELL id — the two numbers are unrelated, so every row
|
||||
/// silently resolved the wrong (or no) composited badge. See
|
||||
/// <see cref="Binding.SpellIds"/>.
|
||||
/// </remarks>
|
||||
internal static class SmokeIconPanel
|
||||
{
|
||||
/// <summary>
|
||||
/// A Decal/VirindiViewService-style bare portal.dat index for retail's
|
||||
/// Melee Defense skill icon (<c>0x06000165</c> — attested in
|
||||
/// <c>src/AcDream.App/UI/Layout/SampleData.cs:69</c>) rather than an
|
||||
/// unverified literal. Deliberately used on BOTH the descriptor and the
|
||||
/// first <c><icon></c> below to prove the host normalizes it
|
||||
/// identically at each sink.
|
||||
/// </summary>
|
||||
public const uint BareIndexIconId = 0x165u;
|
||||
|
||||
/// <summary>A literal, already-normalized RenderSurface DID (a portal icon).</summary>
|
||||
private const uint LiteralDidIconId = 0x06002D14u;
|
||||
|
||||
/// <summary>
|
||||
/// Retail's Strength Other I — the plan's named fallback when the local
|
||||
/// character has not learned any self-castable buff yet.
|
||||
/// </summary>
|
||||
private const uint FallbackSpellId = 1u;
|
||||
|
||||
public static readonly PluginPanelDescriptor Descriptor = new("icons", "Icon Smoke")
|
||||
{
|
||||
IconSurfaceId = BareIndexIconId,
|
||||
StartVisible = true,
|
||||
ShowInSidePanel = true,
|
||||
};
|
||||
|
||||
public const string Markup = """
|
||||
<panel x="60" y="60" w="280" h="220" title="Icon Smoke">
|
||||
<icon x="12" y="28" w="32" h="32" did="0x165" tooltip="Melee Defense skill icon (bare index 0x165)"/>
|
||||
<icon x="52" y="28" w="32" h="32" did="0x06002D14" tooltip="Portal icon 0x06002D14 (literal RenderSurface DID)"/>
|
||||
<icon x="92" y="28" w="32" h="32" spell="{SpellId}" tooltip="composited spell icon (first known self-buff, or Strength Other I)"/>
|
||||
<button x="12" y="68" w="120" h="24" text="Report" icon="0x06002D14" iconkind="did" onclick="{Report}"/>
|
||||
<list x="12" y="100" w="256" h="108" items="{SpellRows}" icons="{SpellIds}"
|
||||
iconkind="spell" selected="{SelectedIndex}"/>
|
||||
</panel>
|
||||
""";
|
||||
|
||||
/// <summary>Binding object for <see cref="Markup"/>. Reads live host state
|
||||
/// on every frame the same way any BCL-only plugin binding would.</summary>
|
||||
internal sealed class Binding
|
||||
{
|
||||
private readonly IPluginHost _host;
|
||||
|
||||
public Binding(IPluginHost host) => _host = host;
|
||||
|
||||
public int SelectedIndex { get; set; } = -1;
|
||||
|
||||
/// <summary>The first spell a plugin markup <c><icon spell=...></c>
|
||||
/// draws — the character's first known self-buff, falling back to
|
||||
/// <see cref="FallbackSpellId"/> (Strength Other I) when nothing is
|
||||
/// learned yet (fresh character, or no live session).</summary>
|
||||
public uint SpellId
|
||||
{
|
||||
get
|
||||
{
|
||||
IReadOnlyList<PluginSpellInfo> known = _host.Automation.Spells.KnownSelfBuffs;
|
||||
return known.Count > 0 ? known[0].SpellId : FallbackSpellId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The first five known self-buffs, or — when the character has
|
||||
/// learned nothing yet — a single-entry fallback to
|
||||
/// <see cref="FallbackSpellId"/>'s REAL catalog entry (never
|
||||
/// fabricated art: if the host cannot resolve spell 1 either, the
|
||||
/// fallback is empty and <see cref="SpellRows"/>/<see cref="SpellIds"/>
|
||||
/// show the honest "no spells known" row instead).
|
||||
/// </summary>
|
||||
private IReadOnlyList<PluginSpellInfo> KnownSelfBuffsOrFallback()
|
||||
{
|
||||
IReadOnlyList<PluginSpellInfo> known = _host.Automation.Spells.KnownSelfBuffs;
|
||||
if (known.Count > 0)
|
||||
return known;
|
||||
return _host.Automation.Spells.TryGet(FallbackSpellId, out PluginSpellInfo info)
|
||||
? new[] { info }
|
||||
: Array.Empty<PluginSpellInfo>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parallel spell-id column for <see cref="SpellRows"/>, feeding the
|
||||
/// list's <c>iconkind="spell"</c> composited badge. Review fix round
|
||||
/// finding 2: this must be the spell id
|
||||
/// (<see cref="PluginSpellInfo.SpellId"/>) that
|
||||
/// <see cref="IMarkupIconResolver.ResolveSpell"/> composites a badge
|
||||
/// from — NOT <see cref="PluginSpellInfo.IconId"/> (the spell's raw
|
||||
/// art DID), which is what this property used to yield. The raw
|
||||
/// <see cref="PluginSpellInfo.IconId"/> is still visible, printed
|
||||
/// alongside the name in <see cref="SpellRows"/>, for anyone
|
||||
/// comparing the composited badge against the plain art tile.
|
||||
/// </summary>
|
||||
public IEnumerable<uint> SpellIds
|
||||
{
|
||||
get
|
||||
{
|
||||
IReadOnlyList<PluginSpellInfo> spells = KnownSelfBuffsOrFallback();
|
||||
if (spells.Count == 0)
|
||||
return new uint[] { 0u };
|
||||
var ids = new List<uint>();
|
||||
foreach (PluginSpellInfo spell in spells)
|
||||
{
|
||||
ids.Add(spell.SpellId);
|
||||
if (ids.Count == 5)
|
||||
break;
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Row text for the first five known self-buffs (or the
|
||||
/// single-entry fallback), with each spell's
|
||||
/// <see cref="PluginSpellInfo.IconId"/> printed alongside its name so
|
||||
/// the composited icon column and the raw art id are both visible in
|
||||
/// one look. A character with no self-buffs known AND no resolvable
|
||||
/// fallback shows one honest "no spells known" row rather than a
|
||||
/// blank list.</summary>
|
||||
public IEnumerable<string> SpellRows
|
||||
{
|
||||
get
|
||||
{
|
||||
IReadOnlyList<PluginSpellInfo> spells = KnownSelfBuffsOrFallback();
|
||||
if (spells.Count == 0)
|
||||
return new[] { "no spells known" };
|
||||
var rows = new List<string>();
|
||||
foreach (PluginSpellInfo spell in spells)
|
||||
{
|
||||
rows.Add($"{spell.Name} (icon 0x{spell.IconId:X8})");
|
||||
if (rows.Count == 5)
|
||||
break;
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
public Action Report =>
|
||||
() => _host.Log.Info(
|
||||
$"smoke icon panel: SpellId={SpellId:X8}, {SpellRows.Count()} spellbook rows");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
using AcDream.Plugin.Abstractions;
|
||||
|
||||
namespace AcDream.Plugins.Smoke;
|
||||
|
||||
public sealed class SmokePlugin : IAcDreamPlugin
|
||||
{
|
||||
private IPluginHost? _host;
|
||||
private int _entitiesSeen;
|
||||
private IDisposable? _iconPanel;
|
||||
|
||||
public void Initialize(IPluginHost host)
|
||||
{
|
||||
_host = host;
|
||||
_host.Log.Info("smoke plugin initialized");
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
_host?.Log.Info("smoke plugin enabled");
|
||||
if (_host is not null)
|
||||
{
|
||||
_host.Events.EntitySpawned += OnEntitySpawned;
|
||||
_host.Log.Info($"smoke plugin sees {_entitiesSeen} entities (replay count at subscribe)");
|
||||
|
||||
// Slice B proof panel (docs/plans/2026-09-06-plugin-shelf-and-dat-icons.md
|
||||
// item 9): in-memory KSML, no plugin-side XML file, exercising
|
||||
// every new icon markup surface.
|
||||
_iconPanel = _host.Ui.RegisterPanelContent(
|
||||
SmokeIconPanel.Descriptor,
|
||||
SmokeIconPanel.Markup,
|
||||
new SmokeIconPanel.Binding(_host));
|
||||
}
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
if (_host is not null)
|
||||
_host.Events.EntitySpawned -= OnEntitySpawned;
|
||||
_iconPanel?.Dispose();
|
||||
_iconPanel = null;
|
||||
_host?.Log.Info($"smoke plugin disabled (saw {_entitiesSeen} entities total)");
|
||||
}
|
||||
|
||||
private void OnEntitySpawned(WorldEntitySnapshot snapshot) => _entitiesSeen++;
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net10.0": {
|
||||
"acdream.plugin.abstractions": {
|
||||
"type": "Project"
|
||||
}
|
||||
},
|
||||
"net10.0/linux-x64": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net10.0": {
|
||||
"acdream.plugin.abstractions": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net10.0": {
|
||||
"acdream.plugin.abstractions": {
|
||||
"type": "Project"
|
||||
}
|
||||
},
|
||||
"net10.0/win-x64": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,9 +23,9 @@ namespace AcDream.App.Tests.Plugins;
|
|||
[Trait("Lane", "InstalledDat")]
|
||||
public sealed class AppAutomationSurfaceIconInstalledDatTests
|
||||
{
|
||||
/// <summary>Retail's Strength Other I — the same well-known spell id
|
||||
/// SmokeIconPanel falls back to when the local character has learned
|
||||
/// nothing yet.</summary>
|
||||
/// <summary>Retail's Strength Other I — a well-known always-present spell
|
||||
/// id, usable as a fallback when the local character has learned nothing
|
||||
/// yet.</summary>
|
||||
private const uint KnownSpellId = 1u;
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ public sealed class LinuxPlatformBoundaryTests
|
|||
Assert.Contains("$(OutputPath)plugins/", project, StringComparison.Ordinal);
|
||||
Assert.Contains("$(PublishDir)plugins/", project, StringComparison.Ordinal);
|
||||
Assert.Equal(
|
||||
4,
|
||||
2,
|
||||
project.Split("Targets=\"GetTargetPath\"", StringSplitOptions.None)
|
||||
.Length - 1);
|
||||
Assert.Contains(
|
||||
|
|
|
|||
65
tests/AcDream.App.Tests/UI/MossTankIconInstalledDatTests.cs
Normal file
65
tests/AcDream.App.Tests/UI/MossTankIconInstalledDatTests.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Tests.Rendering;
|
||||
using AcDream.App.Tests.Rendering.Gpu;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.Content;
|
||||
using AcDream.Core.Items;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Owner direction 2026-09-06 (Smoke-plugin removal +
|
||||
/// <c>docs/plans/2026-09-06-plugin-shelf-and-dat-icons.md</c> follow-up):
|
||||
/// <c>MossTankPlugin</c>'s shelf descriptor now sets a real
|
||||
/// <c>IconSurfaceId</c> instead of relying solely on the <c>"MT"</c> text
|
||||
/// fallback. Mirrors the
|
||||
/// <see cref="AcDream.App.Tests.UI.RetailMarkupIconResolverInstalledDatTests"/>
|
||||
/// installed-DAT gate convention: <see cref="InstalledDatTestPath"/> +
|
||||
/// <c>[Trait("Lane", "InstalledDat")]</c>, <c>Assert.Fail</c> with the
|
||||
/// standard message when no DAT is configured (see docs/release-gate.md).
|
||||
/// </summary>
|
||||
[Trait("Lane", "InstalledDat")]
|
||||
public sealed class MossTankIconInstalledDatTests
|
||||
{
|
||||
/// <summary>The id <c>MossTankPlugin.cs</c> sets as its shelf descriptor's
|
||||
/// <c>IconSurfaceId</c>.</summary>
|
||||
private const uint MossTankShelfIconId = 0x06002C41u;
|
||||
|
||||
[Fact]
|
||||
public void MossTankShelfIconId_IsARealInstalledRenderSurface()
|
||||
{
|
||||
string? datDir = InstalledDatTestPath.Resolve();
|
||||
if (datDir is null)
|
||||
{
|
||||
Assert.Fail(
|
||||
"Lane=InstalledDat requires an installed retail DAT directory; see docs/release-gate.md.");
|
||||
return;
|
||||
}
|
||||
|
||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||
using var adapter = new DatCollectionAdapter(dats);
|
||||
|
||||
// The RenderSurface must exist in Portal or HighRes — the same two
|
||||
// databases RetailMarkupIconResolver.ResolveDid probes before ever
|
||||
// touching TextureCache.
|
||||
bool existsInPortal = adapter.Portal.TryGet<RenderSurface>(MossTankShelfIconId, out _);
|
||||
bool existsInHighRes = adapter.HighRes.TryGet<RenderSurface>(MossTankShelfIconId, out _);
|
||||
Assert.True(
|
||||
existsInPortal || existsInHighRes,
|
||||
$"expected 0x{MossTankShelfIconId:X8} to be a real installed RenderSurface (Portal or HighRes)");
|
||||
|
||||
var device = new RecordingGpuDevice();
|
||||
using var cache = new TextureCache(device, adapter);
|
||||
var icons = new IconComposer(adapter, cache);
|
||||
var objects = new ClientObjectTable();
|
||||
var resolver = new RetailMarkupIconResolver(adapter, cache, icons, objects);
|
||||
|
||||
(uint tex, int w, int h) = resolver.ResolveDid(MossTankShelfIconId);
|
||||
Assert.NotEqual(0u, tex);
|
||||
Assert.True(w > 0 && h > 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,20 +9,20 @@ public class PluginManifestTests
|
|||
{
|
||||
const string json = """
|
||||
{
|
||||
"id": "acdream.smoke",
|
||||
"displayName": "Smoke Test",
|
||||
"id": "acdream.mosstank",
|
||||
"displayName": "MossTank",
|
||||
"version": "0.1.0",
|
||||
"entryDll": "AcDream.Plugins.Smoke.dll",
|
||||
"entryDll": "AcDream.Plugins.MossTank.dll",
|
||||
"apiVersion": 1
|
||||
}
|
||||
""";
|
||||
|
||||
var manifest = PluginManifest.Parse(json);
|
||||
|
||||
Assert.Equal("acdream.smoke", manifest.Id);
|
||||
Assert.Equal("Smoke Test", manifest.DisplayName);
|
||||
Assert.Equal("acdream.mosstank", manifest.Id);
|
||||
Assert.Equal("MossTank", manifest.DisplayName);
|
||||
Assert.Equal("0.1.0", manifest.Version);
|
||||
Assert.Equal("AcDream.Plugins.Smoke.dll", manifest.EntryDll);
|
||||
Assert.Equal("AcDream.Plugins.MossTank.dll", manifest.EntryDll);
|
||||
Assert.Equal(1, manifest.ApiVersion);
|
||||
Assert.Equal([PluginKind.Gameplay], manifest.Kinds);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue