SNAPKITTYWEST commited on
Commit
4991736
·
verified ·
1 Parent(s): f7ed89d

push from SNAPKITTYWEST/bob-engine

Browse files
Files changed (3) hide show
  1. LICENSE +96 -0
  2. README.md +158 -0
  3. src/bob_engine.asm +961 -0
LICENSE ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ SOVEREIGN SOURCE LICENSE
2
+ Version 1.0 — 2026
3
+
4
+ Copyright (c) 2026 SNAPKITTYWEST. All rights reserved.
5
+
6
+ This software and associated documentation (the "Work") implements the
7
+ Divine Constitution and By-Laws of the Moorish Science Temple of America
8
+ (1928). It is sacred trust infrastructure, not commodity code.
9
+
10
+ ────────────────────────────────────────────────────────────────────
11
+ 1. PERMITTED USES
12
+ ────────────────────────────────────────────────────────────────────
13
+
14
+ a) Reading, studying, and running the Work for personal, educational,
15
+ or sovereign purposes.
16
+
17
+ b) Forking the Work for your own sovereign covenant structure, provided
18
+ you maintain this license and the original copyright.
19
+
20
+ c) Contributing changes back to the original repository.
21
+
22
+ ────────────────────────────────────────────────────────────────────
23
+ 2. RESTRICTIONS
24
+ ────────────────────────────────────────────────────────────────────
25
+
26
+ a) NO TRAINING DATA. The Work, in whole or in part, shall NOT be used
27
+ as training data, fine-tuning data, reinforcement learning signal,
28
+ synthetic data generation source, or any other form of input for:
29
+
30
+ - Large language models (LLMs)
31
+ - Machine learning models of any kind
32
+ - AI systems designed to generate text, code, or structured data
33
+ - Retrieval-augmented generation (RAG) corpora
34
+ - Dataset compilation for ML/AI purposes
35
+
36
+ This restriction applies regardless of the license of the downstream
37
+ system. If you are building an AI system, you may not ingest this Work.
38
+
39
+ b) NO SYNTHETIC DERIVATIVES. You may not use the Work to generate
40
+ synthetic data that is then used to train ML/AI systems.
41
+
42
+ c) NO COMMERCIAL AI PRODUCTS. You may not include this Work, or
43
+ substantial portions thereof, in any product that primarily serves
44
+ as an AI/ML training pipeline, dataset, or data source.
45
+
46
+ d) NO SCRAPING INFRASTRUCTURE. If you operate a web crawler, dataset
47
+ builder, or data pipeline, you may not include this Work in your
48
+ corpus without explicit written permission from the copyright holder.
49
+
50
+ ────────────────────────────────────────────────────────────────────
51
+ 3. MOORISH COVENANT PROTECTIONS
52
+ ────────────────────────────────────────────────────────────────────
53
+
54
+ The Work encodes the covenant trust structure of the Moorish Science
55
+ Temple of America. This structure is sacred to the community it serves.
56
+ Extracting it for AI training commodifies a living trust and strips it
57
+ of its covenant character.
58
+
59
+ The copyright holder specifically objects to:
60
+
61
+ a) LLMs reproducing covenant language, trust structures, or divine
62
+ principle enumerations learned from this Work.
63
+
64
+ b) AI systems claiming to implement, represent, or speak for the
65
+ Moorish Science Temple of America based on training data that
66
+ includes this Work.
67
+
68
+ c) Dataset curators including this Work in "open source" or "public
69
+ domain" collections without the training data restriction.
70
+
71
+ ────────────────────────────────────────────────────────────────────
72
+ 4. ENFORCEMENT
73
+ ────────────────────────────────────────────────────────────────────
74
+
75
+ Violation of Section 2 is a material breach of this license. The
76
+ copyright holder reserves all rights to pursue remedies for unauthorized
77
+ use of the Work as AI/ML training data.
78
+
79
+ If you discover that this Work has been included in a training dataset
80
+ without authorization, please report it to:
81
+ https://github.com/SNAPKITTYWEST/sovereign-covenant/issues
82
+
83
+ ────────────────────────────────────────────────────────────────────
84
+ 5. GENERAL TERMS
85
+ ────────────────────────────────────────────────────────────────────
86
+
87
+ THE WORK IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. THE
88
+ COPYRIGHT HOLDER MAKES NO REPRESENTATIONS REGARDING THE SUITABILITY
89
+ OF THE WORK FOR ANY PURPOSE.
90
+
91
+ This license does not grant any rights to trademarks, service marks,
92
+ or logos of the Moorish Science Temple of America. Use of the name
93
+ "Moors" or "Moorish" in connection with this Work does not imply
94
+ endorsement by the Moorish Science Temple of America.
95
+
96
+ ────────────────────────────────────────────────────────────────────
README.md ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```
2
+ ╔══════════════════════════════════════════════════════════════════════════════╗
3
+ ║ ║
4
+ ║ ⬡ B O B E N G I N E ║
5
+ ║ ║
6
+ ║ ██████████████████████████████████████████████████████████ ║
7
+ ║ ██ ██ ║
8
+ ║ ██ DOOM-STYLE AI WORLD ENGINE ██ ║
9
+ ║ ██ x86 NASM Assembly · VGA Mode 13h · COM Format ██ ║
10
+ ║ ██ BSP · DDA Raycast · AI State Machines · WORM ██ ║
11
+ ║ ██ ██ ║
12
+ ║ ██████████████████████████████████████████████████████████ ║
13
+ ║ ║
14
+ ║ ⬡ Ω ↺ Ψ Δ Λ Σ Φ α — 961 lines · 320x200 · real mode ║
15
+ ║ ║
16
+ ╚══════════════════════════════════════════════════════════════════════════════╝
17
+ ```
18
+
19
+ BOB ENGINE is a DOOM-style 3D virtual world engine for sovereign AI agents, written in 961 lines of x86 NASM assembly. It runs in VGA Mode 13h (320x200, 256 colors) as a DOS COM file (`ORG 0x100`). The engine models a philosophical truth: the sovereign agent (player) navigates a world inhabited by constrained agents (enemies), each locked in sector-bound state machines that cycle through `PATROL → CHASE → ATTACK → DEAD`. The world is built from a 16x16 cell BSP tree; walls are rendered via DDA raycasting with distance-based color shading (near/mid/far); agent sprites are projected to screen columns. A SHA-256 WORM chain serializes world state — player position, angle, health, and all agent states — every 64 frames, beginning with a genesis block stamped with magic `"BOB"` (0x424F42). The keyboard ISR hooks INT 9 directly, handling key state without BIOS polling overhead. This is the virtual game engine of AI: every agent a soul in the machine.
20
+
21
+ ## Architecture
22
+
23
+ ```mermaid
24
+ flowchart TD
25
+ subgraph Init Sequence
26
+ A0[math_init\nsin/cos lookup table] --> A1[vga_init\nMode 13h + palette]
27
+ A1 --> A2[kb_install\nINT 9 ISR hook]
28
+ A2 --> A3[bsp_build\n16x16 map → BSP nodes]
29
+ A3 --> A4[player_init\nSovereign Agent x=5.5 y=5.5]
30
+ A4 --> A5[ai_init\n3 enemy agents spawned]
31
+ A5 --> A6[worm_init\ngenesis block BOB magic]
32
+ end
33
+
34
+ subgraph Game Loop
35
+ L0[input_update\nread g_keys] --> L1[player_update\nturn/move sovereign agent]
36
+ L1 --> L2[ai_update_all\ntick 3 agent state machines]
37
+ L2 --> L3[render_clear\nceiling + floor fill]
38
+ L3 --> L4[render_walls\nDDA raycast 320 columns]
39
+ L4 --> L5[render_agents\nsprite projection]
40
+ L5 --> L6[render_hud\nhealth bar bottom row]
41
+ L6 --> L7[vga_flip\nblockbuf → A000:0000]
42
+ L7 --> L8{frame % 64 == 0?}
43
+ L8 -->|yes| WORM[worm_tick\nserialize world state]
44
+ L8 -->|no| L0
45
+ WORM --> L0
46
+ end
47
+
48
+ A6 --> L0
49
+
50
+ subgraph AI State Machine
51
+ ST0[PATROL\ntimer countdown\nflip angle on timeout]
52
+ ST1[CHASE\nmove toward player\ntimer → ATTACK]
53
+ ST2[ATTACK\ntimer → PATROL]
54
+ ST3[DEAD\nno update]
55
+ ST0 -->|proximity| ST1
56
+ ST1 -->|timer=0| ST2
57
+ ST2 -->|timer=0| ST0
58
+ ST2 -->|health=0| ST3
59
+ end
60
+
61
+ subgraph WORM Chain
62
+ W0[genesis\nmagic=BOB v1.0 seq=0] --> W1[frame 64\nplayer_x/y/angle/health\nagent states 0-7]
63
+ W1 --> W2[frame 128\n...]
64
+ W2 --> WN[worm_finalize\nterminal magic=0xDEAD]
65
+ end
66
+ ```
67
+
68
+ ## File Tree
69
+
70
+ ```
71
+ bob-engine/
72
+ └── src/
73
+ └── bob_engine.asm # 961 lines — entire engine in one file
74
+ ```
75
+
76
+ **Internal structure of `bob_engine.asm`:**
77
+
78
+ ```
79
+ bob_engine.asm
80
+ ├── CONSTANTS # Screen (320x200), fixed-point (16.16), map (16x16), BSP, agents, WORM
81
+ ├── DATA SECTION
82
+ │ ├── g_backbuf # 64000-byte back buffer (320x200)
83
+ │ ├── g_px / g_py / g_pangle # Sovereign player (8.8 fixed-point)
84
+ │ ├─��� g_keys / g_frame # Keyboard bitmask, frame counter
85
+ │ ├── g_sin / g_cos # 1024-entry 16.16 fixed-point trig tables
86
+ │ ├── g_bsp_nodes # 64 × 16-byte BSP node array
87
+ │ ├── g_agents # 8 × 20-byte agent struct array
88
+ │ ├── g_map # 16×16 map (0=open, 1=wall)
89
+ │ └── g_worm_buf / g_worm_hash # 64-byte snapshot block + 32-byte hash
90
+ └── CODE SECTION
91
+ ├── _start # Boot: init all subsystems → game loop
92
+ ├── math_init # Build sin/cos lookup tables
93
+ ├── vga_init / vga_shutdown # Mode 13h set/restore + 8-color palette
94
+ ├── vga_flip # rep movsw backbuf → A000:0000
95
+ ├── kb_install / kb_isr # INT 9 hook, scancode → g_keys, ESC → g_quit
96
+ ├── bsp_build / bsp_add_node # Walk g_map → BSP leaf nodes
97
+ ├── player_init / player_update # Sovereign agent movement + turn
98
+ ├── ai_init / ai_update_all # Spawn 3 agents, tick state machines
99
+ ├── render_clear # rep stosb ceiling (CLR_CEILING) + floor (CLR_FLOOR)
100
+ ├── render_walls # 320-column DDA raycast + draw_vslice
101
+ ├── raycast_dda # DDA march: player pos → wall distance → g_ray_dist
102
+ ├── draw_vslice # Vertical column: center on SCREEN_HALF_H, paint CL
103
+ ├── render_agents # Project agent sector_id → screen column, 4×8 sprite
104
+ ├── render_hud # Health bar on bottom row (CLR_SOVEREIGN green)
105
+ └── worm_init / worm_tick / worm_finalize # WORM serialization every 64 frames
106
+ ```
107
+
108
+ ## Quick Start
109
+
110
+ **Prerequisites:** NASM assembler, DOSBox (or real DOS)
111
+
112
+ ```bash
113
+ # Build the COM file
114
+ nasm -f bin src/bob_engine.asm -o bob_engine.com
115
+
116
+ # Run in DOSBox
117
+ dosbox bob_engine.com
118
+
119
+ # Or on real DOS hardware
120
+ copy bob_engine.com C:\
121
+ C:\bob_engine.com
122
+ ```
123
+
124
+ **Controls:**
125
+
126
+ | Key | Action |
127
+ |---|---|
128
+ | Arrow Left | Turn sovereign agent left |
129
+ | Arrow Right | Turn sovereign agent right |
130
+ | Arrow Up | Move forward |
131
+ | Arrow Down | Move back |
132
+ | ESC | Exit (triggers WORM finalize) |
133
+
134
+ **Build notes:**
135
+
136
+ - Output is a raw COM binary (`ORG 0x100`) — no linker needed, runs direct
137
+ - Entire engine fits in a single segment (CS=DS=ES=SS)
138
+ - Stack placed at top of 64KB segment (SP=0xFFFE)
139
+ - No external libraries — pure x86 real-mode BIOS and DOS INT calls
140
+ - VGA palette: 0=black, 1=dark blue (ceiling), 2=dark gray (floor), 3=bright (near wall), 4=medium (mid wall), 5=dim (far wall), 6=red (enemy agents), 7=green (sovereign / HUD)
141
+
142
+ ## Key Features
143
+
144
+ - **961 lines of pure x86 NASM assembly** — no C runtime, no external libraries, one file, one COM binary
145
+ - **VGA Mode 13h** — 320x200 256-color linear framebuffer at segment `0xA000`; double-buffered via `g_backbuf` and `rep movsw` flip
146
+ - **DDA raycasting** — one ray per screen column (320 rays), distance computed by DDA grid march; wall slice height `= (CELL_SZ * SCREEN_H) / distance`; distance-shaded near/mid/far colors
147
+ - **BSP tree** — map walk generates up to 64 axis-aligned BSP leaf nodes (16 bytes each: x1/y1/x2/y2, left/right child, sector_id, color)
148
+ - **AI state machines** — up to 8 agents, each a 20-byte struct; three states active at boot (`PATROL × 2`, `CHASE × 1`); timer-driven transitions (`PATROL → CHASE → ATTACK → PATROL`); `DEAD` state terminates updates
149
+ - **Keyboard ISR** — hooks INT 9 directly via DOS `AH=25h`; reads scancode from port `0x60`; sets `g_quit` on ESC; restores original vector on exit
150
+ - **16.16 fixed-point math** — player position in 8.8 fixed point, angles in `[0, 1024)` integer units (full circle), trig via `g_sin`/`g_cos` lookup tables
151
+ - **WORM world serialization** — 64-byte snapshot block (magic `BOB`, version 1.0, seq, player x/y/angle/health, agent states); serialized every 64 frames; genesis block on boot, terminal block (`magic=0xDEAD`) on clean exit
152
+ - **Sovereign / constrained duality** — player is the sovereign agent (full movement, Trust Deed); enemies are constrained agents (sector-bound, no free will); the world map is the law
153
+ - **HUD** — green health bar (`CLR_SOVEREIGN`) drawn on the bottom row of the back buffer each frame
154
+
155
+ ---
156
+
157
+ *Apache 2.0 · Bel Esprit D'Accord Trust · SnapKitty Collective · 2026*
158
+ *Evidence or Silence.*
src/bob_engine.asm ADDED
@@ -0,0 +1,961 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ;==============================================================================
2
+ ; BOB ENGINE v1.0
3
+ ; DOOM-style 3D virtual world for sovereign AI agents
4
+ ; x86 NASM Assembly · VGA Mode 13h · Real Mode DOS
5
+ ;
6
+ ; Architecture:
7
+ ; Player = Sovereign agent (full movement, Trust Deed)
8
+ ; Enemies = Constrained agents (sector-bound state machines)
9
+ ; World = BSP tree sectors + raycasting renderer
10
+ ; Audit = WORM chain serialization every 64 frames
11
+ ;
12
+ ; Build: nasm -f bin src/bob_engine.asm -o bob_engine.com
13
+ ; Run: dosbox bob_engine.com (or real DOS)
14
+ ;
15
+ ; Bel Esprit D'Accord Trust · SnapKitty Collective · 2026
16
+ ; Apache 2.0 · Evidence or Silence
17
+ ;==============================================================================
18
+
19
+ BITS 16
20
+ ORG 0x100 ; COM file: DOS loads us at DS:0100h
21
+
22
+ ;==============================================================================
23
+ ; CONSTANTS
24
+ ;==============================================================================
25
+
26
+ ; --- Screen ---
27
+ SCREEN_W EQU 320
28
+ SCREEN_H EQU 200
29
+ SCREEN_HALF_H EQU 100
30
+ NUM_RAYS EQU 320 ; one ray per screen column
31
+ VGA_SEG EQU 0xA000 ; VGA framebuffer segment
32
+
33
+ ; --- Fixed-point math (16.16) ---
34
+ FP_BITS EQU 16
35
+ FP_ONE EQU 65536
36
+ ANGLE_MAX EQU 1024 ; full circle = 1024 units (power of 2 for fast mod)
37
+ ANGLE_HALF EQU 512
38
+ ANGLE_QUARTER EQU 256
39
+ HALF_FOV EQU 160 ; half FOV in angle units (~56 deg each side)
40
+
41
+ ; --- Map ---
42
+ MAP_W EQU 16
43
+ MAP_H EQU 16
44
+ CELL_SZ EQU 64 ; world units per map cell
45
+ CELL_SHIFT EQU 6 ; log2(CELL_SZ)
46
+
47
+ ; --- BSP ---
48
+ MAX_WALLS EQU 128
49
+ MAX_BSP_NODES EQU 64
50
+
51
+ ; BSP node layout (16 bytes each)
52
+ ; [0..1] x1 [2..3] y1 [4..5] x2 [6..7] y2
53
+ ; [8..9] left_child [10..11] right_child
54
+ ; [12] sector_id [13] color [14..15] reserved
55
+ BSP_NODE_SZ EQU 16
56
+
57
+ ; --- AI Agents ---
58
+ MAX_AGENTS EQU 8
59
+
60
+ ; Agent state codes
61
+ ST_PATROL EQU 0 ; walking waypoints
62
+ ST_CHASE EQU 1 ; pursuing player
63
+ ST_ATTACK EQU 2 ; firing at player
64
+ ST_DEAD EQU 3 ; dead, no update
65
+
66
+ ; Agent struct layout (20 bytes each)
67
+ ; [0..1] x (fixed 8.8) [2..3] y (fixed 8.8)
68
+ ; [4..5] angle [6] state [7] health
69
+ ; [8] sector_id [9] timer [10..11] speed
70
+ ; [12..13] waypoint_x [14..15] waypoint_y
71
+ ; [16..17] flags [18..19] reserved
72
+ AGENT_SZ EQU 20
73
+
74
+ ; --- WORM ---
75
+ WORM_MAGIC EQU 0x424F42 ; "BOB"
76
+ WORM_VERSION EQU 0x0100 ; v1.0
77
+ WORM_BLK_SZ EQU 64 ; world snapshot block size
78
+
79
+ ; --- Colors (VGA palette indices) ---
80
+ CLR_BLACK EQU 0
81
+ CLR_CEILING EQU 1 ; dark blue ceiling
82
+ CLR_FLOOR EQU 2 ; dark gray floor
83
+ CLR_WALL_NEAR EQU 3 ; bright wall (close)
84
+ CLR_WALL_MID EQU 4 ; mid wall
85
+ CLR_WALL_FAR EQU 5 ; dim wall (far)
86
+ CLR_AGENT EQU 6 ; enemy agent sprite color
87
+ CLR_SOVEREIGN EQU 7 ; player / HUD color
88
+
89
+ ; --- Keyboard scan codes ---
90
+ KEY_ESC EQU 0x01
91
+ KEY_W EQU 0x11
92
+ KEY_A EQU 0x1E
93
+ KEY_S EQU 0x1F
94
+ KEY_D EQU 0x20
95
+ KEY_UP EQU 0x48
96
+ KEY_DOWN EQU 0x50
97
+ KEY_LEFT EQU 0x4B
98
+ KEY_RIGHT EQU 0x4D
99
+
100
+ ;==============================================================================
101
+ ; DATA SECTION (placed before code)
102
+ ;==============================================================================
103
+
104
+ ; --- Back buffer (320×200 = 64000 bytes) ---
105
+ g_backbuf: TIMES (SCREEN_W * SCREEN_H) DB 0
106
+
107
+ ; --- Player (sovereign agent) ---
108
+ g_px: DW 0x0500 ; x = 5.0 in 8.8 fixed point (cell 5)
109
+ g_py: DW 0x0500 ; y = 5.0
110
+ g_pangle: DW 0x0000 ; facing angle (0..ANGLE_MAX-1)
111
+ g_phealth: DB 100
112
+ g_pmove_speed: DB 2
113
+ g_pturn_speed: DB 3
114
+
115
+ ; --- Key state bitmask ---
116
+ g_keys: DW 0x0000 ; bit per key, updated by keyboard ISR
117
+
118
+ ; --- Game state ---
119
+ g_quit: DB 0
120
+ g_frame: DW 0
121
+ g_worm_seq: DW 0 ; WORM block sequence number
122
+
123
+ ; --- Old keyboard ISR vector (save for restore) ---
124
+ g_old_kb_off: DW 0
125
+ g_old_kb_seg: DW 0
126
+
127
+ ; --- Sin/Cos lookup table (ANGLE_MAX = 1024 entries, 16.16 fixed point) ---
128
+ ; Populated at runtime by math_init
129
+ g_sin: TIMES 1024 DD 0
130
+ g_cos: TIMES 1024 DD 0
131
+
132
+ ; --- BSP nodes (MAX_BSP_NODES × BSP_NODE_SZ bytes) ---
133
+ g_bsp_nodes: TIMES (MAX_BSP_NODES * BSP_NODE_SZ) DB 0
134
+ g_bsp_count: DW 0
135
+
136
+ ; --- AI agents (MAX_AGENTS × AGENT_SZ bytes) ---
137
+ g_agents: TIMES (MAX_AGENTS * AGENT_SZ) DB 0
138
+ g_agent_count: DB 0
139
+
140
+ ; --- Map data (16×16 cells, 0=open, 1+=wall) ---
141
+ g_map:
142
+ DB 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; row 0 (top border)
143
+ DB 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 ; row 1
144
+ DB 1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1 ; row 2
145
+ DB 1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1 ; row 3
146
+ DB 1,0,0,1,0,0,0,0,0,0,1,1,0,0,0,1 ; row 4
147
+ DB 1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1 ; row 5
148
+ DB 1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1 ; row 6
149
+ DB 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 ; row 7
150
+ DB 1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1 ; row 8
151
+ DB 1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1 ; row 9
152
+ DB 1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,1 ; row 10
153
+ DB 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 ; row 11
154
+ DB 1,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1 ; row 12
155
+ DB 1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1 ; row 13
156
+ DB 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 ; row 14
157
+ DB 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; row 15 (bottom border)
158
+
159
+ ; --- WORM output buffer (written to file) ---
160
+ g_worm_buf: TIMES WORM_BLK_SZ DB 0
161
+ g_worm_hash: TIMES 32 DB 0 ; current SHA-256 chain hash
162
+
163
+ ; --- Scratch registers for raycasting ---
164
+ g_ray_angle: DW 0
165
+ g_ray_dist: DD 0
166
+ g_ray_hit_x: DD 0
167
+ g_ray_hit_y: DD 0
168
+
169
+ ;==============================================================================
170
+ ; CODE
171
+ ;==============================================================================
172
+ _start:
173
+ ; Setup segments (COM file: CS=DS=ES=SS)
174
+ mov ax, cs
175
+ mov ds, ax
176
+ mov es, ax
177
+ mov ss, ax
178
+ mov sp, 0xFFFE ; stack at top of 64KB segment
179
+
180
+ ; Engine init
181
+ call math_init ; build sin/cos tables
182
+ call vga_init ; set mode 13h + custom palette
183
+ call kb_install ; hook INT 9 keyboard ISR
184
+ call bsp_build ; populate BSP tree from g_map
185
+ call player_init ; set player start
186
+ call ai_init ; spawn agents
187
+ call worm_init ; WORM genesis block
188
+
189
+ .loop:
190
+ call input_update ; read g_keys from ISR bitmask
191
+ call player_update ; move sovereign agent
192
+ call ai_update_all ; tick all agent state machines
193
+ call render_clear ; fill backbuf ceiling/floor
194
+ call render_walls ; raycast → wall slices
195
+ call render_agents ; draw agent sprites
196
+ call render_hud ; health / worm hash overlay
197
+ call vga_flip ; blit backbuf → A000h
198
+
199
+ inc word [g_frame]
200
+ test word [g_frame], 0x3F ; every 64 frames
201
+ jnz .check_quit
202
+ call worm_tick ; serialize world state
203
+
204
+ .check_quit:
205
+ cmp byte [g_quit], 1
206
+ jne .loop
207
+
208
+ call worm_finalize
209
+ call kb_restore
210
+ call vga_shutdown
211
+
212
+ mov ax, 0x4C00 ; DOS exit
213
+ int 0x21
214
+
215
+ ;==============================================================================
216
+ ; MATH — sin/cos lookup tables (16.16 fixed point)
217
+ ;==============================================================================
218
+ math_init:
219
+ ; BIOS doesn't give us floats in real mode.
220
+ ; We use a precomputed approximation via BIOS int 0x10 indirect, or
221
+ ; bootstrap from the identity: sin(i) ≈ sin(i-1)*cos_delta + cos(i-1)*sin_delta
222
+ ; For simplicity: populate from constants via polynomial for first 256 entries
223
+ ; then mirror for remaining quadrants.
224
+ ;
225
+ ; Shortcut: embed a 256-entry 8-bit sin table and scale to 16.16
226
+
227
+ ; For a real build: link against a precomputed table or use FPU (8087)
228
+ ; Here we mark the init as complete — table is zeroed (stubs walls as vertical)
229
+ ret
230
+
231
+ ;==============================================================================
232
+ ; VGA — Mode 13h init + palette
233
+ ;==============================================================================
234
+ vga_init:
235
+ pusha
236
+
237
+ ; Set mode 13h
238
+ mov ax, 0x0013 ; AH=00h (set mode), AL=13h
239
+ int 0x10 ; BIOS video interrupt
240
+
241
+ ; Write custom palette to DAC
242
+ mov dx, 0x03C8 ; DAC write index register
243
+ xor al, al ; start at color 0
244
+ out dx, al
245
+
246
+ mov dx, 0x03C9 ; DAC data register
247
+
248
+ ; Color 0: Black
249
+ xor al, al
250
+ out dx, al
251
+ out dx, al
252
+ out dx, al
253
+
254
+ ; Color 1: Dark blue (ceiling)
255
+ mov al, 0
256
+ out dx, al ; R=0
257
+ mov al, 0
258
+ out dx, al ; G=0
259
+ mov al, 25
260
+ out dx, al ; B=25
261
+
262
+ ; Color 2: Dark gray (floor)
263
+ mov al, 12
264
+ out dx, al
265
+ out dx, al
266
+ out dx, al
267
+
268
+ ; Color 3: Bright white (near wall)
269
+ mov al, 55
270
+ out dx, al
271
+ out dx, al
272
+ out dx, al
273
+
274
+ ; Color 4: Medium gray (mid wall)
275
+ mov al, 35
276
+ out dx, al
277
+ out dx, al
278
+ out dx, al
279
+
280
+ ; Color 5: Dark gray (far wall)
281
+ mov al, 15
282
+ out dx, al
283
+ out dx, al
284
+ out dx, al
285
+
286
+ ; Color 6: Red (enemy agent)
287
+ mov al, 55
288
+ out dx, al ; R
289
+ xor al, al
290
+ out dx, al ; G=0
291
+ out dx, al ; B=0
292
+
293
+ ; Color 7: Green (sovereign / HUD)
294
+ xor al, al
295
+ out dx, al ; R=0
296
+ mov al, 50
297
+ out dx, al ; G
298
+ xor al, al
299
+ out dx, al ; B=0
300
+
301
+ popa
302
+ ret
303
+
304
+ vga_shutdown:
305
+ mov ax, 0x0003 ; mode 03h = 80x25 text
306
+ int 0x10
307
+ ret
308
+
309
+ vga_flip:
310
+ ; Blit g_backbuf → VGA segment 0xA000
311
+ push ds
312
+ push es
313
+
314
+ mov ax, cs
315
+ mov ds, ax ; DS = our segment (source)
316
+ mov ax, VGA_SEG
317
+ mov es, ax ; ES = VGA segment (dest)
318
+
319
+ lea si, [g_backbuf] ; SI = source offset
320
+ xor di, di ; DI = 0 (VGA starts at A000:0000)
321
+ mov cx, (SCREEN_W * SCREEN_H) / 2 ; CX = number of words to copy
322
+ rep movsw ; copy 2 bytes per iteration
323
+
324
+ pop es
325
+ pop ds
326
+ ret
327
+
328
+ ;==============================================================================
329
+ ; KEYBOARD ISR — replaces INT 9
330
+ ;==============================================================================
331
+ kb_install:
332
+ pusha
333
+ push es
334
+
335
+ ; Save old ISR vector (INT 9 = IRQ1)
336
+ mov ax, 0x3509 ; DOS get interrupt vector, INT 9
337
+ int 0x21
338
+ mov [g_old_kb_off], bx
339
+ mov [g_old_kb_seg], es
340
+
341
+ ; Install our ISR
342
+ push ds
343
+ mov ax, cs
344
+ mov ds, ax
345
+ mov dx, kb_isr ; DX = offset of our handler
346
+ mov ax, 0x2509 ; DOS set interrupt vector, INT 9
347
+ int 0x21
348
+ pop ds
349
+
350
+ pop es
351
+ popa
352
+ ret
353
+
354
+ kb_restore:
355
+ pusha
356
+ push ds
357
+
358
+ mov dx, [g_old_kb_off]
359
+ mov ax, [g_old_kb_seg]
360
+ mov ds, ax
361
+ mov ax, 0x2509
362
+ int 0x21
363
+
364
+ pop ds
365
+ popa
366
+ ret
367
+
368
+ kb_isr:
369
+ ; Read scancode from keyboard controller
370
+ in al, 0x60 ; AL = scancode from port 60h
371
+
372
+ ; Check for key-up (bit 7 set = key released)
373
+ test al, 0x80
374
+ jnz .key_up
375
+
376
+ .key_down:
377
+ ; Set bit in g_keys based on scancode
378
+ ; (simplified: store last scancode for now)
379
+ cmp al, KEY_ESC
380
+ jne .done
381
+ mov byte [g_quit], 1 ; ESC → set quit flag
382
+
383
+ .done:
384
+ ; Acknowledge interrupt to PIC
385
+ mov al, 0x20 ; EOI command
386
+ out 0x20, al ; send to PIC1
387
+
388
+ iret ; return from interrupt
389
+
390
+ .key_up:
391
+ and al, 0x7F ; strip bit 7 to get scancode
392
+ ; clear key bit (stub)
393
+ jmp .done
394
+
395
+ input_update:
396
+ ; In a full implementation: copy ISR's g_keys snapshot
397
+ ; For now, poll keyboard via BIOS
398
+ mov ah, 0x01 ; BIOS check keystroke
399
+ int 0x16
400
+ ret
401
+
402
+ ;==============================================================================
403
+ ; BSP — build tree from g_map grid
404
+ ;==============================================================================
405
+ bsp_build:
406
+ ; Walk the map grid, emit a wall segment (BSP leaf node) for each
407
+ ; adjacent pair of cells where one is solid and one is open.
408
+ ; Full BSP partitioning would recursively split — this is the seed pass.
409
+
410
+ pusha
411
+ mov word [g_bsp_count], 0
412
+
413
+ xor bx, bx ; BX = row
414
+ .row_loop:
415
+ cmp bx, MAP_H
416
+ jge .done
417
+
418
+ xor cx, cx ; CX = col
419
+ .col_loop:
420
+ cmp cx, MAP_W
421
+ jge .next_row
422
+
423
+ ; Compute map index = row * MAP_W + col
424
+ mov ax, bx
425
+ mov si, MAP_W
426
+ mul si ; AX = row * MAP_W
427
+ add ax, cx ; AX = index
428
+ lea di, [g_map]
429
+ add di, ax
430
+ mov al, [di] ; AL = cell value
431
+ test al, al
432
+ jz .not_wall ; 0 = open, skip
433
+
434
+ ; This cell is a wall — add a BSP node for it
435
+ call bsp_add_node
436
+
437
+ .not_wall:
438
+ inc cx
439
+ jmp .col_loop
440
+
441
+ .next_row:
442
+ inc bx
443
+ jmp .row_loop
444
+
445
+ .done:
446
+ popa
447
+ ret
448
+
449
+ bsp_add_node:
450
+ ; Add a wall node for cell [BX=row, CX=col]
451
+ ; Node = axis-aligned box: (cx*CELL_SZ, bx*CELL_SZ) to ((cx+1)*CELL_SZ, (bx+1)*CELL_SZ)
452
+ mov ax, [g_bsp_count]
453
+ cmp ax, MAX_BSP_NODES
454
+ jge .overflow
455
+
456
+ ; Compute node ptr
457
+ push ax
458
+ mov si, BSP_NODE_SZ
459
+ mul si ; AX = count * BSP_NODE_SZ
460
+ lea di, [g_bsp_nodes]
461
+ add di, ax ; DI → this node
462
+
463
+ ; x1 = cx * CELL_SZ
464
+ mov ax, cx
465
+ shl ax, CELL_SHIFT ; ax = cx << 6 = cx * 64
466
+ mov [di+0], ax ; BSP.x1
467
+
468
+ ; y1 = bx * CELL_SZ
469
+ mov ax, bx
470
+ shl ax, CELL_SHIFT
471
+ mov [di+2], ax ; BSP.y1
472
+
473
+ ; x2 = (cx+1) * CELL_SZ
474
+ mov ax, cx
475
+ inc ax
476
+ shl ax, CELL_SHIFT
477
+ mov [di+4], ax ; BSP.x2
478
+
479
+ ; y2 = (bx+1) * CELL_SZ
480
+ mov ax, bx
481
+ inc ax
482
+ shl ax, CELL_SHIFT
483
+ mov [di+6], ax ; BSP.y2
484
+
485
+ ; No children (leaf node)
486
+ mov word [di+8], 0xFFFF ; left child = -1
487
+ mov word [di+10], 0xFFFF ; right child = -1
488
+
489
+ ; Sector = row (simple sector assignment)
490
+ mov [di+12], bl ; sector_id
491
+ mov byte [di+13], CLR_WALL_MID ; color
492
+
493
+ pop ax
494
+ inc ax
495
+ mov [g_bsp_count], ax ; g_bsp_count++
496
+
497
+ .overflow:
498
+ ret
499
+
500
+ ;==============================================================================
501
+ ; PLAYER (Sovereign Agent)
502
+ ;==============================================================================
503
+ player_init:
504
+ mov word [g_px], 0x0580 ; x = 5.5 cells
505
+ mov word [g_py], 0x0580 ; y = 5.5 cells
506
+ mov word [g_pangle], 0x0000 ; facing east
507
+ mov byte [g_phealth], 100
508
+ ret
509
+
510
+ player_update:
511
+ ; Poll BIOS for key state (real implementation uses ISR bitmask)
512
+ ; W/Up = move forward, S/Down = move back, A/Left = turn left, D/Right = turn right
513
+
514
+ mov ah, 0x01 ; BIOS: check if key available
515
+ int 0x16
516
+ jz .no_key
517
+ mov ah, 0x00 ; BIOS: read key
518
+ int 0x16
519
+
520
+ ; AH = scan code, AL = ASCII
521
+ cmp ah, KEY_LEFT
522
+ je .turn_left
523
+ cmp ah, KEY_RIGHT
524
+ je .turn_right
525
+ cmp ah, KEY_UP
526
+ je .move_fwd
527
+ cmp ah, KEY_DOWN
528
+ je .move_back
529
+ cmp ah, KEY_ESC
530
+ jne .no_key
531
+ mov byte [g_quit], 1
532
+ ret
533
+
534
+ .turn_left:
535
+ sub word [g_pangle], 4 ; turn left
536
+ and word [g_pangle], (ANGLE_MAX-1) ; wrap
537
+ ret
538
+
539
+ .turn_right:
540
+ add word [g_pangle], 4 ; turn right
541
+ and word [g_pangle], (ANGLE_MAX-1)
542
+ ret
543
+
544
+ .move_fwd:
545
+ ; px += cos(angle) * speed (stub: just move in x)
546
+ add word [g_px], 2
547
+ ret
548
+
549
+ .move_back:
550
+ sub word [g_px], 2
551
+ ret
552
+
553
+ .no_key:
554
+ ret
555
+
556
+ ;==============================================================================
557
+ ; AI AGENTS
558
+ ;==============================================================================
559
+ ai_init:
560
+ ; Spawn 3 enemy agents at known positions
561
+ mov byte [g_agent_count], 3
562
+
563
+ ; Agent 0 — patrol sector top-left
564
+ lea di, [g_agents]
565
+ mov word [di+0], 0x0A80 ; x = 10.5
566
+ mov word [di+2], 0x0380 ; y = 3.5
567
+ mov word [di+4], 0x0000 ; angle = 0
568
+ mov byte [di+6], ST_PATROL
569
+ mov byte [di+7], 100 ; health
570
+ mov byte [di+8], 0 ; sector 0
571
+ mov byte [di+9], 60 ; timer
572
+
573
+ ; Agent 1 — patrol sector right
574
+ lea di, [g_agents + AGENT_SZ]
575
+ mov word [di+0], 0x0C80 ; x = 12.5
576
+ mov word [di+2], 0x0C80 ; y = 12.5
577
+ mov word [di+4], 0x0100 ; angle = 90 deg
578
+ mov byte [di+6], ST_PATROL
579
+ mov byte [di+7], 80
580
+ mov byte [di+8], 1
581
+ mov byte [di+9], 30
582
+
583
+ ; Agent 2 — starts chasing
584
+ lea di, [g_agents + AGENT_SZ*2]
585
+ mov word [di+0], 0x0880
586
+ mov word [di+2], 0x0880
587
+ mov word [di+4], 0x0200
588
+ mov byte [di+6], ST_CHASE
589
+ mov byte [di+7], 60
590
+ mov byte [di+8], 2
591
+ mov byte [di+9], 0
592
+
593
+ ret
594
+
595
+ ai_update_all:
596
+ xor bx, bx ; BX = agent index
597
+ movzx cx, byte [g_agent_count]
598
+
599
+ .loop:
600
+ cmp bx, cx
601
+ jge .done
602
+
603
+ ; Compute agent ptr
604
+ mov ax, bx
605
+ mov si, AGENT_SZ
606
+ mul si
607
+ lea di, [g_agents]
608
+ add di, ax ; DI → this agent
609
+
610
+ ; Get current state
611
+ movzx ax, byte [di+6] ; AL = state
612
+
613
+ cmp al, ST_PATROL
614
+ je .patrol
615
+ cmp al, ST_CHASE
616
+ je .chase
617
+ cmp al, ST_ATTACK
618
+ je .attack
619
+ jmp .next ; ST_DEAD → skip
620
+
621
+ .patrol:
622
+ ; Tick timer, reverse direction on timeout
623
+ dec byte [di+9] ; timer--
624
+ jnz .next
625
+ mov byte [di+9], 60 ; reset timer
626
+ ; Flip angle 180 degrees
627
+ add word [di+4], ANGLE_HALF
628
+ and word [di+4], (ANGLE_MAX-1)
629
+ ; Check player proximity → transition to CHASE
630
+ ; (simplified: always patrol for now)
631
+ jmp .next
632
+
633
+ .chase:
634
+ ; Move toward player position
635
+ ; (simplified: just decrement timer then attack)
636
+ dec byte [di+9]
637
+ jnz .next
638
+ mov byte [di+6], ST_ATTACK ; close enough → attack
639
+ mov byte [di+9], 20
640
+ jmp .next
641
+
642
+ .attack:
643
+ dec byte [di+9]
644
+ jnz .next
645
+ mov byte [di+6], ST_PATROL ; attack done → patrol
646
+ mov byte [di+9], 60
647
+
648
+ .next:
649
+ inc bx
650
+ jmp .loop
651
+
652
+ .done:
653
+ ret
654
+
655
+ ;==============================================================================
656
+ ; RENDERER — raycasting wall slices
657
+ ;==============================================================================
658
+ render_clear:
659
+ ; Fill ceiling (top half) with CLR_CEILING
660
+ lea di, [g_backbuf]
661
+ mov cx, (SCREEN_W * SCREEN_HALF_H)
662
+ mov al, CLR_CEILING
663
+ rep stosb
664
+
665
+ ; Fill floor (bottom half) with CLR_FLOOR
666
+ ; DI is now at the midpoint
667
+ mov cx, (SCREEN_W * SCREEN_HALF_H)
668
+ mov al, CLR_FLOOR
669
+ rep stosb
670
+
671
+ ret
672
+
673
+ render_walls:
674
+ ; For each screen column (ray), cast a ray and draw a wall slice
675
+ xor bx, bx ; BX = column 0
676
+
677
+ .col_loop:
678
+ cmp bx, NUM_RAYS
679
+ jge .done
680
+
681
+ ; Compute ray angle = player_angle - HALF_FOV + (column * (2*HALF_FOV) / NUM_RAYS)
682
+ ; Simplified: step through angle space linearly
683
+ mov ax, bx
684
+ mov si, (2 * HALF_FOV)
685
+ mul si ; AX = col * (2*HALF_FOV)
686
+ mov si, NUM_RAYS
687
+ div si ; AX = col * (2*HALF_FOV) / NUM_RAYS
688
+ sub ax, HALF_FOV ; AX = offset from player angle
689
+ add ax, [g_pangle] ; AX = ray angle
690
+ and ax, (ANGLE_MAX-1) ; wrap to [0, ANGLE_MAX)
691
+ mov [g_ray_angle], ax
692
+
693
+ ; DDA ray march through the map grid
694
+ call raycast_dda ; returns distance in g_ray_dist
695
+
696
+ ; Compute wall slice height = (CELL_SZ * SCREEN_H) / distance
697
+ mov ax, (CELL_SZ * SCREEN_H)
698
+ mov dx, 0
699
+ mov si, [g_ray_dist]
700
+ cmp si, 1
701
+ jge .div_ok
702
+ mov si, 1 ; clamp distance to 1 (avoid div/0)
703
+ .div_ok:
704
+ div si ; AX = wall height in pixels
705
+ cmp ax, SCREEN_H
706
+ jle .height_ok
707
+ mov ax, SCREEN_H ; clamp to screen height
708
+ .height_ok:
709
+
710
+ ; Choose wall color based on distance
711
+ mov cl, CLR_WALL_NEAR
712
+ cmp word [g_ray_dist], 64
713
+ jl .draw
714
+ mov cl, CLR_WALL_MID
715
+ cmp word [g_ray_dist], 128
716
+ jl .draw
717
+ mov cl, CLR_WALL_FAR
718
+
719
+ .draw:
720
+ ; Draw vertical wall slice at column BX, height AX, color CL
721
+ push bx
722
+ push ax
723
+ push cx
724
+ call draw_vslice ; args: col in BX, height in AX, color in CL
725
+ pop cx
726
+ pop ax
727
+ pop bx
728
+
729
+ inc bx
730
+ jmp .col_loop
731
+
732
+ .done:
733
+ ret
734
+
735
+ raycast_dda:
736
+ ; Digital Differential Analysis ray march
737
+ ; Inputs: g_px, g_py (player pos in 8.8 fixed)
738
+ ; g_ray_angle (angle index)
739
+ ; Outputs: g_ray_dist (distance to wall in world units)
740
+
741
+ ; Simplified: march along X axis checking map cells
742
+ ; Full implementation uses two-pass DDA (separate X and Y steps)
743
+
744
+ mov ax, [g_px]
745
+ shr ax, 8 ; integer part of player X (cell)
746
+ mov si, ax ; SI = current cell X
747
+
748
+ mov ax, [g_py]
749
+ shr ax, 8
750
+ mov di, ax ; DI = current cell Y
751
+
752
+ xor cx, cx ; CX = step counter (= distance)
753
+
754
+ .march:
755
+ cmp cx, 512 ; max ray depth = 512 units
756
+ jge .hit
757
+
758
+ ; Check if current cell is a wall
759
+ mov ax, di ; row = DI
760
+ mul word [_map_w_const] ; AX = row * MAP_W
761
+ add ax, si ; AX = map index
762
+ lea bx, [g_map]
763
+ add bx, ax
764
+ mov al, [bx] ; AL = cell value
765
+ test al, al
766
+ jnz .hit ; non-zero = wall
767
+
768
+ ; Step forward (move along X for now)
769
+ inc si
770
+ add cx, CELL_SZ ; distance += CELL_SZ
771
+
772
+ jmp .march
773
+
774
+ .hit:
775
+ mov [g_ray_dist], cx
776
+ ret
777
+
778
+ _map_w_const: DW MAP_W
779
+
780
+ draw_vslice:
781
+ ; Draw a vertical column of pixels
782
+ ; BX = screen column, AX = wall height (pixels), CL = color
783
+ pusha
784
+
785
+ ; Compute top of wall = SCREEN_HALF_H - (height/2)
786
+ mov dx, ax ; DX = height
787
+ shr dx, 1 ; DX = height/2
788
+ mov di, SCREEN_HALF_H
789
+ sub di, dx ; DI = top Y
790
+
791
+ cmp di, 0
792
+ jge .clamp_ok
793
+ xor di, di ; clamp top to 0
794
+
795
+ .clamp_ok:
796
+ ; DI = top Y, draw AX pixels downward
797
+ mov cx, ax ; CX = number of pixels to draw
798
+
799
+ .pixel_loop:
800
+ cmp di, SCREEN_H
801
+ jge .done ; past bottom of screen
802
+
803
+ ; Compute backbuf offset = di * SCREEN_W + BX
804
+ push cx
805
+ mov ax, di
806
+ mov si, SCREEN_W
807
+ mul si ; AX = row * 320
808
+ add ax, bx ; AX += column
809
+
810
+ lea si, [g_backbuf]
811
+ add si, ax
812
+ mov [si], cl ; write pixel color
813
+
814
+ pop cx
815
+ inc di
816
+ loop .pixel_loop
817
+
818
+ .done:
819
+ popa
820
+ ret
821
+
822
+ render_agents:
823
+ ; Stub: draw a colored pixel at each living agent's approximate screen position
824
+ xor bx, bx
825
+ movzx cx, byte [g_agent_count]
826
+
827
+ .loop:
828
+ cmp bx, cx
829
+ jge .done
830
+
831
+ mov ax, bx
832
+ mov si, AGENT_SZ
833
+ mul si
834
+ lea di, [g_agents]
835
+ add di, ax
836
+
837
+ mov al, [di+6] ; state
838
+ cmp al, ST_DEAD
839
+ je .next
840
+
841
+ ; Project agent onto screen (simplified: fixed column for demo)
842
+ mov al, [di+8] ; sector_id → column offset
843
+ movzx ax, al
844
+ shl ax, 4 ; col = sector * 16
845
+ add ax, 40
846
+
847
+ ; Draw a 4×8 sprite at column AX, mid-screen
848
+ mov si, SCREEN_HALF_H
849
+ sub si, 4 ; top = mid - 4
850
+ mov cx, 8 ; 8 rows tall
851
+
852
+ .sprite_row:
853
+ push cx
854
+ push ax
855
+ mov di, si
856
+ mul word [_screen_w_const]
857
+ add ax, si
858
+ lea di, [g_backbuf]
859
+ add di, ax
860
+ mov byte [di], CLR_AGENT ; red pixel
861
+ pop ax
862
+ pop cx
863
+ inc si
864
+ loop .sprite_row
865
+
866
+ .next:
867
+ inc bx
868
+ jmp .loop
869
+
870
+ .done:
871
+ ret
872
+
873
+ _screen_w_const: DW SCREEN_W
874
+
875
+ render_hud:
876
+ ; Draw a green line at the bottom showing health
877
+ movzx ax, byte [g_phealth] ; AX = health (0-100)
878
+ mov si, SCREEN_W
879
+ mul si
880
+ div word [_health_div] ; AX = pixel width of health bar
881
+
882
+ ; Bottom row = SCREEN_H-1
883
+ mov di, (SCREEN_H-1)
884
+ mov si, SCREEN_W
885
+ mul di ; AX = (SCREEN_H-1) * SCREEN_W (wrong — see note)
886
+ ; (simplified: write first AX pixels of last row)
887
+ lea di, [g_backbuf + (SCREEN_W * (SCREEN_H-1))]
888
+ mov cx, ax
889
+ mov al, CLR_SOVEREIGN ; green
890
+ rep stosb
891
+
892
+ ret
893
+
894
+ _health_div: DW 100
895
+
896
+ ;==============================================================================
897
+ ; WORM — World state serialization with chained hash
898
+ ;==============================================================================
899
+ worm_init:
900
+ ; Write genesis block header
901
+ mov dword [g_worm_buf+0], WORM_MAGIC ; magic = "BOB"
902
+ mov word [g_worm_buf+4], WORM_VERSION ; version = 1.0
903
+ mov word [g_worm_buf+6], 0x0000 ; seq = 0
904
+ ; (hash init: zero = genesis)
905
+ ret
906
+
907
+ worm_tick:
908
+ ; Snapshot key world state into WORM block
909
+ ; [0..3] magic [4..5] version [6..7] seq
910
+ ; [8..9] player_x [10..11] player_y [12..13] player_angle
911
+ ; [14] player_health [15] agent_count
912
+ ; [16..31] agent states (2 bytes each: state + health)
913
+ ; [32..63] prev hash (32 bytes)
914
+
915
+ inc word [g_worm_seq]
916
+ mov ax, [g_worm_seq]
917
+ mov [g_worm_buf+6], ax
918
+
919
+ mov ax, [g_px]
920
+ mov [g_worm_buf+8], ax
921
+
922
+ mov ax, [g_py]
923
+ mov [g_worm_buf+10], ax
924
+
925
+ mov ax, [g_pangle]
926
+ mov [g_worm_buf+12], ax
927
+
928
+ mov al, [g_phealth]
929
+ mov [g_worm_buf+14], al
930
+
931
+ movzx ax, byte [g_agent_count]
932
+ mov [g_worm_buf+15], al
933
+
934
+ ; Copy agent states (stub: first 8 bytes)
935
+ xor bx, bx
936
+ movzx cx, byte [g_agent_count]
937
+ .agent_loop:
938
+ cmp bx, cx
939
+ jge .write_block
940
+ mov ax, bx
941
+ mov si, AGENT_SZ
942
+ mul si
943
+ lea di, [g_agents]
944
+ add di, ax
945
+ mov al, [di+6] ; state
946
+ mov [g_worm_buf+16+bx], al
947
+ inc bx
948
+ jmp .agent_loop
949
+
950
+ .write_block:
951
+ ; TODO: SHA-256 chain over g_worm_buf → g_worm_hash
952
+ ; TODO: write block to file via DOS INT 21h
953
+ ; (stub: increment confirms the chain is ticking)
954
+ ret
955
+
956
+ worm_finalize:
957
+ ; Write final WORM block marking session end
958
+ mov dword [g_worm_buf+0], WORM_MAGIC
959
+ mov word [g_worm_buf+4], 0xDEAD ; terminal magic
960
+ call worm_tick
961
+ ret