Skip to content

feat: GameVariant encapsulation — variant-driven CC4 env construction#17

Merged
PaulHax merged 6 commits into
pr-11-refactorfrom
game-variant-encapsulation
May 9, 2026
Merged

feat: GameVariant encapsulation — variant-driven CC4 env construction#17
PaulHax merged 6 commits into
pr-11-refactorfrom
game-variant-encapsulation

Conversation

@PaulHax

@PaulHax PaulHax commented May 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Bundle CC4 game-rule axes (red_agent, target_weight, op_zone_servers, resilience_roles, num_steps) into a frozen GameVariant dataclass with named presets (CC4_STOCK, CIA_RESILIENCE, CIA_C/I/A); both backends read from the variant; recipe YAMLs reference variants by name.
  • New JaxborgScenarioGenerator (CybORG) and threaded op_zone_min_servers through topology.build_topology / ScenarioEnv / FsmRedCC4Env (JAX) so op-zone subnet server count can be fixed when needed.
  • New cyborg_env_factory (make_cyborg_env + explicit reset_cyborg_env + CyborgReset) and jax_env_factory (make_jax_env); recipe.py exposes train_variant / eval_variant resolvers and projections expose TRAIN_VARIANT / EVAL_VARIANT. Drops RED_AGENT / RESILIENCE_MODE / RESILIENCE_TARGET_WEIGHT keys, the make_fsm_red_env constructor, and string-matching "needs roles" predicates. assign_resilience_roles now raises on <3 op-zone server candidates instead of silently truncating.

Stacking

Base: pr-11-refactor (#15). Stacks on top of that branch.

Test plan

  • uv run pytest -x — 767/767 fast tests pass (incl. 19 new tests covering GameVariant, JaxborgScenarioGenerator, reset_cyborg_env, and assign_resilience_roles precondition).
  • uv run ruff check . && ruff format --check . clean.
  • Slow parity goldens (uv run pytest -m slow tests/differential tests/l3): 112 passed / 100 skipped (no parity regressions on stock-CC4 path; op_zone_min_servers=None keeps topology byte-identical).
  • Stock-CC4 + resilience smoke trains (3 seeds × 3M steps, JAX): final rewards in IMPL's −1854 ± 46 band.

The two fix(gate) commits in the stack are drive-by fixes for a pre-existing bug in scripts/dev/parity/gate.py (its pytest -p no:xdist -m slow invocation errored before pytest could run, since addopts="-n auto" from pyproject.toml left an unparseable -n auto arg). Happy to split them into a separate small PR if preferred.

Cross-backend simulator drift (independent of this PR)

The L4 cross-backend equivalence stage of the parity gate flags a JAX↔CybORG simulator gap of ~−275 reward both in stock-CC4 and resilience modes (vs the historical matched-v2 +5.4 ± 58 EQUIVALENT). A subagent investigation confirmed the L4 harness ignores variants entirely — both rollouts run vanilla CC4 regardless of --recipe, so this drift is pre-existing and untouched by this branch. It needs investigation independent of this work; bisecting the ~10 commits on the FSM/red/selector path between 2026-04-25 and 67a9ce2 is the natural next step.

@PaulHax PaulHax force-pushed the game-variant-encapsulation branch from 32c8a04 to 9d08fd7 Compare May 8, 2026 18:54
PaulHax added 3 commits May 8, 2026 15:05
Bundles CC4 game-rule axes (red_agent, target_weight, op_zone_servers,
resilience_roles, num_steps) into a frozen GameVariant dataclass with
named presets (CC4_STOCK, CIA_RESILIENCE, CIA_C/I/A). Both backends
read from the variant; recipe YAMLs reference variants by name.

- New game_variant + game_variants modules; VARIANTS registry.
- New JaxborgScenarioGenerator: subclass with op-zone server count
  fixed when op_zone_servers is set.
- New cyborg_env_factory: make_cyborg_env (pure construction) +
  reset_cyborg_env (explicit reset + role-map inject) +
  CyborgReset dataclass. No proxy wrappers.
- New jax_env_factory: make_jax_env(variant, ...).
- topology.build_topology / ScenarioEnv / FsmRedCC4Env now thread
  op_zone_min_servers through to constrain op-zone subnet server count.
- recipe.py: train_variant(recipe) / eval_variant(recipe) resolvers;
  projections expose TRAIN_VARIANT / EVAL_VARIANT.
- Drop RED_AGENT / RESILIENCE_MODE / RESILIENCE_TARGET_WEIGHT keys
  and string-matching "needs roles" predicates.
- Migrate all 5 CybORG-side rollout sites and 3 JAX-side files
  (test_red_selectors, check_red_bias, ippo_jax) to variant API.
- assign_resilience_roles raises ValueError on <3 candidates instead
  of silently truncating (op_zone_servers=3 makes <3 unreachable in
  CIA mode anyway).
- Recipe YAMLs migrated; no back-compat.
pyproject sets addopts='-n auto -m "not slow"'; -p no:xdist disables
the xdist plugin but leaves '-n auto' as an unrecognized arg, so the
slow-parity stage errored out before running. -o 'addopts=' clears the
inherited args; the explicit -m slow + paths run as intended.
Previous '-o addopts=' cleared *all* addopts including '-n auto', so
slow parity tests ran on a single core and projected to 10+ hours
versus IMPL's ~40 min target. Set addopts='-n auto' explicitly: drops
the '-m not slow' filter (so explicit '-m slow' applies) while keeping
xdist parallelism.
@PaulHax PaulHax force-pushed the game-variant-encapsulation branch from 9d08fd7 to 1ad27a2 Compare May 8, 2026 19:06
PaulHax added 3 commits May 8, 2026 16:01
assign_resilience_roles_from_const argsorts noise scores with non-candidate
hosts pushed to +inf; with <3 op-zone server candidates the inf-tail leaks
non-candidate indices into ranks[0..2], silently tagging non-op-zone hosts.
The CIA metric reads those tags and would produce a meaningless score.

Validate eagerly at make_jax_env construction instead of masking the
symptom inside the JAX function:

- generative: reject variants whose worst-case candidate count (2 *
  alpha-floor + 2 beta-min) is <3 — catches op_zone_servers=0.
- snapshot: load each topology_path and count active op-zone servers,
  rejecting any below 3.

Adds count_resilience_candidates(const) for the eager check.
Two latent issues from PR #11 that GameVariant encapsulation made detectable:

1. CIA selectors hardcoded `_FIXED_CIA_TARGET_WEIGHT=10.0` and ignored
   `variant.target_weight`, while CybORG-side `CRedAgent.with_weight`
   honored it (defaulted to 5.0 from the GameVariant default). Drop the
   constant, plumb the variant's `target_weight` through `_cia_c/i/a`,
   and set `CIA_C/I/A.target_weight=10.0` so both sides read from one
   source of truth.

2. CybORG `_CIARedAgent` mirrored only host-bias from JAX, not the
   action-prob override at FSM_R (root, undiscovered) that shifts mass
   toward Impact + Degrade. Override `state_transitions_probability` on
   `_CIARedAgent` to match `_CIA_PROB_MATRIX[FSM_R]` element-wise.

Adds a parity test asserting the JAX `_CIA_PROB_MATRIX[FSM_R]` row and
the CybORG `_CIARedAgent.state_transitions_probability['R']` row agree.
The L4 cross-backend equivalence stage was previously running vanilla CC4
on both sides regardless of `--recipe`, so any resilience-mode "drift" was
actually policy-OOD (resilience-trained policy on stock-CC4 eval).

Add a single `resolve_eval_variant(recipe_name=..., checkpoint=..., default=...)`
helper in `jaxborg.recipe` that resolves the variant via explicit recipe →
checkpoint sidecar → default. Thread it through:

- Parity harness: `transfer_cli`, `jax_rollout`, `cyborg_rollout`,
  `cyborg_bridge`, `diagnostics`. JAX side switches from direct
  `FsmRedCC4Env(...)` to `make_jax_env(variant)`. CybORG bridge becomes a
  thin delegator to `evaluation.cyborg_env_factory.make_cyborg_env`.
- Eval entry points: `baselines_cyborg`, `baselines_jax`, `benchmark_jax`,
  `export_trajectory`, `generate_cynex_trajectories`. Each now accepts
  `--recipe` (and `--checkpoint` where relevant); benchmark stays variant-
  agnostic since steady-state throughput doesn't depend on the selector.
- Resilience role injection: harness rollouts call `inject_role_map` after
  `env.reset()` when `variant.resilience_roles` is set, so the CybORG-side
  `_CIARedAgent`/`ResilienceRedAgent` instances actually see the per-episode
  role map. Eval scripts go through `reset_cyborg_env`, which handles this
  automatically.
- `make_cyborg_env(wrapper_class=None)` returns the raw `CybORG` instance
  (used by `export_trajectory.make_env` which drives `parallel_step`
  directly).
- `test_make_cyborg_env_accepts_distinct_seeds` now greps both the bridge
  and the underlying factory, since the bridge delegates.
@PaulHax PaulHax merged commit f9270ce into pr-11-refactor May 9, 2026
2 of 3 checks passed
@PaulHax PaulHax deleted the game-variant-encapsulation branch May 9, 2026 18:09
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