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
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
# Git Jet

One to three pixel jets launch from a carrier, bank over your real commit calendar,
and destroy each active day's cell. Transparent background, waves, projectiles,
and destroy each active day's cell. Transparent background, rolling ocean swells,
bow foam and a flowing stern wake, projectiles,
pixel explosions and a spinning radar. Runs with GitHub Actions.

**Lincoln — default carrier**

<picture>
<source media="(prefers-reduced-motion: reduce)" srcset="examples/demo/git-jet.png?v=runway-size-20261102">
<img src="examples/demo/git-jet.gif?v=runway-size-20261102" width="960" alt="A pixel jet launches from a Lincoln-inspired carrier with an angled runway, detailed island and spinning radar.">
<source media="(prefers-reduced-motion: reduce)" srcset="examples/demo/git-jet.png?v=water-bow-loop-20261102">
<img src="examples/demo/git-jet.gif?v=water-bow-loop-20261102" width="960" alt="A pixel jet launches from a Lincoln-inspired carrier through rolling ocean water, with an angled runway, detailed island and spinning radar.">
</picture>

**Classic — original carrier**

<picture>
<source media="(prefers-reduced-motion: reduce)" srcset="examples/demo-classic/git-jet.png?v=carriers-20261102">
<img src="examples/demo-classic/git-jet.gif?v=carriers-20261102" width="960" alt="The original ivory-island carrier with a spinning radar launches a pixel jet to clear the commit calendar.">
<source media="(prefers-reduced-motion: reduce)" srcset="examples/demo-classic/git-jet.png?v=water-bow-loop-20261102">
<img src="examples/demo-classic/git-jet.gif?v=water-bow-loop-20261102" width="960" alt="The original ivory-island carrier with a spinning radar and flowing ocean wake launches a pixel jet to clear the commit calendar.">
</picture>

Choose `carrier: 'lincoln'` (default) or `carrier: 'classic'` in your workflow.
For the CLI, use `--carrier classic`. Both designs have the spinning radar.

Choose `water: 'ocean'` (default) for the flowing ocean surface and wake, or
`water: 'original'` for the original small wave patches. This works with either
carrier. For the CLI, use `--water ocean` or `--water original`.

Both demos use **56 synthetic commits across 14 active days**. Live mode uses your
actual data. **One attack clears one day cell**, regardless of that day's count.
The jet returns to land, the calendar rebuilds, and the animation loops.
Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ inputs:
carrier:
description: Carrier artwork, lincoln (default) or classic
default: lincoln
water:
description: Water artwork, ocean (default) or original
default: ocean
auto-compress:
description: Automatically lower scale then FPS to fit GIF limits without skipping day cells
default: 'true'
Expand Down Expand Up @@ -68,6 +71,7 @@ runs:
JET_FPS: ${{ inputs.fps }}
JET_JETS: ${{ inputs.jets }}
JET_CARRIER: ${{ inputs.carrier }}
JET_WATER: ${{ inputs.water }}
JET_AUTO_COMPRESS: ${{ inputs.auto-compress }}
JET_DEMO: ${{ inputs.demo }}
JET_SEED: ${{ inputs.seed }}
Expand Down
17 changes: 17 additions & 0 deletions artwork/APPROVAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ Both carrier designs and the base artwork are approved. Production artwork lives
`src/git_jet/art.py` (Classic, jets and effects) and `src/git_jet/lincoln.py` (Lincoln).
Draft sources and review images retain the approved drawings for regression checks.

The owner requested a complete water redesign on 2026-11-02. The sea now lives in
`src/git_jet/water.py`, beneath both carriers: crossing travelling swells, drifting
bow foam, waterline turbulence and spreading stern eddies. The original carrier
studies remain archived; regression checks preserve their hull, deck and island
pixels while allowing the original detached water strips to be removed.

The refined bow animation and the selectable `ocean` / `original` setting were
approved by the owner on 2026-11-02. The bow foam starts at the submerged stem and
peels aft along the hull. Pressure crests expand away from the stem, foam flows
out to both sides, and short spray droplets rise and fall into the wake.
Pressure crests dissolve into the sea before their animation age wraps, fixing
the visible reset reported by the owner on 2026-11-02.
The `original` setting reproduces the archived water pixels for both carriers.
Run `python scripts/preview_water.py` to generate the ocean review loop, or add
`--water original` to inspect the preserved effect.

| Asset | Status | Preview |
| --- | --- | --- |
| Angled aircraft carrier, parked aircraft and waves | Revision 2 approved by project owner, 2026-11-01 | drafts/output/carrier-approval.png |
Expand All @@ -12,6 +28,7 @@ Draft sources and review images retain the approved drawings for regression chec
| Projectiles, explosion and smoke | Approved by project owner, 2026-11-01 | drafts/output/effects-approval.png |
| Mast antenna rotating through a full turn | Approved by project owner, 2026-11-02; detached aerial pixels removed as requested | [Animation](drafts/output/radar-spinner.gif) · [Eight angles](drafts/output/radar-spinner-sheet.png) |
| Lincoln carrier traced from the owner's silhouette/runway guides, including island details and radar cleanup | Approved by project owner, 2026-11-02 | [Approved pixels](drafts/output/lincoln-island-detail-transparent.png) · [Island comparison](drafts/output/lincoln-island-detail-comparison.png) · [Animation](drafts/output/lincoln-island-detail-motion.gif) |
| Ocean water with expanding bow pressure crests, outward foam and spray; original water remains selectable | Approved by project owner, 2026-11-02 | [Lincoln demo](../examples/demo/git-jet.gif) · [Classic demo](../examples/demo-classic/git-jet.gif) |

Lincoln is the default; Classic is selectable. Classic's original hull, deck and
island remain unchanged. Its detached aerial was removed alongside the same cleanup
Expand Down
Binary file modified examples/demo-classic/git-jet.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/demo-classic/git-jet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion examples/demo-classic/report.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"attack_mode": "day-cell",
"jets": 1,
"carrier": "classic",
"water": "ocean",
"seed": "git-jet-loop",
"flights": [
{
Expand Down Expand Up @@ -40,7 +41,7 @@
"duration_seconds": 28.0,
"transparent": true,
"decoded_frames_match": true,
"gif_bytes": 8407801,
"gif_bytes": 14334512,
"targets": [
{
"date": "2026-10-18",
Expand Down
Binary file modified examples/demo/git-jet.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/demo/git-jet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion examples/demo/report.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"attack_mode": "day-cell",
"jets": 1,
"carrier": "lincoln",
"water": "ocean",
"seed": "git-jet-loop",
"flights": [
{
Expand Down Expand Up @@ -40,7 +41,7 @@
"duration_seconds": 24.0,
"transparent": true,
"decoded_frames_match": true,
"gif_bytes": 7371234,
"gif_bytes": 13041689,
"targets": [
{
"date": "2026-10-18",
Expand Down
1 change: 1 addition & 0 deletions examples/profile-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
username: ${{ github.repository_owner }}
days: '365'
carrier: lincoln # Choose lincoln (default) or classic.
water: ocean # Choose ocean (default) or original for the old water.
jets: '1' # Choose 1, 2 or 3; more jets shorten busy calendars.
- name: Publish README images
env:
Expand Down
63 changes: 63 additions & 0 deletions scripts/preview_water.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Render a four-second water study and matching light/dark review images."""

import argparse
from pathlib import Path

from PIL import Image

from git_jet.art import CARRIERS, DEFAULT_CARRIER, carrier_sprite
from git_jet.export import indexed, palette
from git_jet.water import DEFAULT_WATER, POSITION, WATER_STYLES, water_sprite


def main():
parser = argparse.ArgumentParser()
parser.add_argument("--carrier", choices=CARRIERS, default=DEFAULT_CARRIER)
parser.add_argument("--water", choices=WATER_STYLES, default=DEFAULT_WATER)
args = parser.parse_args()
output = Path(__file__).resolve().parents[1] / "out/water-preview"
output.mkdir(parents=True, exist_ok=True)
mapping, rgb = palette()
frames = []
bow_frames = []
for index in range(80):
phase = index / 80
scene = Image.new("RGBA", (480, 320))
scene.alpha_composite(water_sprite(phase, water=args.water, carrier=args.carrier), POSITION)
scene.alpha_composite(carrier_sprite(phase, carrier=args.carrier), (65, 12))
frame = indexed(scene, mapping, rgb, 2).crop((0, 0, 960, 440))
frames.append(frame)
bow = frame.crop((45, 220, 425, 415)).resize((760, 390), Image.Resampling.NEAREST)
bow.putpalette(rgb)
bow_frames.append(bow)
frames[0].save(
output / f"{args.water}-{args.carrier}-motion-v4.gif",
save_all=True,
append_images=frames[1:],
duration=50,
loop=0,
transparency=0,
disposal=2,
optimize=False,
)
review = Image.new("RGB", (960, 880))
for row, color in enumerate(("#0d1117", "#ffffff")):
background = Image.new("RGBA", (960, 440), color)
background.alpha_composite(frames[0].convert("RGBA"))
review.paste(background.convert("RGB"), (0, row * 440))
review.save(output / f"{args.water}-{args.carrier}-backgrounds-v4.png")
bow_frames[0].save(
output / f"{args.water}-{args.carrier}-bow-v4.gif",
save_all=True,
append_images=bow_frames[1:],
duration=50,
loop=0,
transparency=0,
disposal=2,
optimize=False,
)
print(output)


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions scripts/run_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def run():
os.environ.get("JET_JETS", "1"),
"--carrier",
os.environ.get("JET_CARRIER", "lincoln"),
"--water",
os.environ.get("JET_WATER", "ocean"),
]
if demo == "true":
args += ["--demo", "--days", os.environ.get("JET_DAYS", "365")]
Expand Down
20 changes: 0 additions & 20 deletions src/git_jet/art.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,26 +153,6 @@ def block(u, v, w, depth, z, height):
CREAM,
)

# Detached stepped foam patches, rather than an opaque ocean rectangle.
angle = math.tau * (t % 1)
for index, u in enumerate((-7, 30, 70, 112, 158, 202, 241)):
x, y = project(u, 58, -2)
x += round(2 * math.sin(angle + index))
y += round(math.sin(angle + index * 0.7))
width = (22, 31, 24, 35, 23, 30, 21)[index]
draw.polygon(
[
(x - 7, y),
(x + width - 5, y - 10),
(x + width, y - 9),
(x + 1, y + 3),
(x - 10, y + 3),
],
fill=DEEP,
)
draw.line((x - 4, y, x + width - 7, y - 9), fill=WAVE, width=2)
draw.line((x, y - 1, x + width - 11, y - 8), fill=FOAM)

# Deep sidewalls, red waterline, and angular bow face in the reference view.
poly([(-18, 20, 24), (-18, 36, 24), (8, 52, 24), (8, 52, 0), (-18, 36, 0), (-18, 20, 7)], INK)
poly(
Expand Down
8 changes: 8 additions & 0 deletions src/git_jet/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .demo import demo_calendar
from .export import Options, render_assets
from .github import GitHubClient, GitHubError
from .water import DEFAULT_WATER, WATER_STYLES


def main(argv=None):
Expand All @@ -37,6 +38,12 @@ def main(argv=None):
default=DEFAULT_CARRIER,
help="carrier artwork (default: lincoln)",
)
render.add_argument(
"--water",
choices=WATER_STYLES,
default=DEFAULT_WATER,
help="water artwork: ocean or original (default: ocean)",
)
render.add_argument(
"--jets", type=int, choices=(1, 2, 3), default=1, help="simultaneous jets (default: 1)"
)
Expand Down Expand Up @@ -78,6 +85,7 @@ def main(argv=None):
options,
jets=args.jets,
carrier=args.carrier,
water=args.water,
seed=args.seed,
source=source_name,
auto_compress=args.auto_compress,
Expand Down
17 changes: 14 additions & 3 deletions src/git_jet/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
from dataclasses import dataclass
from pathlib import Path

from PIL import GifImagePlugin, Image, ImageChops
from PIL import GifImagePlugin, Image, ImageChops, ImageColor

from . import art
from .calendar import Calendar
from .flight import HEIGHT, WIDTH, route_seed
from .limits import FrameLimitError, GifSizeLimitError
from .render import Renderer
from .water import COLORS as WATER_COLORS
from .water import DEFAULT_WATER

ASSETS = ("git-jet.gif", "git-jet.png", "report.json", "calendar.json")
MAX_BYTES = 25_000_000
Expand All @@ -41,7 +43,12 @@ def palette():
*(art.explosion_sprite(i / 10) for i in range(10)),
*(art.jet_sprite(0, thrust, bank) for thrust in range(3) for bank in (-1, 0, 1)),
]
colors = sorted({color for sample in samples for _, color in sample.getcolors(256) if color[3]})
colors = sorted(
{
*(color for sample in samples for _, color in sample.getcolors(256) if color[3]),
*(ImageColor.getcolor(color, "RGBA") for color in WATER_COLORS),
}
)
if len(colors) > 255 or any(color[3] != 255 for color in colors):
raise ValueError("artwork must fit 255 opaque colors plus transparency")
entries = [(0, 0, 0, 0), *colors]
Expand Down Expand Up @@ -131,6 +138,7 @@ def export(
"attack_mode": "day-cell",
"jets": renderer.jet_count,
"carrier": renderer.carrier,
"water": renderer.water,
"seed": renderer.flight.seed,
"flights": [
{
Expand Down Expand Up @@ -181,6 +189,7 @@ def render_assets(
*,
jets=1,
carrier=art.DEFAULT_CARRIER,
water=DEFAULT_WATER,
seed=None,
source="live",
auto_compress=True,
Expand All @@ -193,7 +202,9 @@ def render_assets(
attempt = 0
for fps in rates:
try:
renderer = Renderer(calendar, fps=fps, seed=seed, jets=jets, carrier=carrier)
renderer = Renderer(
calendar, fps=fps, seed=seed, jets=jets, carrier=carrier, water=water
)
scales = (
range(options.scale, 0, -1)
if auto_compress and fps == options.fps
Expand Down
37 changes: 1 addition & 36 deletions src/git_jet/lincoln.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from PIL import Image, ImageDraw

from git_jet.art import DEEP, FOAM, GOLD, INK, LIGHT, WAVE, project, radar_sprite
from git_jet.art import FOAM, GOLD, INK, LIGHT, project, radar_sprite

HULL = "#546570"
LIT = "#71838d"
Expand Down Expand Up @@ -94,41 +94,6 @@ def box(u, v, width, depth, z, height, top=GREY):
top,
)

phase = math.tau * (t % 1)
for index, (u, v) in enumerate(
((-3, 27), (35, 34), (75, 40), (116, 57), (158, 57), (200, 57), (242, 56), (268, 47))
):
x, y = project(u, v, 0)
x += round(2 * math.sin(phase + index))
y += round(math.sin(phase + index * 0.8))
reach = (15, 21, 20, 25, 22, 24, 18, 14)[index]
draw.polygon(
[
(x - 8, y + 2),
(x + reach - 4, y - 7),
(x + reach + 4, y - 6),
(x + 2, y + 5),
(x - 10, y + 5),
],
fill=DEEP,
)
draw.line((x - 5, y + 1, x + reach - 4, y - 6), fill=WAVE, width=2)
draw.line((x - 1, y, x + reach - 9, y - 4), fill=FOAM)
x, y = project(-12, 10, 2)
draw.polygon(
[
(x - 10, y + 1),
(x - 4, y - 4),
(x + 2, y - 2),
(x + 9, y),
(x + 15, y + 3),
(x + 3, y + 6),
(x - 7, y + 5),
],
fill=WAVE,
)
draw.line((x - 7, y, x - 2, y - 2, x + 4, y + 1, x + 10, y + 2), fill=FOAM, width=2)

# A fine raked stem underneath the thin, straight bow deck edge.
stem, keel = (-23, 11, 24), (-10, 11, 8)
poly([stem, (-3, -2, 24), (23, 6, 24), (27, 28, 24), (18, 24, 4), keel], INK)
Expand Down
Loading
Loading