Item H (slice-1 fix round), six sub-parts: 1. Restored VtankMetaProfileSerializerTests.LoadsKnownTypedCondActRecord and SignedHighBitLandblockIdRoundTripsExactly (the latter adapted from the deleted RoundTripPreservesEveryVtankConditionActionAndEmbeddedNav's LandblockEquals/LandcellEquals coverage of unchecked((int)0x8B370000u)), both deleted as collateral damage of an unrelated file move in commit0d10399e0. Neither calls the deleted VtankMetaProfileSerializer.Save writer (demoted to import-only in3ff9461ef) — both are pure reader assertions. 2. MetafSerializer.SaveMeta now refuses (throws InvalidOperationException naming the count) to silently drop a disabled MetaRule: real VTank/ metaf has zero concept of "disabled" (confirmed: metaf_monolithic.py has no "enabled"/"disabled" occurrences anywhere), so MetaRule.Enabled is a MossTank-only extension with no metaf-compatible marker. A new SaveMeta(profile, dropDisabledRules: true) overload lets a caller accept the loss explicitly. MossTankMetaProfileStore.WriteLegacyExport (the .af convenience mirror beside MossTank's own fully-fidelity JSON storage) deliberately does NOT opt in — it leaves that mirror stale and logs a warning via its existing try/catch rather than losing the rule. Recorded as gap 6 in docs/research/vtank-kb/07-meta-and-expressions.md section 5. 3. New VtankProfilesDefault (src/AcDream.App/Plugins/): the graphical host's default VtankProfiles root (<DataDirectory>/vtank), extracted out of Program.cs's inline Path.Combine call into its own pure, injectable-root function specifically so the "Path.Combine only, never a hard-coded Windows path" guarantee is a real, failable Linux-path unit test (VtankProfilesDefaultTests.ResolveIsBuiltWithPathCombineOnly) rather than something only checkable by reading the source — the pattern item F's VtankProfileDirectory rewrite removed when Resolve/PortableDefault moved out of the plugin. 4. New BuffedDoubleRequirementDoesNotApplyBonusWhenBaseKeyIsAbsent: the KeyExistsDouble gate (ComputedItemInfo.cs:234) already existed in BuffedDouble, but only the int side (BuffedIntRequirementDoesNotApplyBonusWhenBaseKeyIsAbsent) had a pinning test. 5/6. DoubleSpellBonuses gained an explicit Change field (KB doc 05 section 2.2: "additive unless the static table's Change==1, in which case multiplicative", ComputedItemInfo.cs:244), replacing BuffedDouble's prior `(int)bonus.Bonus == 1` magnitude-based proxy — that proxy only worked because every multiplicative bonus in the current 19 rows happens to fall in [1.0, 2.0) and every additive one happens to be under 1.0; it would have silently mis-branched on a future row like an additive 1.5 or a multiplicative 2.0+. Every existing row's Change value was derived mechanically from its old proxy result (no behavior change for the current table), and BuffedDoubleRequirementAppliesAdditiveBonusWhenBaseKeyExists/ AppliesMultiplicativeBonusWhenChangeIsSet pin both branches through the real named field. Full MossTank suite: 574 -> 581. App.Tests (Plugin|LaunchOptions|RuntimeOptions filter): 135 -> 137. Core.Tests (Plugin filter): 50/50 (no change, no Core-side edits this item). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
69 lines
3.2 KiB
C#
69 lines
3.2 KiB
C#
using System.Globalization;
|
|
|
|
namespace AcDream.Plugins.MossTank.Tests;
|
|
|
|
/// <summary>
|
|
/// Item H (Campaign VT slice-1 fix round): restores two reader-only tests a
|
|
/// prior commit (0d10399e0, "A2 VTank profile directory resolution") deleted
|
|
/// as collateral damage of an unrelated file move — <c>LoadsKnownTypedCondActRecord</c>
|
|
/// and the signed-high-bit round trip embedded in the deleted
|
|
/// <c>RoundTripPreservesEveryVtankConditionActionAndEmbeddedNav</c>. Both are
|
|
/// adapted to the reader-only contract <c>VtankMetaProfileSerializer</c> has
|
|
/// had since it was demoted to a one-shot <c>.met</c> import (commit
|
|
/// 3ff9461ef, "MossTank does not implement .met"): neither calls the deleted
|
|
/// <c>Save</c> writer.
|
|
/// </summary>
|
|
public sealed class VtankMetaProfileSerializerTests
|
|
{
|
|
[Fact]
|
|
public void LoadsKnownTypedCondActRecord()
|
|
{
|
|
const string source = "1\r\nCondAct\r\n5\r\nCType\r\nAType\r\n"
|
|
+ "CData\r\nAData\r\nState\r\nn\r\nn\r\nn\r\nn\r\nn\r\n1\r\n"
|
|
+ "i\r\n1\r\ni\r\n2\r\ni\r\n0\r\ns\r\n/say ready\r\n"
|
|
+ "s\r\nDefault\r\n";
|
|
|
|
Assert.True(VtankMetaProfileSerializer.TryLoad(
|
|
source, out MetaProfile profile, out string error), error);
|
|
|
|
MetaRule rule = Assert.Single(profile.Rules);
|
|
Assert.Equal(MetaConditionKind.Always, rule.Condition.Kind);
|
|
Assert.Equal(MetaActionKind.ChatCommand, rule.Action.Kind);
|
|
Assert.Equal("/say ready", rule.Action.Text);
|
|
Assert.Equal("Default", rule.State);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Condition type 17 (LandblockEquals) reads a raw signed decimal int
|
|
/// line (<c>ReadCondition</c> case 17: <c>reader.Expect("i");
|
|
/// value.Number = reader.ReadInt();</c>) into a <see cref="double"/>
|
|
/// field. A real landblock id with the high bit set — 0x8B370000, a
|
|
/// legitimate 32-bit landblock/object id — becomes negative once cast to
|
|
/// a signed <see cref="int"/> (VTank's own on-disk encoding, exactly
|
|
/// what the deleted round-trip test exercised via
|
|
/// <c>unchecked((int)0x8B370000u)</c>); this pins that the reader
|
|
/// recovers the exact same bit pattern rather than silently clamping or
|
|
/// mis-widening it (a double exactly represents every 32-bit int, so
|
|
/// this is a real assertion, not a tautology once the string form is
|
|
/// the true value under test).
|
|
/// </summary>
|
|
[Fact]
|
|
public void SignedHighBitLandblockIdRoundTripsExactly()
|
|
{
|
|
int highBitValue = unchecked((int)0x8B370000u);
|
|
Assert.True(highBitValue < 0, "0x8B370000 must decode as negative once cast to Int32.");
|
|
string source = "1\r\nCondAct\r\n5\r\nCType\r\nAType\r\n"
|
|
+ "CData\r\nAData\r\nState\r\nn\r\nn\r\nn\r\nn\r\nn\r\n1\r\n"
|
|
+ "i\r\n17\r\ni\r\n0\r\n"
|
|
+ "i\r\n" + highBitValue.ToString(CultureInfo.InvariantCulture) + "\r\n"
|
|
+ "i\r\n0\r\n"
|
|
+ "s\r\nDefault\r\n";
|
|
|
|
Assert.True(VtankMetaProfileSerializer.TryLoad(
|
|
source, out MetaProfile profile, out string error), error);
|
|
|
|
MetaRule rule = Assert.Single(profile.Rules);
|
|
Assert.Equal(MetaConditionKind.LandblockEquals, rule.Condition.Kind);
|
|
Assert.Equal((double)highBitValue, rule.Condition.Number);
|
|
}
|
|
}
|