Build against plugin API 0.1.19-mm.1; item header words from the host
The contract now reports each object's description header, so the item record carries the words the original's framework filed from it: the weenie and second header flags, the physics description flags, the setup id, the scale key (0.0 whenever a scale was sent, as recorded), the hook type, the parent and parent location, and the framework's own summary word (key 218103831: stackable, has a position, lockable, inscribable, usable, container), derived from the header by a rule that matches all 2,220 recorded items carrying both words. With the header flags known, key presence follows the header exactly instead of guessing from non-zero values: a recorded Pyreal Mote now rebuilds key for key. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0af4efb6b6
commit
50a173ef25
5 changed files with 121 additions and 5 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -28,3 +28,4 @@ Thumbs.db
|
||||||
|
|
||||||
# Frame recordings from live runs
|
# Frame recordings from live runs
|
||||||
*.jsonl
|
*.jsonl
|
||||||
|
packages-staging/
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
<!-- The plugin contract, compile-only, from the .nupkg in ./packages-local
|
<!-- The plugin contract, compile-only, from the .nupkg in ./packages-local
|
||||||
(downloaded from the OpenAC release of the same version). The host
|
(downloaded from the OpenAC release of the same version). The host
|
||||||
supplies the contract assembly at runtime. -->
|
supplies the contract assembly at runtime. -->
|
||||||
<PluginApiPackageVersion>0.1.19-mmpreview.1</PluginApiPackageVersion>
|
<PluginApiPackageVersion>0.1.19-mm.1</PluginApiPackageVersion>
|
||||||
<RestorePackagesWithLockFile>false</RestorePackagesWithLockFile>
|
<RestorePackagesWithLockFile>false</RestorePackagesWithLockFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,9 @@ internal interface IItemHeaderSource
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The contract this plugin builds against exposes none of these words, so
|
/// The header words from the object description the host kept for the item.
|
||||||
/// every one is absent. Replace this when the contract grows them.
|
/// An item without one (built from a vendor listing, say) has none, and every
|
||||||
|
/// word is then absent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class HostItemHeaderSource : IItemHeaderSource
|
internal sealed class HostItemHeaderSource : IItemHeaderSource
|
||||||
{
|
{
|
||||||
|
|
@ -72,5 +73,50 @@ internal sealed class HostItemHeaderSource : IItemHeaderSource
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemHeaderWords Read(in PluginInventoryItem item) => default;
|
public ItemHeaderWords Read(in PluginInventoryItem item)
|
||||||
|
{
|
||||||
|
if (item.Header is not { } header)
|
||||||
|
return default;
|
||||||
|
return new ItemHeaderWords
|
||||||
|
{
|
||||||
|
WeenieHeaderFlags = header.WeenieHeaderFlags,
|
||||||
|
WeenieHeaderFlags2 = header.WeenieHeaderFlags2,
|
||||||
|
PhysicsDescriptionFlags = header.PhysicsDescriptionFlags,
|
||||||
|
PhysicsWord = header.WeenieHeaderFlags is uint weenie
|
||||||
|
? FrameworkFlags(weenie, header.PhysicsDescriptionFlags ?? 0u, item.ItemType, header.ObjectDescriptionFlags ?? item.PublicFlags)
|
||||||
|
: null,
|
||||||
|
SetupId = header.SetupId,
|
||||||
|
// The framework filed 0.0 under its scale key whenever the
|
||||||
|
// physics description carried a scale, whatever the scale was.
|
||||||
|
Scale = header.Scale is null ? null : 0.0,
|
||||||
|
ParentObjectId = header.ParentObjectId,
|
||||||
|
ParentLocation = header.ParentLocation is uint location ? unchecked((int)location) : null,
|
||||||
|
HookType = header.HookType is uint hook ? unchecked((int)hook) : null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The framework's own summary word it kept per object (key 218103831),
|
||||||
|
/// derived from the header: stackable, has a position, lockable,
|
||||||
|
/// inscribable, usable, a container. Checked against every recorded item
|
||||||
|
/// that carries both this word and the header flags.
|
||||||
|
/// </summary>
|
||||||
|
internal static uint FrameworkFlags(uint weenieFlags, uint physicsFlags, uint itemType, uint descriptionFlags)
|
||||||
|
{
|
||||||
|
uint flags = 0u;
|
||||||
|
if ((weenieFlags & 0x3000u) != 0u)
|
||||||
|
flags |= 0x01u; // stack size or maximum stack size sent
|
||||||
|
if ((physicsFlags & 0x8000u) != 0u)
|
||||||
|
flags |= 0x02u; // a position sent
|
||||||
|
if ((itemType & 0x20000u) != 0u)
|
||||||
|
flags |= 0x04u; // a lockable type
|
||||||
|
if ((descriptionFlags & 0x2u) != 0u)
|
||||||
|
flags |= 0x10u; // inscribable
|
||||||
|
if ((weenieFlags & 0xC00u) != 0u)
|
||||||
|
flags |= 0x20u; // uses left or total uses sent
|
||||||
|
if ((weenieFlags & 0x6u) != 0u)
|
||||||
|
flags |= 0x80u; // item or pack capacity sent
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"apiVersion": 1,
|
"apiVersion": 1,
|
||||||
"kinds": ["Gameplay"],
|
"kinds": ["Gameplay"],
|
||||||
"hosts": ["Graphical", "Headless"],
|
"hosts": ["Graphical", "Headless"],
|
||||||
"minHostVersion": "0.1.18",
|
"minHostVersion": "0.1.19",
|
||||||
"capabilitiesVersion": 1,
|
"capabilitiesVersion": 1,
|
||||||
"capabilities": [
|
"capabilities": [
|
||||||
{ "name": "network", "note": "Streams telemetry, vitals, chat, inventory and stats to the Mosswart Overlord backend, and receives shared vitals and chat-box commands from it." },
|
{ "name": "network", "note": "Streams telemetry, vitals, chat, inventory and stats to the Mosswart Overlord backend, and receives shared vitals and chat-box commands from it." },
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
using AcDream.Plugin.Abstractions;
|
||||||
|
using OpenAC.MosswartMassacre.Inventory;
|
||||||
|
|
||||||
|
namespace OpenAC.MosswartMassacre.Tests.Inventory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Items exactly as the original recorded them from a live game (the dumps in
|
||||||
|
/// the Mag-Plugins repository), rebuilt from the header words the host now
|
||||||
|
/// reports: the key tables must come out the same, value for value.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RecordedItemTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void A_recorded_pyreal_mote_rebuilds_key_for_key()
|
||||||
|
{
|
||||||
|
// Barris.json, the first item: a Pyreal Mote in a side pack, never
|
||||||
|
// appraised. Its IntValues and DoubleValues as recorded:
|
||||||
|
var expectedInts = new Dictionary<int, int>
|
||||||
|
{
|
||||||
|
[218103847] = 137345, [218103830] = 33556406, [218103832] = 271151128,
|
||||||
|
[19] = 10, [218103808] = 6353, [218103809] = 7208, [218103834] = 128,
|
||||||
|
[218103843] = 524296, [218103835] = 18, [218103826] = 128, [218103831] = 17,
|
||||||
|
[218103814] = 1, [218103815] = 1, [5] = 1, [218103810] = 1342431769,
|
||||||
|
[218103822] = 0, [218103828] = 2,
|
||||||
|
};
|
||||||
|
var expectedDoubles = new Dictionary<int, double> { [167772170] = 0.0 };
|
||||||
|
|
||||||
|
var item = new PluginInventoryItem(
|
||||||
|
unchecked((uint)-2126483018), 6353u, "Pyreal Mote", 128u, 1342431769u, 0u, 0u, 0u,
|
||||||
|
524296u, 128u, 18u, 1, 0, 0, 0u, 0, 0, 0u, false, 0d, 0, 0, 0, 0d, 0, 0, 0)
|
||||||
|
{
|
||||||
|
ObjectClass = PluginObjectClass.Misc,
|
||||||
|
IconId = 0x06000000u + 7208u,
|
||||||
|
MaximumStackSize = 1,
|
||||||
|
Burden = 1,
|
||||||
|
Value = 10,
|
||||||
|
Header = new PluginObjectHeader(
|
||||||
|
WeenieHeaderFlags: 271151128u,
|
||||||
|
WeenieHeaderFlags2: null,
|
||||||
|
PhysicsDescriptionFlags: 137345u,
|
||||||
|
PhysicsState: null,
|
||||||
|
ObjectDescriptionFlags: 18u,
|
||||||
|
SetupId: 33556406u,
|
||||||
|
Scale: 1.0f,
|
||||||
|
HookType: 2u,
|
||||||
|
ParentObjectId: null,
|
||||||
|
ParentLocation: null,
|
||||||
|
UseRadius: null),
|
||||||
|
};
|
||||||
|
|
||||||
|
MyWorldObject record = DecalItemBuilder.Create(new ItemSource(
|
||||||
|
item, null, false, 0, HostItemHeaderSource.Instance.Read(item)));
|
||||||
|
|
||||||
|
Assert.Equal(expectedInts.OrderBy(p => p.Key), record.IntValues.OrderBy(p => p.Key));
|
||||||
|
Assert.Equal(expectedDoubles.OrderBy(p => p.Key), record.DoubleValues.OrderBy(p => p.Key));
|
||||||
|
Assert.Equal("Pyreal Mote", record.StringValues[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
// Values of key 218103831 recorded for items, with their header words.
|
||||||
|
[InlineData(0x1000u, 0u, 0u, 0u, 0x01u)] // a stack
|
||||||
|
[InlineData(0u, 0x8000u, 0u, 0u, 0x02u)] // on the ground
|
||||||
|
[InlineData(0u, 0u, 0x20000u, 0u, 0x04u)] // lockable
|
||||||
|
[InlineData(0u, 0u, 0u, 0x2u, 0x10u)] // inscribable
|
||||||
|
[InlineData(0x800u, 0u, 0u, 0u, 0x20u)] // uses
|
||||||
|
[InlineData(0x2u, 0u, 0u, 0x12u, 0x90u)] // a pack
|
||||||
|
public void The_frameworks_summary_word(uint weenie, uint physics, uint itemType, uint description, uint expected) =>
|
||||||
|
Assert.Equal(expected, HostItemHeaderSource.FrameworkFlags(weenie, physics, itemType, description));
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue