termfront 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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +41 -0
  3. data/LICENSE +21 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +160 -0
  6. data/Rakefile +12 -0
  7. data/data/audio/THIRD_PARTY_NOTICES.md +45 -0
  8. data/data/audio/beep_02.ogg +0 -0
  9. data/data/audio/button1.ogg +0 -0
  10. data/data/audio/complete.ogg +0 -0
  11. data/data/audio/manifest.json +17 -0
  12. data/data/audio/mission_bgm.wav +0 -0
  13. data/data/audio/mission_clear_se.wav +0 -0
  14. data/data/audio/on.ogg +0 -0
  15. data/data/audio/page_se.wav +0 -0
  16. data/data/audio/sector.mp3 +0 -0
  17. data/data/audio/sfx_22b.ogg +0 -0
  18. data/data/audio/shield_alarm_se.wav +0 -0
  19. data/data/audio/shield_regen_se.wav +0 -0
  20. data/data/audio/shoot_01.ogg +0 -0
  21. data/data/audio/terminal_se.wav +0 -0
  22. data/data/audio/title.mp3 +0 -0
  23. data/data/audio/title_bgm.wav +0 -0
  24. data/data/audio/victory.mp3 +0 -0
  25. data/data/events/corridor_sweep.json +27 -0
  26. data/data/events/final_push.json +40 -0
  27. data/data/events/stronghold.json +27 -0
  28. data/data/events/the_gauntlet.json +27 -0
  29. data/data/events/training_grounds.json +31 -0
  30. data/exe/termfront +6 -0
  31. data/exe/termfront-server +7 -0
  32. data/lib/termfront/audio_manager.rb +225 -0
  33. data/lib/termfront/config.rb +38 -0
  34. data/lib/termfront/demo_player.rb +181 -0
  35. data/lib/termfront/drop_item/base.rb +26 -0
  36. data/lib/termfront/drop_item/weapon.rb +38 -0
  37. data/lib/termfront/enemy/base.rb +133 -0
  38. data/lib/termfront/enemy/crawler.rb +18 -0
  39. data/lib/termfront/enemy/executor.rb +18 -0
  40. data/lib/termfront/game.rb +637 -0
  41. data/lib/termfront/input.rb +75 -0
  42. data/lib/termfront/map.rb +72 -0
  43. data/lib/termfront/mission/base.rb +81 -0
  44. data/lib/termfront/mission/corridor_sweep.rb +41 -0
  45. data/lib/termfront/mission/event_loader.rb +87 -0
  46. data/lib/termfront/mission/event_runtime.rb +37 -0
  47. data/lib/termfront/mission/final_push.rb +44 -0
  48. data/lib/termfront/mission/stronghold.rb +45 -0
  49. data/lib/termfront/mission/the_gauntlet.rb +38 -0
  50. data/lib/termfront/mission/training.rb +38 -0
  51. data/lib/termfront/mission/training_grounds.rb +37 -0
  52. data/lib/termfront/network/client.rb +865 -0
  53. data/lib/termfront/network/connection.rb +101 -0
  54. data/lib/termfront/network/server.rb +620 -0
  55. data/lib/termfront/network/wavesfight_client.rb +364 -0
  56. data/lib/termfront/opponent.rb +24 -0
  57. data/lib/termfront/player.rb +147 -0
  58. data/lib/termfront/projectile.rb +44 -0
  59. data/lib/termfront/remote_enemy.rb +21 -0
  60. data/lib/termfront/renderer.rb +707 -0
  61. data/lib/termfront/scene_player.rb +164 -0
  62. data/lib/termfront/sprite.rb +73 -0
  63. data/lib/termfront/terminal_output.rb +63 -0
  64. data/lib/termfront/title_screen.rb +299 -0
  65. data/lib/termfront/version.rb +5 -0
  66. data/lib/termfront/weapon/assault_rifle.rb +15 -0
  67. data/lib/termfront/weapon/base.rb +44 -0
  68. data/lib/termfront/weapon/pistol.rb +15 -0
  69. data/lib/termfront/weapon/shock_pistol.rb +15 -0
  70. data/lib/termfront/weapon/shock_rifle.rb +15 -0
  71. data/lib/termfront.rb +51 -0
  72. data/sig/termfront.rbs +4 -0
  73. metadata +119 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 97daf7d1b118259e9d1bf5913b4f24ea301c5b3bea048c5b45b37f3df164a61c
4
+ data.tar.gz: 0d4911cd9755884f61dc9d8ee5400655d654dd55e710784997c974414626d5ce
5
+ SHA512:
6
+ metadata.gz: 7a19c3fa19333be6ddb23f63d2d944e70ea2f5128a0461822283ac3853046a82179aed19cdd5c8e46d36750afd1fddea6eb12f50c9be4d055077f3fe6f7d892d
7
+ data.tar.gz: ad42e1e6b604b473c5124b526e9510e0c33cfc01fdf44609822ec12784f4a7b24165d2dcaa95e488393163a7a80d84478f40293fc0b8689951be5eb99a267fb7
data/CHANGELOG.md ADDED
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on Keep a Changelog, and this project follows Semantic Versioning as it stabilizes.
6
+
7
+ ## [0.1.0] - 2026-05-20
8
+
9
+ Initial public gem release candidate.
10
+
11
+ ### Added
12
+
13
+ - Terminal FPS core with DDA raycasting renderer
14
+ - Singleplayer mission mode
15
+ - Campaign mode with multiple missions
16
+ - JSON-driven campaign event system
17
+ - Story scenes with dialogue and title cards
18
+ - In-mission terminal interactions tied to campaign events
19
+ - Experimental PvP mode over TCP + TLS
20
+ - PvP matchmaking queues for `1v1`, `2v2`, and `4v4`
21
+ - Wavesfight PvE mode with stage selection, wave-based survival, and optional 2-player co-op
22
+ - External audio manifest and runtime audio playback
23
+ - Third-party audio notices for bundled CC0 assets
24
+ - Looping shield regeneration audio channel separate from BGM
25
+
26
+ ### Changed
27
+
28
+ - Improved title/campaign transitions by fixing incomplete terminal writes
29
+ - Added terminal markers to the radar and interaction HUD
30
+ - Reworked campaign story content and terminal logs
31
+ - Replaced placeholder audio mappings with selected CC0 assets for BGM and core SE
32
+ - Reworked PvP client/server flow for team-based matches with ally/enemy sync, elimination handling, and team win detection
33
+ - Updated PvP spawn placement to use walkable map positions and improved diagonal team separation
34
+ - Updated PvP match-size selection to support arrow keys in addition to `J`/`K`
35
+ - Disabled quitting PvP matches with `Q` to avoid accidental exits during combat
36
+ - Added Wavesfight arena registration for `Corridor Sweep`, `Stronghold`, and `Final Push`
37
+
38
+ ### Notes
39
+
40
+ - PvP is still considered experimental and needs broader WAN verification
41
+ - Audio playback depends on an available local player such as `ffplay`, `afplay`, `paplay`, or `aplay`
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 S.H.
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.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 S-H-GAMELINKS
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # Termfront
2
+
3
+ `Termfront` is a terminal FPS built in Ruby with a raycasting renderer, campaign scripting, audio support, and experimental PvP multiplayer.
4
+
5
+ ## Features
6
+
7
+ - DDA raycasting renderer for ANSI terminals
8
+ - Singleplayer mission mode
9
+ - Wavesfight PvE mode with wave-based survival arenas
10
+ - Campaign mode with intro/outro scenes and in-mission terminals
11
+ - Experimental PvP over TCP + TLS
12
+ - Radar, pickups, weapons, shields, and enemy projectiles
13
+ - External audio manifest with BGM, SE, and looped shield regeneration audio
14
+
15
+ ## Requirements
16
+
17
+ - Ruby `>= 3.2`
18
+ - A terminal with ANSI escape sequence support
19
+ - One of these audio players if you want sound:
20
+ - `ffplay`
21
+ - `afplay`
22
+ - `paplay`
23
+ - `aplay`
24
+
25
+ RubyGems metadata currently targets Ruby `0.1.0`.
26
+
27
+ ## Installation
28
+
29
+ If the gem is published:
30
+
31
+ ```bash
32
+ gem install termfront
33
+ ```
34
+
35
+ For local development from this repository:
36
+
37
+ ```bash
38
+ bundle install
39
+ bundle exec rake test
40
+ bundle exec rake install
41
+ ```
42
+
43
+ ## Running
44
+
45
+ Start the game:
46
+
47
+ ```bash
48
+ termfront
49
+ ```
50
+
51
+ Start a PvP server:
52
+
53
+ ```bash
54
+ termfront-server
55
+ ```
56
+
57
+ Use a custom port:
58
+
59
+ ```bash
60
+ termfront-server 9000
61
+ ```
62
+
63
+ Default PvP port is `7777`.
64
+
65
+ ## Controls
66
+
67
+ - `W` `A` `S` `D`: move
68
+ - `Left` / `Right`: turn
69
+ - `Space`: fire
70
+ - `T`: swap weapon
71
+ - `E`: interact / pick up / use terminal
72
+ - `Q` or `Esc`: quit or back out
73
+ - `Enter`: confirm menus
74
+
75
+ In story scenes:
76
+
77
+ - `Enter` / `Space`: next page
78
+ - `Esc` / `Q`: skip scene
79
+
80
+ ## Modes
81
+
82
+ ### Singleplayer
83
+
84
+ Quick mission start for testing the core combat loop.
85
+
86
+ ### Campaign
87
+
88
+ Campaign missions include:
89
+
90
+ - mission start scenes
91
+ - mission complete scenes
92
+ - optional in-mission terminal logs
93
+
94
+ Story/event data lives in `data/events/*.json`.
95
+
96
+ ### Wavesfight
97
+
98
+ Wavesfight is a PvE survival mode built on selected campaign maps.
99
+
100
+ - Select from `Corridor Sweep`, `Stronghold`, and `Final Push`
101
+ - Survive escalating enemy waves
102
+ - Difficulty ramps up every few waves
103
+ - Shield, health, and ammo get a partial refresh between waves
104
+ - Supports both solo play and 2-player online co-op
105
+
106
+ ### PvP
107
+
108
+ PvP is currently marked experimental.
109
+
110
+ - The server listens on TCP and wraps traffic with TLS.
111
+ - The client connects directly to `host:port`.
112
+ - Matchmaking now supports `1v1`, `2v2`, and `4v4`.
113
+ - Players choose the match size on the client, and the server keeps separate queues for each mode.
114
+ - Team matches end when one side is fully eliminated.
115
+ - Local relay/start/state/ping behavior is covered by tests.
116
+ - Internet/WAN play still needs broader real-world verification.
117
+
118
+ For internet testing, the simplest setups are:
119
+
120
+ - direct TCP port forwarding to the host running `termfront-server`
121
+ - `nginx stream` TCP passthrough on a VPS
122
+ - a mesh/VPN overlay such as Tailscale
123
+
124
+ ## Audio
125
+
126
+ Audio mappings are defined in:
127
+
128
+ - [data/audio/manifest.json](data/audio/manifest.json)
129
+
130
+ Third-party audio notices are tracked here:
131
+
132
+ - [data/audio/THIRD_PARTY_NOTICES.md](data/audio/THIRD_PARTY_NOTICES.md)
133
+
134
+ If no supported audio player is available, the game still runs without sound.
135
+
136
+ ## Development
137
+
138
+ Run tests:
139
+
140
+ ```bash
141
+ ruby -Itest test/test_termfront.rb
142
+ ```
143
+
144
+ Build the gem:
145
+
146
+ ```bash
147
+ gem build termfront.gemspec
148
+ ```
149
+
150
+ Install the built gem locally:
151
+
152
+ ```bash
153
+ gem install ./termfront-0.1.0.gem
154
+ ```
155
+
156
+ ## License
157
+
158
+ Code is available under the [MIT License](LICENSE.txt).
159
+
160
+ Third-party audio assets remain under their own licenses as documented in [data/audio/THIRD_PARTY_NOTICES.md](data/audio/THIRD_PARTY_NOTICES.md).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
@@ -0,0 +1,45 @@
1
+ # Third-Party Audio Notices
2
+
3
+ ## In Use
4
+
5
+ ### Dark Sci-Fi Audio Pack
6
+
7
+ - Author: SRG774
8
+ - License: CC0 1.0 Universal
9
+ - Source: https://opengameart.org/content/dark-sci-fi-audio-pack
10
+ - Used files:
11
+ - `title.mp3` -> `title_bgm`
12
+ - `sector.mp3` -> `mission_bgm`
13
+ - `victory.mp3` -> `shield_regen` loop
14
+
15
+ ### 50 CC0 Sci-Fi SFX
16
+
17
+ - Author: rubberduck
18
+ - License: CC0 1.0 Universal
19
+ - Source: https://opengameart.org/content/50-cc0-sci-fi-sfx
20
+ - Used files:
21
+ - `shoot_01.ogg` -> `shoot`
22
+ - `beep_02.ogg` -> `shield_alarm`
23
+
24
+ ### 60 CC0 Sci-Fi SFX
25
+
26
+ - Author: rubberduck
27
+ - License: CC0 1.0 Universal
28
+ - Source: https://opengameart.org/content/60-cc0-sci-fi-sfx
29
+ - Used files:
30
+ - `sfx_22b.ogg` -> `damage`
31
+
32
+ ### UI Sounds
33
+
34
+ - Author: StumpyStrust
35
+ - License: CC0 1.0 Universal
36
+ - Source: https://opengameart.org/content/ui-sounds
37
+ - Used files:
38
+ - `button1.ogg` -> `terminal`
39
+ - `on.ogg` -> `page`
40
+ - `complete.ogg` -> `mission_clear`
41
+
42
+ ## Notes
43
+
44
+ - Because these assets are CC0, they can be redistributed with the gem without changing the gem's own code license.
45
+ - Even though attribution is not required for CC0 assets, this file keeps provenance clear for future maintenance.
Binary file
Binary file
Binary file
@@ -0,0 +1,17 @@
1
+ {
2
+ "bgm": {
3
+ "title": "data/audio/title.mp3",
4
+ "mission": "data/audio/sector.mp3"
5
+ },
6
+ "se": {
7
+ "shoot": "data/audio/shoot_01.ogg",
8
+ "damage": "data/audio/sfx_22b.ogg",
9
+ "terminal": "data/audio/button1.ogg",
10
+ "mission_clear": "data/audio/complete.ogg",
11
+ "page": "data/audio/on.ogg",
12
+ "shield_alarm": "data/audio/beep_02.ogg"
13
+ },
14
+ "loop_se": {
15
+ "shield_regen": "data/audio/victory.mp3"
16
+ }
17
+ }
Binary file
Binary file
data/data/audio/on.ogg ADDED
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,27 @@
1
+ {
2
+ "events": [
3
+ {
4
+ "id": "corridor_sweep_intro",
5
+ "trigger": { "type": "mission_start" },
6
+ "actions": [
7
+ { "type": "dialogue", "speaker": "OPS", "text": "Facility A-13 went dark six hours ago. This corridor is your first live sector." },
8
+ { "type": "dialogue", "speaker": "OPS", "text": "Sweep the access corridor and recover any surviving logs before local systems collapse." }
9
+ ]
10
+ },
11
+ {
12
+ "id": "corridor_terminal_a",
13
+ "trigger": { "type": "terminal_used", "terminal_id": "ops_corridor_a" },
14
+ "actions": [
15
+ { "type": "text", "text": "OPS LOG 04\nPower loss started in the maintenance wing, then moved against cable direction.\nSecurity sealed Sector C after staff reported chanting inside locked rooms." }
16
+ ]
17
+ },
18
+ {
19
+ "id": "corridor_sweep_outro",
20
+ "trigger": { "type": "mission_complete" },
21
+ "actions": [
22
+ { "type": "dialogue", "speaker": "OPS", "text": "Corridor secure. The blackout did not stop the transmissions." },
23
+ { "type": "dialogue", "speaker": "OPS", "text": "Advance to the service run. If the logs are right, the facility was sealing something in, not keeping us out." }
24
+ ]
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "events": [
3
+ {
4
+ "id": "final_push_intro",
5
+ "trigger": { "type": "mission_start" },
6
+ "actions": [
7
+ { "type": "dialogue", "speaker": "OPS", "text": "This is the last gate. Expect fortress-grade resistance and no clean answers." },
8
+ { "type": "dialogue", "speaker": "OPS", "text": "If the archive core is intact, bring back whatever it stored and who authorized the breach." }
9
+ ]
10
+ },
11
+ {
12
+ "id": "final_push_terminal_archive",
13
+ "trigger": { "type": "terminal_used", "terminal_id": "archive_core" },
14
+ "actions": [
15
+ { "type": "text", "text": "ARCHIVE CORE\nDirective mismatch detected.\nContainment protocol revoked by archive authority: ORPHEUS.\nLocal command objections logged, denied, and sealed." }
16
+ ]
17
+ },
18
+ {
19
+ "id": "final_push_outro",
20
+ "trigger": { "type": "mission_complete" },
21
+ "actions": [
22
+ {
23
+ "type": "demo",
24
+ "duration": 3.0,
25
+ "caption": "ARCHIVE VAULT / SIGNAL STILL PRESENT",
26
+ "path": [
27
+ { "x": 20.5, "y": 9.5, "angle": 3.14, "t": 0.0 },
28
+ { "x": 16.5, "y": 9.5, "angle": 3.05, "t": 1.0 },
29
+ { "x": 11.5, "y": 9.5, "angle": 2.95, "t": 2.0 },
30
+ { "x": 7.5, "y": 8.5, "angle": 2.75, "t": 3.0 }
31
+ ]
32
+ },
33
+ { "type": "title_card", "text": "FORTRESS CLEARED\nSIGNAL SOURCE UNRESOLVED" },
34
+ { "type": "dialogue", "speaker": "OPS", "text": "Fortress cleared. The signal is still active somewhere below you." },
35
+ { "type": "dialogue", "speaker": "OPS", "text": "This facility did not fail containment. Someone revoked it." },
36
+ { "type": "dialogue", "speaker": "OPS", "text": "Archive authority ORPHEUS is still broadcasting. This sector is over, but the incident is not." }
37
+ ]
38
+ }
39
+ ]
40
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "events": [
3
+ {
4
+ "id": "stronghold_intro",
5
+ "trigger": { "type": "mission_start" },
6
+ "actions": [
7
+ { "type": "dialogue", "speaker": "OPS", "text": "Inner rooms are fortified. Executors are holding the choke points on purpose." },
8
+ { "type": "dialogue", "speaker": "OPS", "text": "Break the stronghold and find the source of their command signal before it reroutes again." }
9
+ ]
10
+ },
11
+ {
12
+ "id": "stronghold_terminal_guard",
13
+ "trigger": { "type": "terminal_used", "terminal_id": "guard_post" },
14
+ "actions": [
15
+ { "type": "text", "text": "SECURITY POST\nStaff rotation suspended by archive authority, not site command.\nAny report of ritual language is to be escalated and removed from local logs before outside review." }
16
+ ]
17
+ },
18
+ {
19
+ "id": "stronghold_outro",
20
+ "trigger": { "type": "mission_complete" },
21
+ "actions": [
22
+ { "type": "dialogue", "speaker": "OPS", "text": "Stronghold is down. Something in the facility wanted time, not victory." },
23
+ { "type": "dialogue", "speaker": "OPS", "text": "Archive authority overrode local containment. Push the final gate and confirm what they were trying to keep active." }
24
+ ]
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "events": [
3
+ {
4
+ "id": "the_gauntlet_intro",
5
+ "trigger": { "type": "mission_start" },
6
+ "actions": [
7
+ { "type": "dialogue", "speaker": "OPS", "text": "The service corridor ahead is a kill lane, but its layout is wrong for defense." },
8
+ { "type": "dialogue", "speaker": "OPS", "text": "Push through fast. If a terminal still answers, check why the shutters were chained inward." }
9
+ ]
10
+ },
11
+ {
12
+ "id": "the_gauntlet_terminal_service",
13
+ "trigger": { "type": "terminal_used", "terminal_id": "service_hub" },
14
+ "actions": [
15
+ { "type": "text", "text": "SERVICE HUB\nEmergency shutters failed in sequence from the archive end of the corridor.\nWitnesses reported synchronized movement before first contact, as if something was herding them away from the vault." }
16
+ ]
17
+ },
18
+ {
19
+ "id": "the_gauntlet_outro",
20
+ "trigger": { "type": "mission_complete" },
21
+ "actions": [
22
+ { "type": "dialogue", "speaker": "OPS", "text": "You made it through. The path deeper into the complex is open." },
23
+ { "type": "dialogue", "speaker": "OPS", "text": "These corridors were built to funnel breaches back toward the core. Move on the stronghold and find who broke the seal." }
24
+ ]
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "events": [
3
+ {
4
+ "id": "training_grounds_intro",
5
+ "trigger": { "type": "mission_start" },
6
+ "actions": [
7
+ { "type": "title_card", "text": "OPERATION: TRAINING GROUNDS\nSIMULATION WING / SECTOR A-13" },
8
+ { "type": "dialogue", "speaker": "OPS", "text": "This is a training sweep on paper. Treat it like deployment anyway." },
9
+ { "type": "dialogue", "speaker": "OPS", "text": "Sector A-13 lost contact beyond this wing. We are not waiting for a second failure." },
10
+ { "type": "dialogue", "speaker": "OPS", "text": "Use the yard terminals. They still carry the old incident briefings." }
11
+ ]
12
+ },
13
+ {
14
+ "id": "training_grounds_terminal_intro",
15
+ "trigger": { "type": "terminal_used", "terminal_id": "training_console" },
16
+ "actions": [
17
+ { "type": "text", "text": "TRAINING CONSOLE\nIncident drill package: Sector A-13 containment loss.\nScenario note: field teams are not to be told whether the exercise has gone live." }
18
+ ]
19
+ },
20
+ {
21
+ "id": "training_grounds_outro",
22
+ "trigger": { "type": "mission_complete" },
23
+ "actions": [
24
+ { "type": "title_card", "text": "SECTOR SECURE\nTRAINING SWEEP COMPLETE" },
25
+ { "type": "dialogue", "speaker": "OPS", "text": "Training sweep complete. Your live assignment starts now." },
26
+ { "type": "dialogue", "speaker": "OPS", "text": "The blackout in the next sector is real. This was the last clean room." },
27
+ { "type": "dialogue", "speaker": "OPS", "text": "Move to the access corridor. Recover logs before the sector goes completely dark." }
28
+ ]
29
+ }
30
+ ]
31
+ }
data/exe/termfront ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "termfront"
5
+
6
+ Termfront.start
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "termfront"
5
+
6
+ port = (ARGV[0] || Termfront::Config::PVP_PORT).to_i
7
+ Termfront::Network::Server.new(port: port).run