Documentation
¶
Overview ¶
Package game is the Linefire runtime: it loads a player asset and a level and runs a minimal ship-in-a-world prototype. The ship stays centered on screen always pointing up; the world rotates and translates around it (the camera follows the ship's position and heading).
Index ¶
- Constants
- func CheckTrace(r io.Reader) ([]string, error)
- func FactionColor(f int) color.RGBA
- func Run(content fs.FS, player *asset.Asset, lvl *level.Level, mapDir, mapName string) error
- type BattleOptions
- type BattleResult
- type FleetStats
- type Game
- type IPCDriver
- type Match
- type MatchCommand
- type PilotIssue
- type SkirmishOptions
Constants ¶
const ( MatchEndless = "endless" MatchLastFleet = "lastfleet" MatchTimed = "timed" )
The match model: a battle as a first-class object, Core Wars style. The ~20-second arena regeneration the overlay inherited from the attract screen lives ONLY in the endless mode (the aquarium); a real match has a mode, an end condition, a winner and a scoreboard.
Modes:
MatchEndless the aquarium: reinforcements forever, the arena regenerates,
nothing ever ends. Stats still count — the eventual panel
reads them — and survive arena regenerations.
MatchLastFleet annihilation: each faction lands one fleet, no
reinforcements, the last faction with ships on the field
(or in a vortex) prevails. A single faction is solo
practice — there is nobody to annihilate, so it flies on.
MatchTimed a deadline: fleets land, no reinforcements, and when time
runs out the most kills prevail (a wipe still ends it
early). A dead-even score is a draw.
const ( SkirmishMapArena = "arena" SkirmishMapMaze = "maze" )
Skirmish map modes: the open field is the default show; the maze is the cave generator fitted to the screen, where rock blocks sight lines and shots and the fight happens around corners.
Variables ¶
This section is empty.
Functions ¶
func CheckTrace ¶ added in v0.0.29
CheckTrace reads a battle trace (one or many battles per stream) and returns every invariant violation it finds, in order. It is the deterministic half of trace analysis: it turns megabytes of events into a short list of findings that a human — or an AI with all the time in the world — can then explain. An empty result means the battle behaved.
Invariants checked:
- every position (spawn, snap, hit, death) lies inside the arena;
- a ship's hull falls only through hits, and rises only after it has salvaged something — a repair is legitimate, an unexplained recovery is not. A hit that leaves the hull untouched is a shield absorbing it, and says so by reporting no damage;
- nothing happens to a ship after its death, and nobody is hit or killed by a ship that never existed (a shooter may die while its bolt flies, so a dead "by" is legal — an unborn one is not);
- the result's survivor counts match the spawns minus the deaths;
- STALL findings: a live ship that moved less than stallEps per snapshot for stallSnaps consecutive snapshots while foes were alive. A stall is a real finding, not necessarily a bug — a wall-blind program pressing rock stalls honestly — which is exactly why it is worth reporting.
func FactionColor ¶ added in v0.0.47
FactionColor is the same team color, for anything OUTSIDE the engine that has to agree with the battlefield — a launcher's fleet list, an application icon. Colour says which faction here, so a second copy of this palette somewhere else is a divergence waiting to happen.
Types ¶
type BattleOptions ¶ added in v0.0.28
type BattleOptions struct {
Programs []string // one Filo source per faction, Programs[0] = faction 1 (empty = house brain)
Factions int // teams, clamped to 2..maxFactions (0 = 2)
Ships int // hulls per faction (0 = 8)
Map string // SkirmishMapArena (default) or SkirmishMapMaze
Seed int64 // arena generation and placement seed
MaxTicks int // battle length cap in ticks (0 = 7200: two minutes of game time)
Trace io.Writer // battle trace destination (JSONL; see trace.go) — nil = no trace
Battle int // battle number stamped on the trace header, for multi-battle files
}
RunBattle fights one skirmish without a window: the ships are placed up front, there are no reinforcements, and the simulation steps at full CPU speed until one faction stands alone or the tick budget runs out. Hundreds of battles fit in a few seconds of processing, which is what makes program development honest — a change to an AI is measured over many fights, not eyeballed over one. The graphical skirmish and this runner step the same code: updateEnemies, the Filo pilots, the shots, the damage.
A battle is DETERMINISTIC: placement, per-hull temperament and every patrol turn draw from the seed's own dice (simRand), so the same options replay the same fight byte for byte — a trace is a reproducible artifact, a regression can be bisected by seed, and a player-vs-player dispute has a verifiable record.
type BattleResult ¶ added in v0.0.28
type BattleResult struct {
Winner int // the faction left standing; 0 = draw (timeout, or mutual destruction)
Ticks int // how long the fight ran
Alive map[int]int // live hulls per faction at the end
Stats map[int]*FleetStats // the scoreboard: kills, losses, shots per faction
}
BattleResult is how one fight ended.
func RunBattle ¶ added in v0.0.28
func RunBattle(content fs.FS, mapDir string, opts BattleOptions) (BattleResult, error)
RunBattle builds the battlefield, lands the fleets and fights to the end.
type FleetStats ¶ added in v0.0.32
type FleetStats struct {
Kills int // enemy ships destroyed
Losses int // own ships destroyed
Shots int // bolts fired (with Kills, an accuracy read)
Powerups int // pickups salvaged off the battlefield (see salvage.go)
}
FleetStats is one faction's scoreboard.
type Game ¶
type Game struct {
// contains filtered or unexported fields
}
Game is the ebiten.Game runtime.
func New ¶
New builds a game from a player asset and a level. simple skips the per-area flood negative-space map and the fog-of-war (line-wall view, no discovery grid / fog texture): the procedural map uses it so a large streamed region renders fast and rebuilds cheaply on a chunk crossing. New builds a game that reads its data straight from the operating system (the default for tests and dev, which pass a directory such as "../gameassets"). Run uses newWithContent to serve the same data from the embedded bundle instead.
func NewSkirmish ¶ added in v0.0.19
NewSkirmish builds the faction battle for a transparent desktop window: an endless horde dealt round-robin across the teams, and a fresh procedurally generated arena every ~20 seconds. The caller runs it with ebiten.RunGameWithOptions and ScreenTransparent.
func (*Game) Draw ¶
Draw renders the world around the centered, upward-pointing ship. Because the world swings by a large per-frame angle during a turn, a single still frame on a sample-and-hold display reads as a doubled line (stroboscopic stepping). To fill the gap, the moving world is accumulated over several intermediate camera poses between the previous and current frame and averaged. The ship itself is fixed at the screen center, so it is drawn once, crisp, on top.
func (*Game) Layout ¶
Layout renders at the display's native pixel density: the offscreen the game draws into is sized in device pixels and mapped 1:1 to the framebuffer, so vector strokes are anti-aliased at full resolution with no resampling step.
func (*Game) PostCommand ¶ added in v0.0.33
func (g *Game) PostCommand(c MatchCommand)
PostCommand queues a control command for the game loop. Safe to call from any goroutine; a nil inbox (the campaign, or no control plane) ignores it.
type IPCDriver ¶ added in v0.0.31
type IPCDriver struct {
// contains filtered or unexported fields
}
IPCDriver is one faction's external pilot. The caller owns the transport (usually a child process's stdio) and hands in its reader and writer; the game does no exec of its own.
func NewIPCDriver ¶ added in v0.0.31
NewIPCDriver wires a driver to its transport and starts reading its orders. The reader goroutine lives until r closes; the writer never blocks the game beyond the OS pipe buffer (a driver that stops reading eventually stalls its own state feed, not the battle — see send).
type Match ¶ added in v0.0.32
type Match struct {
Mode string
Ships int // fleet size per faction (lastfleet/timed)
Duration int // deadline in ticks (timed)
Factions int // teams dealt in: one is solo practice, with nobody to annihilate
Stats map[int]*FleetStats
// contains filtered or unexported fields
}
Match is the battle being fought: its rules, its clock and its scoreboard.
type MatchCommand ¶ added in v0.0.33
type MatchCommand struct {
Op string `json:"op"`
Mode string `json:"mode,omitempty"`
Ships int `json:"ships,omitempty"`
Duration int `json:"duration,omitempty"` // seconds
}
The control plane: how the OUTSIDE talks to a running skirmish. One surface serves them all — a terminal, the garage UI, an agent: battle events flow OUT as JSONL (the same stream the headless trace writes: spawns, shots, hits, deaths, battle staged, battle decided), and match commands flow IN.
Commands, one JSON object per line:
{"op":"restart"} stage the battle again
{"op":"match","mode":"timed","ships":6,"duration":120} reconfigure and stage
{"op":"quit"} end the battle and exit
Anything invalid is reported and ignored — the show goes on.
Quitting used to be left to the transport owner, on the reasoning that ending a process it started is a launcher's own business. crg turned that around: there is already a channel here, so ASKING costs nothing, while a signal costs portability — the caller ends up writing per-platform process handling for something the protocol says in seven bytes. Asking is also the better of the two, because a game that is asked gets to end on its own terms instead of being cut down mid-frame.
type PilotIssue ¶ added in v0.0.44
PilotIssue is one problem in a fleet program. Line is 1-based, or 0 when the failure has no usable position (runtime errors do not carry one).
func ValidatePilot ¶ added in v0.0.44
func ValidatePilot(src string) *PilotIssue
ValidatePilot checks a Filo fleet program the way the battle will actually treat it, in two stages. First it compiles with the REAL pilot engine — the same builtins the battle registers, so nothing passes here that would fail there. Then it flies ONE dry tick on a synthetic ship with live instruments, which is what catches the errors Filo only raises at runtime: an undefined global, a wrong arity, a step-limit blowup. A dynamic language cannot promise more than the paths it ran — a branch the dry tick never took stays unchecked — but the first tick is the one every program runs, and it is where the overwhelming majority of mistakes surface.
nil means the program compiles and survives its first tick.
type SkirmishOptions ¶ added in v0.0.19
type SkirmishOptions struct {
Sound bool // create the audio context (default silent)
Debug bool // show the debug HUD (there is no F3 to toggle it: skirmish reads no keys)
Factions int // teams sharing the arena, clamped to 1..maxFactions (0 = 2; one is solo practice)
Map string // SkirmishMapArena (default) or SkirmishMapMaze
// The match being fought (see match.go): MatchEndless (default) is the
// aquarium; MatchLastFleet and MatchTimed are real battles — Ships hulls
// per faction, no reinforcements, rematch on a fresh field after the
// outcome. Duration is the timed mode's deadline in SECONDS.
Mode string
Ships int
Duration int
// Programs is one Filo SOURCE per faction (Programs[0] drives faction 1,
// and so on); an empty entry leaves that faction on the house brain. The
// contract a program flies under is pilot.go's doc. Reading files is the
// caller's job: the game takes source text.
Programs []string
// IPC is one external driver per faction, same indexing (nil entry = none).
// The caller owns the transport — usually a child process's stdio — and
// builds each with NewIPCDriver; the protocol is ipc.go's doc. A faction
// cannot have both a Program and a driver.
IPC []*IPCDriver
// Events is the control plane's outbound stream (JSONL: spawns, shots,
// hits, deaths, battles staged and decided — see control.go and trace.go);
// nil keeps the game silent. Commands come back via PostCommand.
Events io.Writer
}
Source Files
¶
- align.go
- ally.go
- arsenal.go
- assetcache.go
- autofire.go
- battle.go
- bonus.go
- boost.go
- bounds.go
- breakable.go
- cheats.go
- checkpoint.go
- collision.go
- combatmods.go
- control.go
- credits.go
- devourer.go
- drops.go
- edge.go
- effects.go
- ending.go
- endless.go
- floattext.go
- floodmap.go
- game.go
- gamelog.go
- hud.go
- hudslots.go
- ipc.go
- laser.go
- mapscreen.go
- mapstate.go
- match.go
- materialize.go
- menu.go
- mine.go
- minimap.go
- missile.go
- music.go
- musicweb.go
- nav.go
- objective.go
- perf.go
- pilot.go
- portal.go
- powerups.go
- procedural.go
- projectile.go
- resolution.go
- results.go
- rng.go
- roundview.go
- salvage.go
- secondary.go
- shieldmods.go
- shiparms.go
- shockwave.go
- skirmish.go
- sound.go
- soundloop.go
- spawner.go
- title.go
- touch.go
- touchdraw.go
- trace.go
- tracecheck.go
- validate.go
- verdict.go
- victory.go
- vision.go
- weapon.go
- weaponpickup.go
- websettings.go
- world.go