// The strategic AI's task vocabulary and its ordering policy. // // The AI does not search and it does not score. Once a turn it rebuilds a list of candidate // tasks -- which task families it builds at all depends on the player's species -- sorts that // list by a per-task-type priority, and then walks it twice, calling each task's Execute with // pass 0 and then pass 1. This header is the two halves of that which are pure data: the task // type enumeration and the priority function, plus a stable ranking that reproduces the // original's sort exactly. // // Three details are easy to get wrong and are the reason this is a module rather than a table: // // * The priority function is a lookup on the task's type id, but five task types override it. // Two of them (the artifact tasks) override with a CONSTANT that is nothing like their table // entry -- port only the table and they rank last instead of near the top. // * The sort is a stable list sort, descending. Ties therefore keep the order the tasks were // created in, which makes the per-species creation order part of the answer, not an // implementation detail. CreationOrder() carries it. // * The NPC species creates no strategic tasks at all. // // Pure: no state, no I/O, no random draws. // CONFIDENCE: high on the enumeration, the priority table, the sort and the two flagged // priorities (650 / 750 -- constants with one reader and no writer in the original). The // creation order is the call order of the per-family creators, not a claim about what each // creates. What bit 0 of a task's flag byte MEANS is unknown; only its effect is. #pragma once #include #include #include "game/sim/species.h" namespace sots::ai { // The complete task type space. Values are the ids the tasks report for themselves; they index // the priority table directly, so the two ids with no surviving task type are kept as holes // rather than closed up. enum class TaskType : int { Steamroll = 0x00, Explore = 0x01, ExploreInForce = 0x02, EscortGate = 0x03, EscortGateInvade = 0x04, EscortGateInvadeGoal = 0x05, DeployGateAt = 0x06, Colonize = 0x07, ColonizeGoal = 0x08, ColonizeAt = 0x09, Invade = 0x0a, InvadeGate = 0x0b, InvadeGoal = 0x0c, Retired0d = 0x0d, // no task type survives with this id; the priority entry does DefendColonyIncoming = 0x0e, Retired0f = 0x0f, // likewise DefendGateIncoming = 0x10, KillEasterEgg = 0x11, InterceptEnemy = 0x12, Mining = 0x13, MiningReturn = 0x14, AttackBlockade = 0x15, AdvanceIdleShips = 0x16, StockFreighters = 0x17, RespondAttackSystem = 0x18, RespondDefendSystem = 0x19, NodeBore = 0x1a, BuildStations = 0x1b, BuildPoliceShips = 0x1c, BuildDeepScanShips = 0x1d, Raid = 0x1e, RetrieveArtifact = 0x1f, ReturnArtifact = 0x20, }; constexpr int kTaskTypeCount = 0x21; // The name each task type reports for itself. Empty for the two retired ids. const char* TaskTypeName(TaskType t); // True for the two ids that have a priority but no task type. constexpr bool IsRetiredTaskType(TaskType t) { return t == TaskType::Retired0d || t == TaskType::Retired0f; } // The priority every task type gets from the shared table. This is the whole default ranking // policy; higher runs first. Out-of-range ids yield 0, as the original's bounds check does. // // NOTE: for RetrieveArtifact and ReturnArtifact this is NOT the priority those tasks actually // use -- they override it. Prefer PriorityOf(), which applies the overrides. int TablePriority(TaskType t); // The two priorities the Invade / EscortGateInvade overrides use in place of the table's. In the // original these are two standalone integers rather than table entries; they are constants, not // configuration -- each has exactly one reader in the whole image (its own override) and no // writer anywhere, so there is nothing that could change them at runtime. Kept as a struct so a // caller can still substitute them in a test. struct TaskPriorityPolicy { int flaggedInvade = 650; int flaggedEscortGateInvade = 750; }; // One task, as far as ranking is concerned. struct RankedTask { TaskType type = TaskType::Steamroll; // Bit 0 of the task's flag byte. When SET, Invade and EscortGateInvade take the higher // priority from TaskPriorityPolicy instead of the table's; when clear they fall through to // the table. Deliberately named after the bit and not after a meaning: an earlier reading // called this "committed" AND had the polarity backwards, and what the bit actually means is // still unknown -- the two bytes on an Invade task that look like commitment state live // elsewhere on the object. bool priorityFlagBit0 = false; // AttackBlockade is the one task whose priority depends on what other tasks exist; the // caller supplies the result. Ignored for every other type. bool overridePriority = false; int priority = 0; // Opaque to this module. Carried through the ranking so callers can recover their own task. const void* handle = nullptr; }; // The priority a task actually ranks by: the table, with the five overrides applied. int PriorityOf(const RankedTask& t, const TaskPriorityPolicy& policy); // Rank a candidate list the way the original does: a STABLE sort, descending by PriorityOf. // Equal priorities keep their input order, which is why the input order matters -- see // CreationOrder(). void Rank(std::vector& tasks, const TaskPriorityPolicy& policy); // Which task families a species builds candidates for, in the order they are built. Ties in // Rank() are broken by this order, so it is part of the ordering policy. // // Four distinct arms: the NPC species builds nothing; the Hiver arm is the only one that builds // the gate families; the Zuul arm is the only one that builds NodeBore; everyone else shares a // fourth. Two families -- DefendColonyIncoming and DefendGateIncoming -- are additionally gated // on the player's policy value being non-zero, and DefendGateIncoming is Hiver-only even inside // the Hiver arm's own gate. // // `policyNonZero` is the player's policy field; pass false to suppress the defensive families. std::vector CreationOrder(sim::Species species, bool policyNonZero); // True when this species builds no strategic tasks at all. constexpr bool BuildsNoTasks(sim::Species species) { return species == sim::Species::NPC; } } // namespace sots::ai