ascii-city 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ogme01
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.
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # ascii-city
2
+
3
+ A walkable — and drivable — ASCII cyberpunk city. By default it's an ambient
4
+ stroll: traffic cruises the avenues, pedestrians wander the sidewalks, and you
5
+ can hop into any car (E) just to drive around. Set `engine.game: true` in
6
+ js/config.js for GTA mode: missions, money, wanted stars, and cops. Three
7
+ layers:
8
+
9
+ - **The base** — `js/citygen.js`, a seeded city generator: spec in, deterministic
10
+ city model out. Pure data, no rendering, strictly printable ASCII (0x20–0x7E).
11
+ - **The sim** — `js/sim.js`, the shared gameplay core: player (on foot or
12
+ driving), civilian traffic, pedestrians, police + wanted stars, delivery
13
+ missions + money, radio stations, and BFS GPS routing.
14
+ - **The renderers** — `js/walker.js` (browser) and `js/tui.js` (terminal),
15
+ the same first-person raycaster drawing the same sim as glyphs.
16
+
17
+ `js/config.js` is the single input: `city` goes straight into `generateCity()`,
18
+ `engine` tunes the renderer. City specs land there.
19
+
20
+ ## Run — browser
21
+
22
+ ```
23
+ python3 serve.py 8670
24
+ ```
25
+
26
+ http://localhost:8670 — click to walk. WASD move, mouse look, shift run,
27
+ M minimap, N new seed, 1-4 glyph size, arrows work without pointer lock.
28
+
29
+ ## Run — terminal
30
+
31
+ ```
32
+ npx . # from the repo, or: npm i -g . && ascii-city
33
+ ```
34
+
35
+ Same city, same raycaster, rendered as ANSI truecolor cells. In terminals
36
+ with the kitty keyboard protocol (iTerm2, Ghostty, kitty, WezTerm) you get
37
+ real hold-to-move: hold WASD, shift to run, hold arrows or drag the mouse
38
+ to look. Elsewhere (e.g. Terminal.app, which never reports key releases)
39
+ it falls back to latched movement: `w` walks and keeps going, `s` stops
40
+ then reverses, space stops, `a`/`d`/arrows turn in eased steps, `r`
41
+ toggles run. `n` new city, `q` quits, either way. Flags: `--seed`,
42
+ `--palette neon|terminal|amber|rust|ice`, `--size WxD`, `--fov`, `--dist`,
43
+ and `--frame` to print a single frame and exit. Needs Node 18+ and a
44
+ truecolor terminal. `js/tui.js` mirrors `js/walker.js` — change the
45
+ projection or ramps in one, mirror it in the other.
46
+
47
+ ## The generator
48
+
49
+ ```js
50
+ import { generateCity } from './js/citygen.js';
51
+ const model = generateCity({ seed: 'ghost-harbor-412', size: { width: 200, depth: 160 } });
52
+ ```
53
+
54
+ Any subset of `DEFAULT_CONFIG` (see `js/citygen.js`) can be passed: road
55
+ spacing/widths/jitter, per-district weight/centerBias/heights/parcels/window
56
+ style/wall glyph (custom district types are kept), building tiers and antennas,
57
+ plazas, trees, every glyph. Each stage pulls from its own named RNG stream and
58
+ per-cell effects are coordinate hashes, so changing one parameter never
59
+ reshuffles unrelated parts of the city.
60
+
61
+ Model output: `cells` (road/sidewalk/building/park/plaza/tree), `roadAxis`
62
+ (lane markings), `heightMap` (per-cell height — what the raycaster marches),
63
+ `bIndex` (cell → building), `buildings` (footprint, tiers, district, window
64
+ style), plus blocks/trees/fountains/stats.
65
+
66
+ ## The walker
67
+
68
+ Per screen column: DDA front-to-back through the grid. Each cell contributes a
69
+ wall face and a top-surface strip, clipped against `yCur` (lowest unpainted row)
70
+ so occlusion is free. Window lattices are per-building coordinate hashes —
71
+ stable across frames. Distance fog is quantized so the glyph atlas stays small.
72
+
73
+ ## Debug page
74
+
75
+ http://localhost:8670/debug.html — slider playground over the generator
76
+ (plan view + skyline probe). Diagnostic tool only, not the product.
77
+
78
+ ## Roadmap
79
+
80
+ - traffic + pedestrians (roads/sidewalks already in the model)
81
+ - interiors, lifts, elevation
82
+ - signs, rain, audio
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ // npx gta6 — walk around an ASCII cyberpunk city in your terminal.
3
+ // No options. WASD to move, mouse drag to look, E to drive a car, Q to quit.
4
+ // (--frame renders one frame and exits; used for testing.)
5
+
6
+ import { run } from '../js/tui.js';
7
+
8
+ run({ oneFrame: process.argv.includes('--frame') });
package/css/style.css ADDED
@@ -0,0 +1,174 @@
1
+ :root {
2
+ --bg: #08090d;
3
+ --panel: #0e1015;
4
+ --panel2: #12141b;
5
+ --line: #1d2230;
6
+ --text: #c8d0e0;
7
+ --dim: #6a7488;
8
+ --accent: #53e9ff;
9
+ --accent2: #ff5ec8;
10
+ font-size: 14px;
11
+ }
12
+
13
+ * { box-sizing: border-box; margin: 0; padding: 0; }
14
+
15
+ body {
16
+ background: var(--bg);
17
+ color: var(--text);
18
+ font-family: ui-monospace, Menlo, Consolas, monospace;
19
+ min-height: 100vh;
20
+ }
21
+
22
+ header {
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ gap: 16px;
27
+ flex-wrap: wrap;
28
+ padding: 10px 16px;
29
+ border-bottom: 1px solid var(--line);
30
+ background: var(--panel);
31
+ position: sticky;
32
+ top: 0;
33
+ z-index: 5;
34
+ }
35
+
36
+ .logo {
37
+ font-size: 1.15rem;
38
+ font-weight: 700;
39
+ color: var(--accent);
40
+ letter-spacing: 0.02em;
41
+ }
42
+ .logo .cursor { animation: blink 1.1s steps(1) infinite; }
43
+ .logo em {
44
+ font-style: normal;
45
+ font-weight: 400;
46
+ font-size: 0.75rem;
47
+ color: var(--dim);
48
+ margin-left: 10px;
49
+ }
50
+ @keyframes blink { 50% { opacity: 0; } }
51
+
52
+ .toolbar {
53
+ display: flex;
54
+ align-items: center;
55
+ gap: 10px;
56
+ flex-wrap: wrap;
57
+ }
58
+ .toolbar label {
59
+ display: flex;
60
+ align-items: center;
61
+ gap: 6px;
62
+ font-size: 0.75rem;
63
+ color: var(--dim);
64
+ }
65
+ .toolbar input[type="text"] {
66
+ background: var(--bg);
67
+ border: 1px solid var(--line);
68
+ color: var(--accent);
69
+ font: inherit;
70
+ padding: 4px 8px;
71
+ width: 170px;
72
+ border-radius: 3px;
73
+ }
74
+ .toolbar select, .toolbar button {
75
+ background: var(--panel2);
76
+ border: 1px solid var(--line);
77
+ color: var(--text);
78
+ font: inherit;
79
+ font-size: 0.8rem;
80
+ padding: 4px 9px;
81
+ border-radius: 3px;
82
+ cursor: pointer;
83
+ }
84
+ .toolbar button:hover { border-color: var(--accent); color: var(--accent); }
85
+
86
+ main {
87
+ display: grid;
88
+ grid-template-columns: 250px 1fr;
89
+ gap: 14px;
90
+ padding: 14px 16px;
91
+ align-items: start;
92
+ }
93
+
94
+ #controls {
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: 10px;
98
+ position: sticky;
99
+ top: 62px;
100
+ }
101
+ fieldset {
102
+ border: 1px solid var(--line);
103
+ border-radius: 4px;
104
+ padding: 8px 10px 10px;
105
+ background: var(--panel);
106
+ }
107
+ legend {
108
+ font-size: 0.7rem;
109
+ text-transform: uppercase;
110
+ letter-spacing: 0.12em;
111
+ color: var(--accent2);
112
+ padding: 0 6px;
113
+ }
114
+ .ctl {
115
+ display: grid;
116
+ grid-template-columns: 86px 1fr 34px;
117
+ align-items: center;
118
+ gap: 8px;
119
+ margin-top: 5px;
120
+ }
121
+ .ctl label { font-size: 0.72rem; color: var(--dim); }
122
+ .ctl .val { font-size: 0.7rem; color: var(--text); text-align: right; }
123
+ input[type="range"] {
124
+ -webkit-appearance: none;
125
+ height: 3px;
126
+ background: var(--line);
127
+ border-radius: 2px;
128
+ }
129
+ input[type="range"]::-webkit-slider-thumb {
130
+ -webkit-appearance: none;
131
+ width: 11px; height: 11px;
132
+ border-radius: 2px;
133
+ background: var(--accent);
134
+ cursor: pointer;
135
+ }
136
+
137
+ #views { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
138
+
139
+ .panel {
140
+ border: 1px solid var(--line);
141
+ border-radius: 4px;
142
+ background: var(--panel);
143
+ overflow: hidden;
144
+ }
145
+ .panel h2 {
146
+ font-size: 0.72rem;
147
+ text-transform: uppercase;
148
+ letter-spacing: 0.12em;
149
+ color: var(--dim);
150
+ padding: 7px 12px;
151
+ border-bottom: 1px solid var(--line);
152
+ }
153
+ .panel h2 small { color: #3d4557; margin-left: 8px; text-transform: none; letter-spacing: 0; }
154
+
155
+ .canvasWrap {
156
+ overflow: auto;
157
+ background: #000;
158
+ max-height: 560px;
159
+ }
160
+ .canvasWrap.tall { max-height: 700px; }
161
+ canvas { display: block; }
162
+ canvas.fit { width: 100%; height: auto; image-rendering: pixelated; }
163
+
164
+ #stats {
165
+ font-size: 0.72rem;
166
+ color: var(--dim);
167
+ padding: 4px 2px 12px;
168
+ line-height: 1.6;
169
+ }
170
+
171
+ @media (max-width: 900px) {
172
+ main { grid-template-columns: 1fr; }
173
+ #controls { position: static; }
174
+ }
package/css/walker.css ADDED
@@ -0,0 +1,61 @@
1
+ html, body {
2
+ margin: 0;
3
+ height: 100%;
4
+ overflow: hidden;
5
+ background: #000;
6
+ }
7
+
8
+ #view {
9
+ display: block;
10
+ width: 100vw;
11
+ height: 100vh;
12
+ image-rendering: pixelated;
13
+ cursor: crosshair;
14
+ }
15
+
16
+ #hud {
17
+ position: fixed;
18
+ left: 12px;
19
+ bottom: 10px;
20
+ color: #9fb0c8;
21
+ font: 12px ui-monospace, Menlo, Consolas, monospace;
22
+ text-shadow: 0 1px 3px #000;
23
+ opacity: 0.85;
24
+ pointer-events: none;
25
+ white-space: pre;
26
+ }
27
+
28
+ #mini {
29
+ position: fixed;
30
+ right: 12px;
31
+ top: 12px;
32
+ border: 1px solid #2a3142;
33
+ opacity: 0.9;
34
+ image-rendering: pixelated;
35
+ pointer-events: none;
36
+ }
37
+
38
+ #intro {
39
+ position: fixed;
40
+ inset: 0;
41
+ display: flex;
42
+ flex-direction: column;
43
+ gap: 10px;
44
+ align-items: center;
45
+ justify-content: center;
46
+ background: rgba(3, 4, 8, 0.72);
47
+ color: #c8d0e0;
48
+ font: 14px ui-monospace, Menlo, Consolas, monospace;
49
+ line-height: 1.9;
50
+ text-align: center;
51
+ cursor: pointer;
52
+ user-select: none;
53
+ }
54
+ #intro.hidden { display: none; }
55
+ #intro .title {
56
+ color: #53e9ff;
57
+ font-size: 24px;
58
+ font-weight: 700;
59
+ letter-spacing: 0.06em;
60
+ }
61
+ #intro .dim { color: #6a7488; }
package/debug.html ADDED
@@ -0,0 +1,52 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>ascii-city — debug</title>
7
+ <link rel="stylesheet" href="css/style.css">
8
+ </head>
9
+ <body>
10
+ <header>
11
+ <div class="logo">ascii-city<span class="cursor">_</span><em>seeded city factory</em></div>
12
+ <div class="toolbar">
13
+ <label class="seedbox">seed
14
+ <input id="seed" type="text" spellcheck="false" autocomplete="off">
15
+ </label>
16
+ <button id="reroll" title="R">reroll</button>
17
+ <label>preset
18
+ <select id="preset"></select>
19
+ </label>
20
+ <label>palette
21
+ <select id="palette"></select>
22
+ </label>
23
+ <label>glyphs
24
+ <select id="zoom">
25
+ <option value="8">S</option>
26
+ <option value="10" selected>M</option>
27
+ <option value="13">L</option>
28
+ </select>
29
+ </label>
30
+ <button id="copyConfig">copy config</button>
31
+ <button id="exportModel">export model</button>
32
+ </div>
33
+ </header>
34
+
35
+ <main>
36
+ <aside id="controls"></aside>
37
+ <section id="views">
38
+ <div class="panel">
39
+ <h2>skyline probe <small>southern elevation, 3 depth bands</small></h2>
40
+ <div class="canvasWrap"><canvas id="skyline" class="fit"></canvas></div>
41
+ </div>
42
+ <div class="panel">
43
+ <h2>plan view <small>top-down, glyph brightness = height</small></h2>
44
+ <div class="canvasWrap tall"><canvas id="map" class="fit"></canvas></div>
45
+ </div>
46
+ <footer id="stats"></footer>
47
+ </section>
48
+ </main>
49
+
50
+ <script type="module" src="js/main.js"></script>
51
+ </body>
52
+ </html>
package/index.html ADDED
@@ -0,0 +1,20 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>ascii-city</title>
7
+ <link rel="stylesheet" href="css/walker.css">
8
+ </head>
9
+ <body>
10
+ <canvas id="view"></canvas>
11
+ <canvas id="mini"></canvas>
12
+ <div id="hud"></div>
13
+ <div id="intro">
14
+ <div class="title">ASCII CITY</div>
15
+ <div>click to walk</div>
16
+ <div class="dim">WASD move &nbsp;·&nbsp; mouse look &nbsp;·&nbsp; E enter/exit car &nbsp;·&nbsp; shift run<br>space handbrake &nbsp;·&nbsp; R radio &nbsp;·&nbsp; M minimap &nbsp;·&nbsp; N new city &nbsp;·&nbsp; 1-4 glyph size</div>
17
+ </div>
18
+ <script type="module" src="js/walker.js"></script>
19
+ </body>
20
+ </html>