Skip to content

Extract pure game engine + Synergy Capability Resolution Layer#29

Merged
launchswitch merged 11 commits into
mainfrom
feature/engine-view-split
May 29, 2026
Merged

Extract pure game engine + Synergy Capability Resolution Layer#29
launchswitch merged 11 commits into
mainfrom
feature/engine-view-split

Conversation

@launchswitch
Copy link
Copy Markdown
Owner

Summary

Two major features on this branch:

1. Pure Game Engine Extraction (Phase 0–2)

  • Extracts pure simulation logic from GameSession into a standalone GameEngine class (web/src/game/engine/)
  • Clean engine/view separation: engine has zero React/Phaser dependencies
  • Enables headless simulation, AI training, replay validation, and future multiplayer server
  • Includes design doc: docs/multiplayer-engine-split.md

2. Synergy Capability Resolution Layer (Phase 1–6)

  • Declarative capability grant system (synergyCapabilities.ts, synergyCapabilityResolver.ts) replacing ad-hoc setFlag/statMod primitive scanning
  • 10 synergy mechanisms migrated to capabilityGrants: countsAsCity, transportedTroopsStealth, slaveHordeIgnoresZoc, stealthAuraShareRadius, caravanRelayVisionRange, bombardmentRange, amphibiousMovementOnTerrain, positionSwapVerb, caravanPassengerActive, ignoreZoc
  • Consumer systems (zocSystem, fogSystem, movementSystem, activateUnit, combat-action/helpers) now use unitHasCapabilityFromState() instead of manual resolveEffectiveSynergies + flag scanning
  • Emergent rules cleaned up: juggernaut/iron_turtle use ignoreZoc capability; dead emergentIgnoreZoc removed from phantom mode
  • Comprehensive test suite: synergyCapability.test.ts (736 lines, full resolver coverage)
  • Existing synergy/activation/awareness tests updated and passing (188 tests)
  • Design docs: docs/Project Plan Synergy Capability.md, docs/Project Plan Synergy Migration.md

Commits (10)

  1. fd994df — feat: extract pure game engine from GameSession (Phase 0-2)
  2. 1e899ce — fix: address review findings from engine extraction
  3. 3621f80 — Phase 1: Add Synergy Capability Resolution Layer types
  4. 9c84315 — Phase 2: Add Synergy Capability Resolver
  5. 68b539a — Phase 3: Combat Context Enrichment via Capability Resolution
  6. 94cd706 — Phase 4: Add system capability query helpers
  7. f443624 — Phase 5: Migrate Stealth Charge to capability grants
  8. 6c244a3 — Phase 6: Add comprehensive tests for Synergy Capability Resolution Layer
  9. c80bc0e — Style: biome auto-formatting from capability layer implementation
  10. 6af166c — Migrate synergy flags to capability grants; reformat to tabs

Test Status

  • All 188 synergy/activation/awareness tests pass
  • New synergyCapability.test.ts full resolver coverage
  • npm run build clean

Engine/View Split — Creates a pure, stateless game engine layer that
takes (GameState, Action) → (GameState, Events). GameSession becomes
a thin wrapper that delegates to the engine and processes events into
UI feedback.

New files:
- web/src/game/engine/types.ts — EngineResult, EngineEvent, EngineAction,
  GameEngine interface, CombatPreviewResult, AiActivationResult
- web/src/game/engine/engine.ts — All 24 action handlers as pure functions
  (move_unit, end_turn, set_city_production, queue_move, terraform,
  prepare_ability, board_transport, disembark_unit, cancel_production,
  start/cancel research, sacrifice, bastion, maelstrom, oasis, submerge,
  destroy_fort, build_city, summon_unit, etc.)
- web/src/game/engine/index.ts — barrel exports

Changes to GameSession.ts (1520 → 1127 lines, -26%):
- dispatch() delegates 22 of 24 actions to engine.applyAction()
- attack_unit stays session-level (two-phase: preview → animate → apply)
- undo stays session-level (snapshot management)
- processEngineEvents() converts engine events into session feedback
  (audio, animations, UI state)
- All private action handlers removed (now in engine)
- AI loop orchestration stays in GameSession (setTimeout yielding,
  combat queue, animation coordination)

Verified:
- All 1308 game tests pass (0 regressions)
- Web build succeeds (tsc + vite)
- No circular dependencies
- No type errors across engine + session files
- Add canSubmerge guard to applySubmerge to prevent false events on failure
- Remove 6 dead EngineEvent kinds never emitted by engine (combat_resolved,
  domain_learned, domains_absorbed, hit_and_run_retreat,
  enemy_synergy_contact, log)
- Emit research_completed for all newly completed nodes, not just the last
- Add position to unit_sacrificed event (populated before unit deletion)
- Add unitId to bastion_built, maelstrom_declared, oasis_declared events
- Add unitId + position to unit_submerged event
- Update processEngineEvents to use new fields instead of hardcoded
  empty/zero values
- Fix duplicate terraform_failed union member
- New src/systems/synergyCapabilities.ts with SynergyCapabilityName union
  (8 capability names), SynergyCapabilityGrant interface, and
  ResolvedSynergyCapabilities class with terrain-gated queries.
- Extended PairSynergyConfig, EmergentRuleConfig, and ActiveSynergy with
  optional capabilityGrants field in synergyTypes.ts.
- Propagated capabilityGrants at all 4 ActiveSynergy construction sites
  in synergyEngine.ts.
- All existing tests pass (88/88).
- New src/systems/synergyCapabilityResolver.ts with resolveSynergyCapabilities()
  that collects capability grants from pair synergies and triple/emergent stack.
- Evaluates grant conditions against BASE combat context only (non-recursive).
- Grants do not trigger other grants.
- Uses evaluateCondition() from primitiveEvaluator.ts for condition evaluation.
- Returns ResolvedSynergyCapabilities with terrain-gated capability queries.
- All existing tests pass (73/73).
- applyCombatSynergies() now resolves capability grants against base context
  before building an enriched CombatContext with OR'd combat-facet booleans.
- Enriched context used only for primitive resolution; ambush bonus uses base.
- resolveRetreatSynergyEffects() in resolveAftermath.ts also wired with
  capability enrichment (fixes bypass identified in review).
- countsAsWaterForSynergy documented as needing dedicated integration
  (terrain-based, not boolean-OR-able).
- All 1308 tests pass (1 pre-existing optuna failure unrelated).
- Added unitHasSynergyCapability() for quick boolean capability checks
  from non-combat systems (fog, movement, ZoC).
- Added getTerrainSynergyCapabilities() for terrain-gated capability queries.
- Shared hasAnyCapabilityGrants() guard for early-return optimization.
- Shared buildMinimalContext() to reduce CombatContext construction duplication.
- Helpers are stubs ready for future integration when content needs them.
- All tests pass (73/73).
- Added capabilityGrants to charge+river_stealth pair synergy:
  - countsAsChargeForSynergy when isStealthAttack
  - countsAsStealthAttackForSynergy when isCharge on river/swamp terrain
- Existing damage/cooldown-waiver effects unchanged, now fire when either
  base facts OR capability grants satisfy the isCharge AND isStealthAttack condition.
- Granted charge-for-synergy does NOT trigger global charge mechanics
  (enriched context only used for primitive resolution).
- All tests pass (156/156 across synergy test files, 48/48 wiring tests).
- 42 new tests in tests/synergyCapability.test.ts covering:
  - ResolvedSynergyCapabilities unit tests (7 tests)
  - resolveSynergyCapabilities resolver tests (8 tests)
  - Enriched combat context integration tests (5 tests)
  - Primitive evaluator isolation test (1 test)
  - Stealth Charge behavior tests (7 tests)
  - Ambush bonus isolation tests (2 tests)
  - Regression tests for existing synergies (5 tests)
  - System capability query helper tests (7 tests)
- All 198 synergy tests pass (42 new + 156 existing).
- Full suite: 1350/1351 pass (1 pre-existing optuna failure).
Auto-formatting (quotes, indentation) applied by biome during
the capability resolution layer implementation phases.
Migrate synergy mechanisms from setFlag/statMod primitives to the
declarative capability grant system (synergyCapabilities.ts):

- Content layer (synergies/index.ts): countsAsCity, transportedTroopsStealth,
  slaveHordeIgnoresZoc, stealthAuraShareRadius, caravanRelayVisionRange,
  bombardmentRange, amphibiousMovementOnTerrain, positionSwapVerb,
  caravanPassengerActive, ignoreZoc now use capabilityGrants

- Consumer layer: zocSystem, fogSystem, movementSystem, activateUnit,
  combat-action/helpers updated to use unitHasCapabilityFromState()
  instead of resolveEffectiveSynergies+manual flag scanning

- Emergent rules: juggernaut and iron_turtle use ignoreZoc capability
  grant; many_faced phantom's dead emergentIgnoreZoc flag removed

- synergyCapabilities.ts: add 8 new capability names, terrain merge on
  add(), getField/getFields helpers for parameterized grants

- synergyCapabilityResolver.ts: new unitHasCapabilityFromState()
  convenience helper for GameState+Unit consumers

- All 188 synergy/activation/awareness tests pass
- Reformatted all touched files from 2-space to tab indentation
@vercel
Copy link
Copy Markdown

vercel Bot commented May 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
9tribes Ready Ready Preview, Comment May 29, 2026 4:23pm

10 fields migrated from effects primitives to capabilityGrants are now
correctly classified as orphan (no longer written by content, but still
have dispatcher/consumer code): bombardmentRange, amphibiousMovementBonus,
stealthAuraShareRadius, caravanRelayVisionRange, caravanPassengerActive,
slaveHordeIgnoresZoc, countsAsCity, transportedTroopsStealth, positionSwap,
emergentIgnoreZoc. Plus pre-existing orphan multiplierStackValue = 11 total.
@launchswitch launchswitch merged commit 00ab76b into main May 29, 2026
3 checks passed
@launchswitch launchswitch deleted the feature/engine-view-split branch May 31, 2026 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant