Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,9 @@ uv run pytest tests/unit # fast pure-function tests only
uv run pytest tests/integration/test_stack.py::test_name # single test
uv run pytest -m api tests/test_api.py # REST API tests — spawn a separate process, opt-in

uv run ruff check . # lint
uv run ruff format . # format (line-length 100)
uv run pyright # type check (standard mode; covers example_plugins EXCEPT example_plugins/tangram)

# tangram frontend plugin — its own self-contained uv + pnpm workspace (separate .venv);
# all commands run from example_plugins/tangram/ (see its justfile)
cd example_plugins/tangram && just check # ruff + pyright + pnpm (eslint + vue-tsc + tsc)
cd example_plugins/tangram && just fmt # ruff --fix/format + pnpm lint:fix
cd example_plugins/tangram && pnpm build # bundle each plugin into dist-frontend/
just check # ruff, pyright, frontend checks
just fmt # lint fixes and tangram/frontend formatting
pnpm build # tangram frontend bundle

uv run minisky commands docs # regenerate docs/reference/commands.md after changing commands
uv run minisky docs serve # docs live preview
Expand All @@ -47,9 +41,9 @@ The FastAPI app lives in `minisky/server.py`; `minisky server` is the CLI entry

Full details in `docs/architecture.md` — read it before making structural changes. The essentials:

**Singletons.** `minisky.init()` constructs module-level singletons everything else references: `sim` (clock/state machine), `traf` (all aircraft state + flight-dynamics update), `runner` (async loop stepping at a controllable rate), `scr` (`ConsoleIO` output buffer), `navdb` (waypoints/airports/airways from parquet). They are `None` until `init()` runs. Call `load_plugins()` after `init()` to activate plugins from `settings.toml`.
**Runtime ownership.** [`MiniSky`][minisky.runtime.MiniSky] owns one simulator object graph: settings, simulation, traffic, runner, console, navigation, command stack, plugins, replaceables, areas, variable explorer, random generators, and streaming hub. Unlike `bluesky`, there is no package-level `traf`, `sim`, `scr`, `runner`, or `navdb`.

**Import order in `minisky/__init__.py` is load-bearing.** `traffic` is imported last and separately because the performance model runs module-level code touching `minisky.data` (set up by the settings import). Reordering causes a circular import.
**Lifecycle.** Use `with MiniSky(settings)` for manually stepped synchronous work. Use `async with MiniSky(settings)` and `await runtime.run()` when running the async loop. The FastAPI lifespan owns its background runner task and awaits asynchronous cleanup.

**Simulation loop.** `sim.step()` runs, in order: stack processing → time advance (only in `OP` state) → plugin `preupdate` → `traf.update()` (autopilot/FMS, conflict detection+resolution, performance limits, wind, position integration) → plugin `update`. States: `INIT`, `OP`, `HOLD`, `END`. Drive it either by calling `sim.step()` manually (embedding) or via `runner.run()` (wall-clock paced; `runner.speed` and `runner.forward()`).

Expand Down
23 changes: 14 additions & 9 deletions docs/api/minisky.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
# `minisky`

The top-level package: initialisation, simulation-state constants, and the global
singleton objects (`sim`, `traf`, `runner`, `scr`, `navdb`) described in
[Architecture](../architecture.md#the-singletons).

::: minisky
options:
members:
- init
- load_plugins
The top-level package exposes the explicit runtime owner, validated settings,
default settings path, and simulation-state constants.

## Runtime

::: minisky.MiniSky

## Simulation state

::: minisky.SimulationState

## Settings

::: minisky.MiniSkySettings
20 changes: 13 additions & 7 deletions docs/api/plugin.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
# `minisky.plugin`

Plugin discovery, loading, and the building blocks for writing plugins — see the
[plugin guide](../guides/plugins.md).
Runtime-owned plugin discovery, loading, timed hooks, per-aircraft entities,
and command declarations. See the [plugin guide](../guides/plugins.md).

## Plugin management

::: minisky.plugin.plugin
::: minisky.plugin.plugin.PluginManager

## Plugin records

::: minisky.plugin.plugin.Plugin

## Entity

::: minisky.plugin.entity.Entity

## Timed functions
## Timed hooks

::: minisky.plugin.timedfunction.TimedFunctionManager

::: minisky.plugin.timedfunction
::: minisky.plugin.timedfunction.Timer

## Stack command decorators
## Stack command declarations

::: minisky.plugin.plugin_decorators
::: minisky.plugin.plugin_decorators.command
4 changes: 4 additions & 0 deletions docs/api/simulation.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ console output.

::: minisky.simulation.simulation.Simulation

## Simulation state

::: minisky.simulation.simulation.SimulationState

## Runner

::: minisky.simulation.runner.Runner
Expand Down
20 changes: 9 additions & 11 deletions docs/api/stack.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
# `minisky.stack`

The text-command interpreter. Every command — from scenario files, the console, or the
REST API — is queued with [`stack()`][minisky.stack.stack] and executed during
[`process()`][minisky.stack.process] on the next simulation step. See the
[stack command reference](../reference/commands.md) for the available commands.
The text-command interpreter. Every command from scenario files, the console,
or the REST API is queued with
[`CommandStack.stack`][minisky.stack.CommandStack.stack] and executed by
[`CommandStack.process`][minisky.stack.CommandStack.process] on the next
simulation step. See the [stack command reference](../reference/commands.md)
for the available commands.

::: minisky.stack
options:
members:
- stack
- process
- ic
- ic_StringIO
- reset
- CommandStack
- Command

## Argument parsing

Parsers for the aviation-aware argument types (`alt`, `spd`, `hdg`, `latlon`, `wpt`, ...)
used in command signatures.
Parsers for the aviation-aware argument types (`alt`, `spd`, `hdg`, `latlon`,
`wpt`, ...) used in command signatures.

::: minisky.stack.argparser
4 changes: 2 additions & 2 deletions docs/api/tools.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# `minisky.tools`

Aeronautics and geodesy utilities. These are pure functions, usable outside the
simulator.
Aeronautics and geodesy utilities, plus runtime-owned area, navigation, and
position helpers.

## Aeronautics (`aero`)

Expand Down
26 changes: 26 additions & 0 deletions docs/api/traffic.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,18 @@ routes, conflict detection and resolution, aircraft performance, wind, and turbu

::: minisky.traffic.autopilot.Autopilot

## Active waypoint

::: minisky.traffic.activewpdata.ActiveWaypoint

## Route

::: minisky.traffic.route.Route

## ASAS command target

::: minisky.traffic.aporasas.APorASAS

## Conflict detection

::: minisky.traffic.asas.detection.ConflictDetection
Expand All @@ -25,6 +33,24 @@ routes, conflict detection and resolution, aircraft performance, wind, and turbu

::: minisky.traffic.asas.mvp.MVP

## Wind

::: minisky.traffic.wind.Windfield

::: minisky.traffic.wind.Wind

## Uncertainty

::: minisky.traffic.uncertainty.SurveillanceUncertainty

## Trails

::: minisky.traffic.trails.Trails

## Groups

::: minisky.traffic.trafficgroups.TrafficGroups

## Performance (OpenAP)

::: minisky.traffic.performance.perfoap.OpenAP
86 changes: 47 additions & 39 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,79 +1,87 @@
# Architecture

MiniSky keeps BlueSky's core simulation model but removes the GUI, networking, and node
management. What remains is a single-process simulator built around a handful of global
singleton objects and a text-command stack.
MiniSky keeps BlueSky's core simulation model but removes the GUI, networking,
and node management. The mutabl state is owned by one [`MiniSky`][minisky.MiniSky] runtime.

## The singletons
## Runtime ownership

Calling [`minisky.init()`][minisky.init] creates the module-level singletons that the rest
of the code refers to:
Constructing `MiniSky` creates an independent object graph:

| Singleton | Class | Role |
| Runtime attribute | Class | Role |
| --- | --- | --- |
| `minisky.sim` | [`Simulation`][minisky.simulation.simulation.Simulation] | Simulation clock, timestep, and state machine |
| `minisky.traf` | [`Traffic`][minisky.traffic.traffic.Traffic] | All per-aircraft state and the flight-dynamics update |
| `minisky.runner` | [`Runner`][minisky.simulation.runner.Runner] | Async loop that calls `sim.step()` at a controllable rate |
| `minisky.scr` | [`ConsoleIO`][minisky.simulation.console.ConsoleIO] | Text output buffer (console and REST API read from it) |
| `minisky.navdb` | [`Navdatabase`][minisky.tools.navdata.Navdatabase] | Waypoints, airports, and airways loaded from parquet files |
| [`runtime.simulation`][minisky.simulation.simulation.Simulation] | [`Simulation`][minisky.simulation.simulation.Simulation] | Clock, timestep, and state machine |
| [`runtime.traffic`][minisky.traffic.traffic.Traffic] | [`Traffic`][minisky.traffic.traffic.Traffic] | Per-aircraft state and flight-dynamics update |
| [`runtime.runner`][minisky.simulation.runner.Runner] | [`Runner`][minisky.simulation.runner.Runner] | Async loop that steps the simulation |
| [`runtime.console`][minisky.simulation.console.ConsoleIO] | [`ConsoleIO`][minisky.simulation.console.ConsoleIO] | Buffered text output |
| [`runtime.navigation`][minisky.tools.navdata.Navdatabase] | [`Navdatabase`][minisky.tools.navdata.Navdatabase] | Waypoints, airports, and airways |
| [`runtime.commands`][minisky.stack.CommandStack] | [`CommandStack`][minisky.stack.CommandStack] | Command registry, queue, and scenario state |
| [`runtime.plugins`][minisky.plugin.plugin.PluginManager] | [`PluginManager`][minisky.plugin.plugin.PluginManager] | Plugin records, hooks, timers, and state |
| `runtime.areas` | [`AreaFilter`][minisky.tools.areafilter.AreaFilter] | Named geographic areas |
| `runtime.variables` | [`VariableExplorer`][minisky.core.varexplorer.VariableExplorer] | Runtime data inspection |
| `runtime.streaming` | `StreamHub` | Rate-capped snapshot fan-out |

```python
import minisky
from minisky import DEFAULT_SETTINGS_FILE, MiniSky, MiniSkySettings

minisky.init() # create the singletons
minisky.load_plugins() # optional: load plugins enabled in settings.toml
settings = MiniSkySettings.from_file(DEFAULT_SETTINGS_FILE)
with MiniSky(settings) as runtime:
runtime.load_plugins()
runtime.simulation.step()
```

## The simulation loop

The simulation advances in discrete timesteps of `sim.simdt` seconds (default 1 s). One
call to [`sim.step()`][minisky.simulation.simulation.Simulation.step] does, in order:
The simulation advances in discrete timesteps of [`runtime.simulation.simdt`][minisky.simulation.simulation.Simulation] seconds (default 1 s). One
call to [`Simulation.step`][minisky.simulation.simulation.Simulation.step] does, in order:

1. **Stack processing** — pending text commands are parsed and executed
([`stack.process()`][minisky.stack.process]).
2. **Time advance** — `sim.simt` and the simulated UTC clock move forward by `simdt`
1. `INIT` switches to `OP` when traffic or scenario work exists.
2. pending text commands are parsed and executed
([`CommandStack.process`][minisky.stack.CommandStack.process]).
3. [`runtime.simulation.simt`][minisky.simulation.simulation.Simulation] and the simulated UTC clock move forward by `simdt`
(only in the `OP` state).
3. **Plugin pre-update** — timed plugin functions registered with the `preupdate` hook.
4. **Traffic update** — [`traf.update()`][minisky.traffic.traffic.Traffic.update]
4. Timed plugin functions registered with the `preupdate` hook.
5. [`Traffic.update`][minisky.traffic.traffic.Traffic.update]
integrates aircraft state: autopilot/FMS logic, conflict detection and resolution,
aircraft performance limits, wind, and finally position integration.
5. **Plugin update** — timed plugin functions registered with the `update` hook.
6. Timed plugin functions registered with the `update` hook.
7. The runtime-owned hub publishes when subscribers are present.

The simulation state machine has four states, exposed as constants on the `minisky`
package: `INIT` (waiting for traffic), `OP` (running), `HOLD` (paused), and `END`.
The simulation switches from `INIT` to `OP` automatically as soon as there is traffic
The simulation state machine uses [`SimulationState`][minisky.simulation.simulation.SimulationState]:
`SimulationState.INIT` waits for traffic, `SimulationState.OP` runs,
`SimulationState.HOLD` pauses, and `SimulationState.END` stops. The simulation
switches from `SimulationState.INIT` to `SimulationState.OP` automatically as soon as there is traffic
or pending scenario commands.

### Real time vs. fast time

There are two ways to drive the loop:

- **Manual stepping** — call `sim.step()` yourself in a plain loop. Each call advances the
simulation by `simdt` simulated seconds, as fast as your CPU allows. This is what you
- **Manual stepping** — call `runtime.simulation.step()` yourself in a plain loop. Each call advances the
simulation by [`runtime.simulation.simdt`][minisky.simulation.simulation.Simulation] simulated seconds, as fast as your CPU allows. This is what you
want when embedding MiniSky in your own code or experiments.
- **The runner** — `await minisky.runner.run()` steps the simulation once per wall-clock
interval. `runner.speed = 10` makes simulated time pass 10× faster than wall time, and
`runner.forward(seconds)` fast-forwards by stepping at the maximum rate until the target
- **The runner** — `await runtime.run()` steps the simulation once per wall-clock
interval. `runtime.runner.speed = 10` makes simulated time pass 10× faster than wall time, and
`runtime.runner.forward(seconds)` fast-forwards by stepping at the maximum rate until the target
simulation time is reached. The REST API server and `minisky run` both use the runner.

## Per-aircraft arrays: `TrafficArrays`

Aircraft state is stored as NumPy arrays (and lists for strings), one element per
aircraft, spread across many objects: `traf.lat`, `traf.alt`, `traf.ap.route`,
`traf.perf.mass`, and so on. Keeping all of these in sync when aircraft are created and
aircraft, spread across many objects: [`runtime.traffic.lat`][minisky.traffic.traffic.Traffic], [`runtime.traffic.alt`][minisky.traffic.traffic.Traffic], [`runtime.traffic.ap.route`][minisky.traffic.route.Route],
[`runtime.traffic.perf.mass`][minisky.traffic.performance.perfoap.OpenAP], and so on. Keeping all of these in sync when aircraft are created and
deleted is the job of [`TrafficArrays`][minisky.core.trafficarrays.TrafficArrays].

Classes that hold per-aircraft data derive from it and register their arrays:

```python
class Example(Entity):
def __init__(self):
super().__init__()
def __init__(self, traffic):
super().__init__(traffic)
with self.settrafarrays():
self.npassengers = np.array([])
```

`TrafficArrays` instances form a tree rooted at `traf`. When an aircraft is created or
`TrafficArrays` instances form a tree rooted at [`runtime.traffic`][minisky.traffic.traffic.Traffic]. When an aircraft is created or
deleted, the whole tree is walked and every registered array grows or shrinks in lockstep,
so index `i` refers to the same aircraft everywhere.

Expand All @@ -82,8 +90,8 @@ so index `i` refers to the same aircraft everywhere.
Every text command — whether it comes from a scenario file, the REST `stack/` endpoint,
or the console — goes through the same interpreter: [`minisky.stack`](api/stack.md).

- Commands are queued with [`stack.stack("CRE KL001 B738 52 4 90 FL100 250")`][minisky.stack.stack]
and executed on the next `sim.step()`.
- Commands are queued with `runtime.commands.stack("CRE KL001 B738 52 4 90 FL100 250")`
and executed on the next `runtime.simulation.step()`.
- Each command is a [`Command`][minisky.stack.Command] object with typed
parameters. Argument strings like `"callsign,wpt,[alt,spd]"` are parsed by
[`minisky.stack.argparser`](api/stack.md#argument-parsing), which knows aviation types
Expand Down Expand Up @@ -119,7 +127,7 @@ argument parsers convert on the way in.

## I/O: how output gets back to you

Simulation code reports through `minisky.scr` (a
Simulation code reports through [`runtime.console`][minisky.simulation.console.ConsoleIO] (a
[`ConsoleIO`][minisky.simulation.console.ConsoleIO]), which buffers echo text instead of
printing it. The REST API's `stack/` endpoint sends a command, waits for the stack to
process it, then reads the buffer back to the HTTP client — which is how the console shows
Expand Down
52 changes: 29 additions & 23 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,37 +60,43 @@ See the [command-line interface](guides/cli.md), [REST API](guides/rest-api.md),
## From Python

```python
import minisky

minisky.init()

minisky.sim.reset()
minisky.traf.cre("KL315", lat=52.0, lon=4.0, hdg=45, alt=5000, spd=250)
minisky.stack.stack("KL315 ADDWPT HELEN FL100 250")

minisky.sim.simdt = 10 # 10-second timesteps

for _ in range(5):
minisky.sim.step()
print(f"t={minisky.sim.simt}s lat={minisky.traf.lat} lon={minisky.traf.lon}")
from minisky import DEFAULT_SETTINGS_FILE, MiniSky, MiniSkySettings

settings = MiniSkySettings.from_file(DEFAULT_SETTINGS_FILE)
with MiniSky(settings) as runtime:
runtime.simulation.reset()
runtime.traffic.cre(
"KL315", lat=52.0, lon=4.0, hdg=45, alt=5000, spd=250
)
runtime.commands.stack("KL315 ADDWPT HELEN FL100 250")

runtime.simulation.simdt = 10 # 10-second timesteps

for _ in range(5):
runtime.simulation.step()
print(
f"t={runtime.simulation.simt}s "
f"lat={runtime.traffic.lat} lon={runtime.traffic.lon}"
)
```

See the [Python library guide](guides/python-api.md) for details on the singleton objects
and stepping the simulation yourself.
See the [Python library guide](guides/python-api.md) for details on runtime
ownership and stepping the simulation yourself.

## Configuration

Runtime settings live in `settings.toml` at the repository root, e.g. conflict-detection
lookahead time and protected-zone sizes, the plugin search directory, and which plugins to
load at startup:
lookahead time and protected-zone sizes, plus the current plugin search directory and startup list:

```yaml
asas_dtlookahead: 300 # ASAS lookahead time [sec]
asas_pzr: 5 # ASAS horizontal protected zone radius [nm]
asas_pzh: 1000 # ASAS vertical protected zone height [ft]
```toml
asas_dtlookahead = 300
asas_pzr = 5
asas_pzh = 1000
asas_marh = 1.05
asas_marv = 1.05

plugin_path: example_plugins
# enabled_plugins: ['EXAMPLE']
plugin_path = "example_plugins"
enabled_plugins = []
```

## Running the tests
Expand Down
Loading
Loading