diff --git a/src/circuit/grpc/CommandDispatch.cpp b/src/circuit/grpc/CommandDispatch.cpp index 03ad81db..74e56cff 100644 --- a/src/circuit/grpc/CommandDispatch.cpp +++ b/src/circuit/grpc/CommandDispatch.cpp @@ -5,6 +5,7 @@ #include "grpc/CommandDispatch.h" #include "grpc/FactoryProductionPolicy.h" #include "grpc/GrpcLog.h" +#include "grpc/NativeCommandOptions.h" #include "grpc/StockQueueReader.h" #include "grpc/StockQueueTrace.h" #include "grpc/TacticalNativeState.h" @@ -54,11 +55,6 @@ namespace { constexpr float kMaximumTacticalAreaRadius = 2048.0f; constexpr std::size_t kMaximumTacticalQueueEntries = 64; -short TacticalOptions(::highbar::v1::NativeQueuePolicy policy) { - return policy == ::highbar::v1::NATIVE_QUEUE_POLICY_APPEND - ? UNIT_COMMAND_OPTION_SHIFT_KEY : 0; -} - static_assert(EngineFacingForNativeBuild(::highbar::v1::NATIVE_BUILD_FACING_NORTH) == UNIT_FACING_NORTH); static_assert(EngineFacingForNativeBuild(::highbar::v1::NATIVE_BUILD_FACING_EAST) == UNIT_FACING_EAST); static_assert(EngineFacingForNativeBuild(::highbar::v1::NATIVE_BUILD_FACING_SOUTH) == UNIT_FACING_SOUTH); @@ -317,7 +313,7 @@ bool DispatchTacticalCommand(::circuit::CCircuitAI* ai, } return dispatch_under_external_control([&] { unit->CmdBuild(def, native->GetPos(), UNIT_NO_FACING, - TacticalOptions(body.queue_policy())); + StockFactoryProductionOptions()); }); #else return DispatchFactoryProductionIfAllowed( diff --git a/src/circuit/grpc/NativeCommandOptions.h b/src/circuit/grpc/NativeCommandOptions.h new file mode 100644 index 00000000..6e04a70c --- /dev/null +++ b/src/circuit/grpc/NativeCommandOptions.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-only +#pragma once + +#include "highbar/live_control.pb.h" + +#include "ExternalAI/Interface/AISCommands.h" + +namespace circuit::grpc { + +constexpr short TacticalOptions(::highbar::v1::NativeQueuePolicy policy) { + return policy == ::highbar::v1::NATIVE_QUEUE_POLICY_APPEND + ? UNIT_COMMAND_OPTION_SHIFT_KEY : 0; +} + +// Stock Recoil interprets SHIFT on a factory build command as a quantity +// multiplier of five. Queue append is already the factory's default, so the +// admitted count-one intent must reach the engine without modifier options. +constexpr short StockFactoryProductionOptions() { + return 0; +} + +} // namespace circuit::grpc diff --git a/tests/unit/tactical_native_state_test.cc b/tests/unit/tactical_native_state_test.cc index ff4b570a..ab4bdff8 100644 --- a/tests/unit/tactical_native_state_test.cc +++ b/tests/unit/tactical_native_state_test.cc @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only #include "grpc/TacticalNativeState.h" +#include "grpc/NativeCommandOptions.h" #include @@ -45,6 +46,29 @@ TEST(TacticalNativeState, StockFactoryReplaceNeverAliasesAppend) { StockFactoryQueuePolicy::Replace, true)); } +TEST(TacticalNativeState, StockFactoryCountOneUsesDefaultAppendWithoutQuantityMultiplier) { + constexpr auto stock_factory_count = [](short options) { + return (options & UNIT_COMMAND_OPTION_SHIFT_KEY) != 0 ? 5 : 1; + }; + constexpr std::size_t queued_before = 2; + + const short production_options = StockFactoryProductionOptions(); + EXPECT_EQ(production_options, 0); + EXPECT_EQ(queued_before + stock_factory_count(production_options), 3u); + + // This is the previous production mapping. It proves the regression would + // observe five new engine queue entries rather than accepting count one. + const short generic_append = TacticalOptions( + ::highbar::v1::NATIVE_QUEUE_POLICY_APPEND); + EXPECT_EQ(generic_append, UNIT_COMMAND_OPTION_SHIFT_KEY); + EXPECT_EQ(queued_before + stock_factory_count(generic_append), 7u); + + // Ordinary tactical commands keep their established append modifier. + EXPECT_EQ(TacticalOptions(::highbar::v1::NATIVE_QUEUE_POLICY_APPEND), + UNIT_COMMAND_OPTION_SHIFT_KEY); + EXPECT_EQ(TacticalOptions(::highbar::v1::NATIVE_QUEUE_POLICY_REPLACE), 0); +} + TEST(TacticalNativeState, FinalQueueFenceRunsAfterControlAndBeforeEffect) { std::vector events; EXPECT_TRUE(DispatchAfterTacticalControlAndFinalFence(false,