Skip to content

Sun-Blocking

Quickstart

import jax
from orbitalgym import OrbitalGymEnv, BySide, make_sun_blocking
from orbitalgym.policies import ZeroControl
from orbitalgym.rollout import rollout

cfg = make_sun_blocking()
env = OrbitalGymEnv(cfg)
guard = ZeroControl(n_vehicles=cfg.n_guards, command_cls=env.guard_command_cls)
bandit = ZeroControl(n_vehicles=cfg.n_bandits, command_cls=env.bandit_command_cls)
traj = rollout(env, BySide(guard=guard, bandit=bandit),
               BySide(guard=lambda c, s, k: None, bandit=lambda c, s, k: None),
               jax.random.PRNGKey(0), n_steps=cfg.max_steps)

The bandit is rewarded for occluding the guard's view of the Sun — interposing along the line from the bandit to the Sun, at a desired standoff distance. The reward is the KSP-DG SB1 formulation, signed so the guard is rewarded when the configuration is reversed.

What the reward shapes

The reward at each timestep is

bandit_r = -û_BG · û_BS  *  exp(-decay * (d_BG - d_target)²)
guard_r  = -bandit_r

with vertex at the bandit. Three regimes:

  • Peak (+1) — the bandit is between sun and guard at distance target_viewing_distance_m. û_BG points away from the sun, û_BS points toward the sun, so their dot product is ≈ -1, and -dot ≈ +1.
  • Trough (-1) — the guard is between sun and bandit at the same distance. The dot product flips sign.
  • Zero — far from either configuration, either because the geometry is perpendicular (dot product ≈ 0) or because |d_BG - d_target| is large (range factor crushes everything).

The game is zero-sum and bounded [-1, +1]. There is no separate angular σ — the dot product is its own peak-shape.

The reward looks up Sun ECI position via astrojax.sun_position(epoch) and converts vehicle RTN positions to ECI via the shared frame helper in games/_frames.py. The pure formula is exposed as orbitalgym.games.sun_blocking.sun_blocking_kernel(...) and is used directly by the position-sweep diagnostic.

Termination

max_steps only. Sun-Blocking has no capture concept.

Builder

from orbitalgym import make_sun_blocking

cfg = make_sun_blocking(
    target_viewing_distance_m=500.0,
    range_decay_coef=4.0e-6,
    max_horizon_s=5400.0,    # ~90 min, one full LEO orbit
    seed=0,
)

The default horizon is one full LEO orbit so the Sun-guard-bandit geometry varies meaningfully across the episode.

Knobs at a glance

Field Type Default What it does
n_guards int 1 Observer count.
n_bandits int 1 Blocker count.
target_viewing_distance_m float 500.0 Desired bandit-guard standoff at the reward peak.
range_decay_coef float 4.0e-6 Gaussian decay coefficient (1/m²); lower → wider peak.
max_horizon_s float 5400.0 Total duration (one LEO orbit).
dt float 10.0 Step size.
seed int 0 PRNG seed.

Suggested experiments

  • Bandit positioning. Train a bandit policy to maximize cumulative reward — i.e., maintain interposition near target_viewing_distance_m over the orbit.
  • Guard counter-positioning. Train a guard to actively maneuver to push the bandit out of the peak region (or into the trough).
  • Range-peak sweep. Vary target_viewing_distance_m from 100 m to 2000 m. The reward peak sits at that range; the IC sampler scale should scale alongside or the bandit will rarely visit the peak in default rollouts.

Variants

The four customization axes (reward, termination, IC, observation) all swap by dataclasses.replace. Two SB-flavored recipes:

Variant 1: Sun-line-aware bandit

SunTrackerBlocker is the gallery's static-Sun blocker heuristic — it thrusts along a fixed RTN Sun direction toward the projection of the opponent onto that line. Use it as the bandit to give the guard an adversary that already understands the geometry and is harder to evade than a generic chaser.

    import dataclasses

    import jax.numpy as jnp

    from orbitalgym import make_sun_blocking
    from orbitalgym.policies.heuristic import SunTrackerBlocker

    cfg = make_sun_blocking(seed=0, max_horizon_s=5400.0)
    sun_bandit = SunTrackerBlocker(
        sun_dir_rtn=jnp.array([1.0, 0.0, 0.0]),
        max_dv_mps=0.05,
    )
    cfg = dataclasses.replace(cfg, bandit_policy=sun_bandit)

Variant 2: jittered Sun-tracker for population-of-adversaries

Wrap SunTrackerBlocker in JitteredPolicy to produce a small population of stochastic Sun-line blockers. Useful for training a guard robust to a band of Sun-aware threats rather than overfitting to one deterministic adversary.

    from orbitalgym.policies.heuristic import JitteredPolicy

    base = SunTrackerBlocker(
        sun_dir_rtn=jnp.array([1.0, 0.0, 0.0]),
        max_dv_mps=0.05,
    )
    cfg2 = dataclasses.replace(cfg, bandit_policy=JitteredPolicy(base=base, sigma=0.005))

For more axes, see Extending → Customize rewards, …termination, …IC sampling, and …observations.

Built-in policies and adversaries

Sensible gallery picks for Sun-Blocking:

Sanity-check notebook

examples/games/sun_blocking.ipynb is a full walkthrough that builds a SB scenario, runs a rollout, plots the rollout-time diagnostic, and renders the 2D position-sweep surface — the latter is the canonical reward-shape verification, and it should match the KSP-DG SB1 reference shape.

Where to next