deadair 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deadair-1.0.0/.gitignore +5 -0
- deadair-1.0.0/DESIGN.md +136 -0
- deadair-1.0.0/LICENSE +21 -0
- deadair-1.0.0/PKG-INFO +109 -0
- deadair-1.0.0/README.md +81 -0
- deadair-1.0.0/deadair/__main__.py +4 -0
- deadair-1.0.0/deadair/app.py +503 -0
- deadair-1.0.0/deadair/art.py +470 -0
- deadair-1.0.0/deadair/content.py +1144 -0
- deadair-1.0.0/deadair/state.py +687 -0
- deadair-1.0.0/play +2 -0
- deadair-1.0.0/pyproject.toml +53 -0
deadair-1.0.0/.gitignore
ADDED
deadair-1.0.0/DESIGN.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# DEAD AIR — design notes
|
|
2
|
+
|
|
3
|
+
> **Spoilers.** This document describes the mechanics, the escalation, and the
|
|
4
|
+
> shape of the endings. If you would rather play it cold, stop here.
|
|
5
|
+
|
|
6
|
+
## What's implemented
|
|
7
|
+
|
|
8
|
+
**Act One: the park.** The game opens at the trailhead at 18:40 with 85
|
|
9
|
+
minutes of usable dusk and no cave in sight. Five locations, five people to
|
|
10
|
+
talk to, eleven things to examine — and nowhere near enough light to do all
|
|
11
|
+
of it. Talking costs 3 minutes, examining costs 2-4, walking costs 6-9. Close
|
|
12
|
+
work (a clue costing 4 minutes) cannot be done below 25% daylight at all, so
|
|
13
|
+
the ground you cover early is ground you actually get.
|
|
14
|
+
|
|
15
|
+
The helmet lamp starts **off** — it is daylight, and switching it on early
|
|
16
|
+
tells you nothing. `F` toggles it, and once the day is gone it buys back a
|
|
17
|
+
description layer the dusk took away. Underground `F` is refused: the lamp is
|
|
18
|
+
why you are alive down there.
|
|
19
|
+
|
|
20
|
+
This is deliberate teaching. The park runs exactly the mechanic the cave will
|
|
21
|
+
later charge you for — a light budget that buys you description — using a
|
|
22
|
+
resource you cannot die from spending. By the time the lamp starts draining
|
|
23
|
+
you already know what running out of light feels like.
|
|
24
|
+
|
|
25
|
+
You are cleared to descend once you have Trammell's brief and have spoken to
|
|
26
|
+
Wren's mother. Everything else in Act One is optional and none of it changes
|
|
27
|
+
a mechanic; it changes what you understand while a mechanic is happening to
|
|
28
|
+
you. See PAYOFFS.
|
|
29
|
+
|
|
30
|
+
**The bridge.** Taking the descent runs the night ground search past you in
|
|
31
|
+
three paragraphs, resets the clock, and puts you on the lip of the sink at
|
|
32
|
+
02:14, which is where the game used to start.
|
|
33
|
+
|
|
34
|
+
**Light as a rendering budget.** Every room is written in three layers —
|
|
35
|
+
arm's length, the room, and beyond. As the cell drains you stop being *told*
|
|
36
|
+
what is there. Below 25% the far layer is gone; below 8% you get one sentence
|
|
37
|
+
about rock. Dimming the beam doubles your endurance and costs you a layer, so
|
|
38
|
+
the meter is a real decision rather than a countdown.
|
|
39
|
+
|
|
40
|
+
**Resources.** Lamp (burns per minute, two spare cells hidden in the cave),
|
|
41
|
+
air (CO₂ pools in the low passage; a fast crossing is survivable, loitering
|
|
42
|
+
is not), rope (60 m, spent permanently when you rig a pitch — and you need
|
|
43
|
+
20 m in reserve for a route that closes behind you), radio signal (by depth).
|
|
44
|
+
|
|
45
|
+
**A cave that answers back.** Ambience is keyed to a hidden dread band driven
|
|
46
|
+
by depth and by what you have seen. Deep in, `L` stops returning cave noises
|
|
47
|
+
and starts returning *your own past actions* — the game logs what you did in
|
|
48
|
+
each room and plays it back at you from the wrong direction, hours late.
|
|
49
|
+
|
|
50
|
+
**Escalation with teeth.** The first half is procedure: rigging, survey tags,
|
|
51
|
+
radio checks that work. Things go wrong structurally, not with a jump scare —
|
|
52
|
+
the radio degrades, then degrades incorrectly, then is clear at sixty metres
|
|
53
|
+
of limestone and saying something impossible. Taking the find starts a
|
|
54
|
+
115-minute pursuit clock, and the way you came in is no longer open.
|
|
55
|
+
|
|
56
|
+
**Seven endings.** Two ways out, five ways not to.
|
|
57
|
+
|
|
58
|
+
The two you survive resolve against the clock. Act Two starts at 02:14 and a
|
|
59
|
+
run is anywhere from twelve minutes (straight back out of the Letterbox) to
|
|
60
|
+
most of the night, so nothing about the exit can be written down in advance:
|
|
61
|
+
`SKY` and `ARRIVAL` in `state.py` are indexed by `Game._sky_band()`, which
|
|
62
|
+
knows roughly where September twilight falls at this latitude. Coming out
|
|
63
|
+
after Trammell's 06:00 turnaround adds a line, because you were given a
|
|
64
|
+
turnaround time in front of the family and the game should notice you missed
|
|
65
|
+
it. A helmet run at best speed is 2h43 and puts you out at 04:57.
|
|
66
|
+
|
|
67
|
+
**Graphics that carry the mechanic.** Every room has a viewport above the
|
|
68
|
+
prose, and it is not decoration. Scenes are described as signed-distance
|
|
69
|
+
fields and raymarched at runtime, then lit by your actual lamp — a headlamp
|
|
70
|
+
at the eye with a cone, inverse-square falloff, and a reach that scales with
|
|
71
|
+
what is left in the cell. Losing the far prose layer and watching the picture
|
|
72
|
+
pull in are the same event. Above ground the light is the sky instead, and it
|
|
73
|
+
goes out on the same schedule the dusk does.
|
|
74
|
+
|
|
75
|
+
## Layout
|
|
76
|
+
|
|
77
|
+
deadair/content.py the park, the cave, the prose, the tables — pure data
|
|
78
|
+
deadair/state.py resources, hazards, dread, endings — pure logic
|
|
79
|
+
deadair/art.py scene geometry and the renderer — pure rendering
|
|
80
|
+
deadair/app.py Textual widgets, meters, viewport, the survey map
|
|
81
|
+
|
|
82
|
+
Content is fully separated from mechanics, so writing is edited without
|
|
83
|
+
touching the engine. A new room is a `Room(...)` in `ROOMS` plus an `Exit`
|
|
84
|
+
pointing at it; give it `mx`/`my` grid coordinates and it appears on the
|
|
85
|
+
survey map automatically, and a `Scene(...)` in `art.SCENES` under the same
|
|
86
|
+
id and it draws itself. A room with no scene falls back to a generic passage
|
|
87
|
+
rather than failing.
|
|
88
|
+
|
|
89
|
+
Act One rooms carry `people` (a `Person` with `beats` consumed one per ask)
|
|
90
|
+
and `clues` (a `Clue` costing minutes and granting a flag), and are tagged
|
|
91
|
+
`park` so they stay off the survey map and out of the passage count.
|
|
92
|
+
`Game.choices()` returns people, then clues, then exits as one numbered list;
|
|
93
|
+
in Act Two the first two are empty and it collapses to the exits, which is
|
|
94
|
+
what it always was.
|
|
95
|
+
|
|
96
|
+
## The renderer
|
|
97
|
+
|
|
98
|
+
`art.py` has five scene kinds — `tube`, `chamber`, `hole`, `forest`, `void` —
|
|
99
|
+
and one shading pipeline. A `Scene` is about twenty numbers: passage radii,
|
|
100
|
+
bedding relief, chamber extents, tree and canopy counts, fog, lamp reach,
|
|
101
|
+
palette, camera tilt.
|
|
102
|
+
|
|
103
|
+
Notes for anyone changing it:
|
|
104
|
+
|
|
105
|
+
- The SDF is displaced by noise, which breaks the Lipschitz bound, so the
|
|
106
|
+
march steps at 0.62 of the reported distance underground. Above ground the
|
|
107
|
+
dominant surface is an exact plane and it strides at 0.92 in fewer steps —
|
|
108
|
+
worth about a second a frame.
|
|
109
|
+
- The march runs on a one-octave SDF and the normals are taken with two. All
|
|
110
|
+
the visible texture comes from the normals; the march only needs to know
|
|
111
|
+
roughly where the rock is.
|
|
112
|
+
- Chambers are smooth-minimum boxes. A hard corner reads instantly as
|
|
113
|
+
architecture rather than cave.
|
|
114
|
+
- Frames are cached on `(room, light band, sky band, w, h)` — quantised, or
|
|
115
|
+
the cache would never hit — rendered on a worker thread, and the rooms
|
|
116
|
+
reachable from where you are standing are drawn before you walk into them.
|
|
117
|
+
A forest frame costs about two seconds, a cave frame well under one.
|
|
118
|
+
|
|
119
|
+
## Tuning
|
|
120
|
+
|
|
121
|
+
Constants at the top of `state.py`: `LAMP_BURN_HIGH`, `LAMP_BURN_LOW`,
|
|
122
|
+
`AIR_DRAIN`, `ROPE_TOTAL`, `PURSUIT_LIMIT`, `BYPASS_ROPE`, and for Act One
|
|
123
|
+
`DAYLIGHT_TOTAL`, `PARK_START`, `CLOSE_WORK`.
|
|
124
|
+
|
|
125
|
+
Act One is tuned so that a thorough player sees roughly two thirds of it.
|
|
126
|
+
Raising `DAYLIGHT_TOTAL` past about 110 lets you exhaust the park, which
|
|
127
|
+
costs the act its only source of pressure.
|
|
128
|
+
|
|
129
|
+
A clean run to the bottom and back is about 3 hours of game time and lands
|
|
130
|
+
you at the surface around 10–20% lamp.
|
|
131
|
+
|
|
132
|
+
`Game(seed=N)` makes the ambience deterministic for testing, and `--seed N`
|
|
133
|
+
threads one in from the shell — `deadair --seed 7`, or `./play --seed 7`. The
|
|
134
|
+
seed is held on the app, so `N` for a new run reseeds identically rather than
|
|
135
|
+
drifting. It fixes only the RNG draws; the cave, the prose and the endings
|
|
136
|
+
are not procedural.
|
deadair-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Austin Guidry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
deadair-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: deadair
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A first-person cave-horror game for the terminal, with scenes raymarched at runtime and lit by your own lamp.
|
|
5
|
+
Project-URL: Homepage, https://github.com/AustinGuidry/dead-air
|
|
6
|
+
Project-URL: Source, https://github.com/AustinGuidry/dead-air
|
|
7
|
+
Project-URL: Issues, https://github.com/AustinGuidry/dead-air/issues
|
|
8
|
+
Author: Austin Guidry
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: game,horror,interactive-fiction,terminal,textual,tui
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Games/Entertainment
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
|
+
Requires-Dist: numpy>=1.26
|
|
24
|
+
Requires-Dist: pillow>=10.0
|
|
25
|
+
Requires-Dist: textual-image>=0.13
|
|
26
|
+
Requires-Dist: textual>=8.0
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# DEAD AIR
|
|
30
|
+
|
|
31
|
+
A first-person cave-horror game for the terminal. You are a search-and-rescue
|
|
32
|
+
officer entering an unsurveyed sink in Piney Ridge National Park, nineteen
|
|
33
|
+
hours behind a solo caver who did not come out.
|
|
34
|
+
|
|
35
|
+
It opens above ground. You have one evening of light to walk the approach,
|
|
36
|
+
read the ground, and talk to the people who saw Wren Alcott last — and then
|
|
37
|
+
you go down the hole.
|
|
38
|
+
|
|
39
|
+
Every room draws itself. The scenes are raymarched at runtime and lit by your
|
|
40
|
+
actual lamp, so the picture loses reach and detail as the cell dies, for the
|
|
41
|
+
same reason the prose does.
|
|
42
|
+
|
|
43
|
+
Built with [Textual](https://textual.textualize.io/). Runs in kitty — the
|
|
44
|
+
graphics use the kitty protocol, and fall back to sixel and then to Unicode
|
|
45
|
+
half-blocks elsewhere, so it plays in any terminal and looks best in one that
|
|
46
|
+
can draw. Ghostty, WezTerm and Konsole speak the protocol too.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
Needs Python 3.12 or newer. [pipx](https://pipx.pypa.io) is the tidiest way —
|
|
51
|
+
it keeps the game and its dependencies in their own environment and puts
|
|
52
|
+
`deadair` on your PATH:
|
|
53
|
+
|
|
54
|
+
pipx install git+https://github.com/AustinGuidry/dead-air
|
|
55
|
+
|
|
56
|
+
Or with plain pip, ideally into a virtualenv:
|
|
57
|
+
|
|
58
|
+
pip install git+https://github.com/AustinGuidry/dead-air
|
|
59
|
+
|
|
60
|
+
Then, from anywhere:
|
|
61
|
+
|
|
62
|
+
deadair
|
|
63
|
+
|
|
64
|
+
Pass `--seed N` to fix the run's coin-flips — the same seed always gives you
|
|
65
|
+
the same cave noises. `--help` lists the options.
|
|
66
|
+
|
|
67
|
+
## Running from a clone
|
|
68
|
+
|
|
69
|
+
If you would rather have the source to hand:
|
|
70
|
+
|
|
71
|
+
git clone https://github.com/AustinGuidry/dead-air
|
|
72
|
+
cd dead-air
|
|
73
|
+
python3 -m venv .venv
|
|
74
|
+
.venv/bin/pip install -r requirements.txt
|
|
75
|
+
./play
|
|
76
|
+
|
|
77
|
+
## Controls
|
|
78
|
+
|
|
79
|
+
| key | action |
|
|
80
|
+
|-------|--------|
|
|
81
|
+
| `1`–`9` | take the numbered action — someone to talk to, something to look at, or a way on |
|
|
82
|
+
| `L` | listen |
|
|
83
|
+
| `R` | radio basecamp |
|
|
84
|
+
| `X` | look again |
|
|
85
|
+
| `F` | switch the helmet lamp on or off |
|
|
86
|
+
| `D` | stop the beam down — half the burn rate, half the sight |
|
|
87
|
+
| `C` | swap in a spare cell |
|
|
88
|
+
| `N` | new run |
|
|
89
|
+
| `Q` | quit |
|
|
90
|
+
|
|
91
|
+
`F` is for above ground — you start in daylight with the lamp off, and the
|
|
92
|
+
evening does not wait for you. `D` and `C` are underground only, where the
|
|
93
|
+
lamp is not something you get to switch off.
|
|
94
|
+
|
|
95
|
+
Take your time in the first few rooms. The cave teaches you what it wants
|
|
96
|
+
before it asks you for anything.
|
|
97
|
+
|
|
98
|
+
You cannot see everything in the park before the light goes, and that is the
|
|
99
|
+
point of the park.
|
|
100
|
+
|
|
101
|
+
## Design notes
|
|
102
|
+
|
|
103
|
+
Mechanics, escalation, module layout, and tuning constants are documented in
|
|
104
|
+
[DESIGN.md](DESIGN.md) — **that file spoils the game**, so read it only if you
|
|
105
|
+
are here to work on the code rather than to play.
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT — see [LICENSE](LICENSE).
|
deadair-1.0.0/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# DEAD AIR
|
|
2
|
+
|
|
3
|
+
A first-person cave-horror game for the terminal. You are a search-and-rescue
|
|
4
|
+
officer entering an unsurveyed sink in Piney Ridge National Park, nineteen
|
|
5
|
+
hours behind a solo caver who did not come out.
|
|
6
|
+
|
|
7
|
+
It opens above ground. You have one evening of light to walk the approach,
|
|
8
|
+
read the ground, and talk to the people who saw Wren Alcott last — and then
|
|
9
|
+
you go down the hole.
|
|
10
|
+
|
|
11
|
+
Every room draws itself. The scenes are raymarched at runtime and lit by your
|
|
12
|
+
actual lamp, so the picture loses reach and detail as the cell dies, for the
|
|
13
|
+
same reason the prose does.
|
|
14
|
+
|
|
15
|
+
Built with [Textual](https://textual.textualize.io/). Runs in kitty — the
|
|
16
|
+
graphics use the kitty protocol, and fall back to sixel and then to Unicode
|
|
17
|
+
half-blocks elsewhere, so it plays in any terminal and looks best in one that
|
|
18
|
+
can draw. Ghostty, WezTerm and Konsole speak the protocol too.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
Needs Python 3.12 or newer. [pipx](https://pipx.pypa.io) is the tidiest way —
|
|
23
|
+
it keeps the game and its dependencies in their own environment and puts
|
|
24
|
+
`deadair` on your PATH:
|
|
25
|
+
|
|
26
|
+
pipx install git+https://github.com/AustinGuidry/dead-air
|
|
27
|
+
|
|
28
|
+
Or with plain pip, ideally into a virtualenv:
|
|
29
|
+
|
|
30
|
+
pip install git+https://github.com/AustinGuidry/dead-air
|
|
31
|
+
|
|
32
|
+
Then, from anywhere:
|
|
33
|
+
|
|
34
|
+
deadair
|
|
35
|
+
|
|
36
|
+
Pass `--seed N` to fix the run's coin-flips — the same seed always gives you
|
|
37
|
+
the same cave noises. `--help` lists the options.
|
|
38
|
+
|
|
39
|
+
## Running from a clone
|
|
40
|
+
|
|
41
|
+
If you would rather have the source to hand:
|
|
42
|
+
|
|
43
|
+
git clone https://github.com/AustinGuidry/dead-air
|
|
44
|
+
cd dead-air
|
|
45
|
+
python3 -m venv .venv
|
|
46
|
+
.venv/bin/pip install -r requirements.txt
|
|
47
|
+
./play
|
|
48
|
+
|
|
49
|
+
## Controls
|
|
50
|
+
|
|
51
|
+
| key | action |
|
|
52
|
+
|-------|--------|
|
|
53
|
+
| `1`–`9` | take the numbered action — someone to talk to, something to look at, or a way on |
|
|
54
|
+
| `L` | listen |
|
|
55
|
+
| `R` | radio basecamp |
|
|
56
|
+
| `X` | look again |
|
|
57
|
+
| `F` | switch the helmet lamp on or off |
|
|
58
|
+
| `D` | stop the beam down — half the burn rate, half the sight |
|
|
59
|
+
| `C` | swap in a spare cell |
|
|
60
|
+
| `N` | new run |
|
|
61
|
+
| `Q` | quit |
|
|
62
|
+
|
|
63
|
+
`F` is for above ground — you start in daylight with the lamp off, and the
|
|
64
|
+
evening does not wait for you. `D` and `C` are underground only, where the
|
|
65
|
+
lamp is not something you get to switch off.
|
|
66
|
+
|
|
67
|
+
Take your time in the first few rooms. The cave teaches you what it wants
|
|
68
|
+
before it asks you for anything.
|
|
69
|
+
|
|
70
|
+
You cannot see everything in the park before the light goes, and that is the
|
|
71
|
+
point of the park.
|
|
72
|
+
|
|
73
|
+
## Design notes
|
|
74
|
+
|
|
75
|
+
Mechanics, escalation, module layout, and tuning constants are documented in
|
|
76
|
+
[DESIGN.md](DESIGN.md) — **that file spoils the game**, so read it only if you
|
|
77
|
+
are here to work on the code rather than to play.
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT — see [LICENSE](LICENSE).
|