The switches used to be held by simply standing near them, and the crown captured
by standing near it - clicking a switch only produced the client's "not implemented
yet" message. This drives both from the original interaction instead:
- Clicking a Crown Switch starts an operation which completes after
CastleSiegeSettings.SwitchPushSeconds (15) and keeps the switch for the guild
until its operator leaves the switch's area. One player per switch; anybody else
clicking it is told another team is on it (C1 B2 14 state 2).
- While one guild holds both switches the crown's shield drops for it, and its
guild master captures the throne by CLICKING the crown and holding it for
CrownHoldTimeSeconds - seeded to 60 now, to match the countdown the client's
registration panel hardcodes. The throne stays contestable until the siege ends.
- The shield now depends on the switches alone, as in the original; the gates and
statues remain what they always were, the obstacle in the way.
The switch info packet (C1 B2 20) is broadcast before any switch-state packet
because the client's "switch released" handler reads its switch table without
checking that it exists - that table is only allocated when the info packet
arrives, so the wrong order crashes the client.
Also fixed while in here:
- The crown registration panel could never be closed: the cancel was only sent
while the master still stood on the crown, which is precisely when the hold does
NOT break. The panel is now closed for the player it was opened for.
- A contested switch was decided by enumeration order.
- Panels opened by the siege are closed when it ends.
- TryCaptureThrone was dead code carrying a second, diverged rule set.
- /csphase advertised the pre-refactor phase names to the client.
- The periodic broadcasts keyed off "UtcNow.Second % n", which silently skips when
a tick runs late; they count ticks now.
The unit tests never compiled against the refactored model - they are migrated to
the state machine and guild ids, and cover the new switch and crown rules. A new
test proves update 105 writes the configuration into an existing database.
ApplyPendingUpdatesTool applies pending configuration updates without the admin
panel; it is [Explicit], so it never runs in a normal test pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The PreBuild targets ran the generator with "--no-build", so it used whatever
assemblies happened to sit in its output folder. When that copy of the data model
was older than a newly added type, the generator regenerated the checked-in
*.Generated.cs files WITHOUT that type and overwrote them in the source tree.
Nothing failed at build time: the C# compile stayed green and docker builds
(-p:ci=true) skip the generator entirely, so they compiled whatever was in the
tree. The damage only surfaced at runtime, when EF validated the model and found
the inherited GameConfiguration.CastleSiegeConfiguration navigation pointing at a
keyless type - the server died on startup with "The entity type
'CastleSiegeConfiguration' requires a primary key to be defined".
Dropping the switch makes the generator build first, so its output always matches
the data model. The regenerated files here are that missing output: the Castle
Siege mappings, and the packet tests for packets whose XML was already committed.
TypedContextModelTests builds the typed context the startup reads its plugin
configurations through - the first one to touch the model - so this class of
breakage fails in seconds without a database.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>