Hydra-Game 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.
@@ -0,0 +1,308 @@
1
+ from __future__ import annotations
2
+ import json
3
+ import socket
4
+ import shutil
5
+ import sys
6
+ import termios
7
+ import threading
8
+ import tty
9
+ from dataclasses import dataclass, field
10
+ from select import select
11
+ from typing import Any, Dict, List, Optional, Protocol, Tuple, Union
12
+
13
+ from .Plexus import GemName, Intent
14
+ from .Pulse import CursorLeft, Flicker1, Flicker2, Flicker3, Flicker4, Green, HideCursor, PadLine, ReadKey, Reset, ShowCursor, Step, Teal
15
+
16
+ class Heart(Protocol):
17
+ head: str
18
+ heads: List[str]
19
+ tail: Optional[Dict[str, Any]]
20
+ state: Any
21
+
22
+ def Snapshot(self) -> Dict[str, Any]: ...
23
+ def Emotions(self) -> Dict[str, Any]: ...
24
+ def Ingest(self, tailin: Dict[str, Any]) -> List[Intent]: ...
25
+ def Propose(self, tohead: str, amount: int) -> Dict[str, Any]: ...
26
+ def DreamState(self) -> Dict[str, Any]: ...
27
+
28
+ PrintLock = threading.Lock()
29
+ SeenMax = 4096
30
+ Command = Union[str, Tuple[str, str, int]]
31
+ WelcomeLines = [
32
+ "",
33
+ "",
34
+ f" {Teal}It's feeding time{Green}...{Reset}",
35
+ "",
36
+ f" {Teal}Go for it, let another set of jaws chomp on your tallies{Reset}",
37
+ f" {Teal}and give of yourself freely{Green}. {Teal}Nothing here is lost{Green}.{Reset}",
38
+ f" {Teal}If another takes too much, draw it back through the ichor{Green}.{Reset}",
39
+ f" {Teal}Hydra feels no pain{Green}. {Teal}It has no memory{Green}...{Reset}",
40
+ "",
41
+ f" {Teal}Use {Green}←{Teal} and {Green}→{Teal} to select a head, then {Green}↑{Teal} and {Green}↓{Teal} for an amount{Green}.{Reset}",
42
+ f" {Green}Enter{Teal}({Green}Feed!{Teal}) {Green}Ctrl{Teal}+{Green}X{Teal}({Green}Sever{Teal}) {Green}Ctrl{Teal}+{Green}C{Teal}({Green}Cauterize{Teal}){Reset}",
43
+ "",
44
+ f" {Teal}Heads that become {Green}Envious{Teal} must be {Green}Severed{Teal} and Rehydrated{Green}.{Reset}",
45
+ "",
46
+ "",
47
+ ]
48
+
49
+ class ExitSignal(Exception):
50
+ pass
51
+
52
+
53
+ def FirstTarget(head: str, heads: List[str]) -> str:
54
+ for item in heads:
55
+ if item != head:
56
+ return item
57
+ return heads[0]
58
+
59
+ def NextTarget(head: str, heads: List[str], targethead: str, direction: int) -> str:
60
+ if not heads:
61
+ return targethead
62
+ index = heads.index(targethead)
63
+ for _ in range(len(heads)):
64
+ index = (index + direction) % len(heads)
65
+ candidate = heads[index]
66
+ if candidate != head or len(heads) == 1:
67
+ return candidate
68
+ return targethead
69
+
70
+ @dataclass
71
+ class Body:
72
+ head: str
73
+ heads: List[str]
74
+ sock: socket.socket
75
+ peers: List[Tuple[str, int]]
76
+ heart: Heart
77
+ lock: threading.Lock
78
+ targethead: str = ""
79
+ amount: int = 1
80
+ seen: Dict[str, None] = field(default_factory=dict)
81
+
82
+ def Crown(self) -> int:
83
+ return int(self.heart.state.crown)
84
+
85
+ def EnsureTarget(self) -> None:
86
+ if not self.targethead or self.targethead not in self.heads:
87
+ self.targethead = FirstTarget(self.head, self.heads)
88
+
89
+ def MoveTarget(self, direction: int) -> None:
90
+ self.EnsureTarget()
91
+ self.targethead = NextTarget(self.head, self.heads, self.targethead, direction)
92
+
93
+ def TailItems(self, tallies: Dict[str, Any]) -> str:
94
+ return f"{Reset}{Green}:{Reset}".join(
95
+ f"{Teal}{item}{Reset}{(Flicker3() if index % 2 == 0 else Flicker4())}{tallies.get(item, 'x')}{Reset}"
96
+ for index, item in enumerate(self.heads)
97
+ )
98
+
99
+ def HudLine(self, crown: int, tallies: Dict[str, Any], envy: bool) -> Tuple[str, int]:
100
+ headchunk = f"{Flicker2()}Head{Reset}{Green}:{Reset}{Flicker2()}{self.head}{Reset}" if envy else f"{Teal}Head{Green}:{Reset}{Teal}{self.head}{Reset}"
101
+ visible = f".::Head:{self.head}:::Crown:{GemName(crown)}:::{self.targethead}:{self.amount}:::Tails:" + ".".join(f"{item}{tallies.get(item, 'x')}" for item in self.heads) + "::."
102
+ line = (
103
+ f" {Green}.::{Reset}{headchunk}{Green}:::{Reset}"
104
+ f"{Teal}Crown{Green}:{Reset}{Flicker1()}{GemName(crown)}{Reset}{Green}:::{Reset}"
105
+ f"{Flicker3()}{self.targethead}{Reset}{Green}:{Reset}{Flicker4()}{self.amount}{Reset}{Green}:::{Reset}"
106
+ f"{Teal}Tails{Green}:{Reset}{Flicker3()}{self.TailItems(tallies)}{Reset}{Green}::.{Reset}"
107
+ )
108
+ return line, len(visible)
109
+
110
+ def Paint(self, lines: List[str], cursorleft: int) -> None:
111
+ with PrintLock:
112
+ width, height = shutil.get_terminal_size(fallback=(80, 24))
113
+ built = [PadLine(line, max(1, width)) for line in lines]
114
+ if len(built) < height:
115
+ built.extend([" " * max(1, width)] * (height - len(built)))
116
+ sys.stdout.write("\x1b[H")
117
+ sys.stdout.write("\n".join(built[:height]))
118
+ sys.stdout.write(CursorLeft(cursorleft))
119
+ sys.stdout.flush()
120
+
121
+ def RenderStatus(self) -> None:
122
+ self.EnsureTarget()
123
+ snap = self.heart.Snapshot()
124
+ crown = int(snap.get("crown", 1) or 1)
125
+ tallies = dict(snap.get("tallies", {}) or {})
126
+ envy = bool(self.heart.Emotions().get("envy", False))
127
+ hudline, cursorleft = self.HudLine(crown, tallies, envy)
128
+ self.Paint(WelcomeLines + [hudline], cursorleft)
129
+
130
+ def SendMessage(self, message: Dict[str, Any], dstaddr: Optional[Tuple[str, int]] = None, skipaddr: Optional[Tuple[str, int]] = None) -> None:
131
+ payload = json.dumps(message, separators=(",", ":")).encode("utf-8")
132
+ if dstaddr is not None:
133
+ try:
134
+ self.sock.sendto(payload, dstaddr)
135
+ except Exception:
136
+ pass
137
+ return
138
+ for host, port in self.peers:
139
+ if skipaddr is not None and (host, port) == skipaddr:
140
+ continue
141
+ try:
142
+ self.sock.sendto(payload, (host, port))
143
+ except Exception:
144
+ pass
145
+
146
+ def SendTail(self, tail: Dict[str, Any], srcaddr: Optional[Tuple[str, int]] = None) -> None:
147
+ self.SendMessage(tail, skipaddr=srcaddr)
148
+
149
+ def SendHunger(self, crown: int, needtail: bool = True) -> None:
150
+ self.SendMessage({"type": "HUNGER", "head": self.head, "crown": int(crown), "needtail": bool(needtail)})
151
+
152
+ def SendRoster(self, dstaddr: Optional[Tuple[str, int]] = None) -> None:
153
+ self.SendMessage({"type": "ROSTER", "head": self.head, "heads": list(self.heads)}, dstaddr=dstaddr)
154
+
155
+ def SendDream(self, dstaddr: Tuple[str, int]) -> None:
156
+ with self.lock:
157
+ self.SendMessage(dict(self.heart.DreamState()), dstaddr=dstaddr)
158
+
159
+ def SoftReboot(self) -> None:
160
+ with self.lock:
161
+ self.SendHunger(self.Crown(), needtail=True)
162
+ self.RenderStatus()
163
+
164
+ def MarkSeen(self, message: Dict[str, Any]) -> bool:
165
+ try:
166
+ seenkey = json.dumps({
167
+ "head": message.get("head", ""),
168
+ "crown": int(message.get("crown", 1) or 1),
169
+ "tallies": dict(message.get("tallies", {}) or {}),
170
+ }, sort_keys=True, separators=(",", ":"))
171
+ except Exception:
172
+ return False
173
+ if not (message.get("is_dream") and self.heart.envy) and seenkey in self.seen:
174
+ return False
175
+ self.seen[seenkey] = None
176
+ if len(self.seen) > SeenMax:
177
+ self.seen.pop(next(iter(self.seen)))
178
+ return True
179
+
180
+ def HandleSignal(self, message: Dict[str, Any], addr: Tuple[str, int]) -> bool:
181
+ messagetype = str(message.get("type", "") or "")
182
+ if messagetype == "HUNGER":
183
+ self.SendDream(addr)
184
+ elif messagetype == "AWAKE":
185
+ self.SendRoster(dstaddr=addr)
186
+ elif messagetype != "ROSTER":
187
+ return False
188
+ return True
189
+
190
+ def IngestMessage(self, message: Dict[str, Any], addr: Tuple[str, int]) -> None:
191
+ if "tallies" not in message or "crown" not in message:
192
+ return
193
+ with self.lock:
194
+ if not self.MarkSeen(message):
195
+ return
196
+ intents = self.heart.Ingest(dict(message))
197
+ self.ExecuteIntents(intents, srcaddr=addr)
198
+
199
+ def ExecuteIntents(self, intents: List[Intent], srcaddr: Optional[Tuple[str, int]] = None) -> None:
200
+ for intent in intents:
201
+ if intent.type == "Propagate":
202
+ tail = dict(intent.payload.get("tail", {}))
203
+ if tail:
204
+ self.SendTail(tail, srcaddr=srcaddr)
205
+ elif intent.type == "RequestSync":
206
+ crown = int(intent.payload.get("crown", 1) or 1)
207
+ if bool(intent.payload.get("needtail", False)) or bool(self.heart.envy):
208
+ self.SendHunger(crown, needtail=True)
209
+ self.RenderStatus()
210
+
211
+ class Receiver(threading.Thread):
212
+ def __init__(self, body: Body):
213
+ super().__init__(daemon=True)
214
+ self.body = body
215
+
216
+ def run(self) -> None:
217
+ while True:
218
+ try:
219
+ data, addr = self.body.sock.recvfrom(65535)
220
+ message = json.loads(data.decode("utf-8"))
221
+ if isinstance(message, dict) and self.body.HandleSignal(message, addr):
222
+ continue
223
+ if isinstance(message, dict):
224
+ self.body.IngestMessage(message, addr)
225
+ except Exception:
226
+ continue
227
+
228
+ def ReadCommand(body: Body) -> Command:
229
+ body.EnsureTarget()
230
+ filedescriptor = sys.stdin.fileno()
231
+ original = termios.tcgetattr(filedescriptor)
232
+ body.RenderStatus()
233
+ try:
234
+ tty.setcbreak(filedescriptor)
235
+ laststep = None
236
+ while True:
237
+ step = Step()
238
+ if step != laststep:
239
+ body.RenderStatus()
240
+ laststep = step
241
+ ready, _, _ = select([sys.stdin], [], [], 1 / 60)
242
+ if not ready:
243
+ continue
244
+ key = ReadKey()
245
+ if key == "":
246
+ raise EOFError
247
+ if key in ("\n", "\r"):
248
+ target = body.targethead
249
+ if target == body.head and len(body.heads) > 1:
250
+ body.MoveTarget(+1)
251
+ target = body.targethead
252
+ body.RenderStatus()
253
+ return ("FEED", target, body.amount)
254
+ if key == "\x03":
255
+ raise KeyboardInterrupt
256
+ if key == "\x18":
257
+ body.SoftReboot()
258
+ continue
259
+ if key in ("h", "H"):
260
+ return "HUNGER"
261
+ if key == "C":
262
+ body.MoveTarget(+1)
263
+ elif key == "D":
264
+ body.MoveTarget(-1)
265
+ elif key == "A":
266
+ body.amount = min(999, body.amount + 1)
267
+ elif key == "B":
268
+ body.amount = max(-999, body.amount - 1)
269
+ else:
270
+ continue
271
+ body.RenderStatus()
272
+ finally:
273
+ termios.tcsetattr(filedescriptor, termios.TCSADRAIN, original)
274
+
275
+ def RunBody(*, heart: Heart, head: str, port: int, peers: List[Tuple[str, int]], heads: List[str]) -> None:
276
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
277
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1 << 20)
278
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1 << 20)
279
+ if any(host == "255.255.255.255" for host, _ in peers):
280
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
281
+ sock.bind(("0.0.0.0", int(port)))
282
+ body = Body(head=str(head).upper(), heads=list(heads), sock=sock, peers=list(peers), heart=heart, lock=threading.Lock(), targethead=FirstTarget(head, heads))
283
+ Receiver(body).start()
284
+ with PrintLock:
285
+ sys.stdout.write("\x1b[2J\x1b[H")
286
+ sys.stdout.write(HideCursor)
287
+ sys.stdout.flush()
288
+ body.RenderStatus()
289
+ with body.lock:
290
+ body.SendHunger(body.Crown(), needtail=True)
291
+ try:
292
+ while True:
293
+ command = ReadCommand(body)
294
+ if command == "HUNGER":
295
+ with body.lock:
296
+ body.SendHunger(body.Crown(), needtail=True)
297
+ continue
298
+ if isinstance(command, tuple) and command[0] == "FEED":
299
+ _, tohead, amount = command
300
+ with body.lock:
301
+ intents = heart.Ingest(dict(heart.Propose(tohead, amount)))
302
+ body.ExecuteIntents(intents)
303
+ except (KeyboardInterrupt, EOFError) as exc:
304
+ raise ExitSignal() from exc
305
+ finally:
306
+ with PrintLock:
307
+ sys.stdout.write(ShowCursor)
308
+ sys.stdout.flush()
@@ -0,0 +1,288 @@
1
+ from __future__ import annotations
2
+ import json
3
+ import socket
4
+ import sys
5
+ import termios
6
+ import tty
7
+ from select import select
8
+ from typing import Dict, List, Optional, Set, Tuple
9
+
10
+ from .Plexus import Plexus
11
+ from .Body import ExitSignal, RunBody
12
+ from .Pulse import AwakeField, Clear, ExitLine, HideCursor, Index, Now, Phase, ReadKey, RenderCentered, RenderField, ShowCursor
13
+
14
+ BaseHeads = ["A", "B", "C", "D", "E"]
15
+ Fields = ["environment", "depth", "mutation", "head", "awakening"]
16
+ Labels = {
17
+ "environment": "ENVIRONMENT",
18
+ "depth": "DEPTH",
19
+ "mutation": "MUTATIONS",
20
+ "head": "HEADS",
21
+ "awakening": "AWAKENING",
22
+ }
23
+ Options = {
24
+ "environment": ["Den", "Swamp"],
25
+ "mutation": ["1", "2", "3", "4", "5"],
26
+ "head": list(BaseHeads),
27
+ }
28
+ RosterCache: Set[str] = set()
29
+ AwakeInterval = 0.75
30
+
31
+
32
+ def BuildDen(heads: List[str], depth: int, head: str) -> Tuple[int, List[Tuple[str, int]]]:
33
+ ports = {item: depth + index for index, item in enumerate(heads)}
34
+ return ports[head], [("127.0.0.1", ports[item]) for item in heads if item != head]
35
+
36
+
37
+ def BuildSwamp(heads: List[str], depth: int, head: str) -> Tuple[int, List[Tuple[str, int]]]:
38
+ return depth, [("255.255.255.255", depth)]
39
+
40
+
41
+ def ExitScreen() -> None:
42
+ filedescriptor = sys.stdin.fileno()
43
+ original = termios.tcgetattr(filedescriptor)
44
+ tty.setcbreak(filedescriptor)
45
+ start = Now()
46
+ lastpulse = None
47
+
48
+ try:
49
+ sys.stdout.write(HideCursor)
50
+ Clear()
51
+ while True:
52
+ phase = Phase(start)
53
+ pulse = Index(phase, 4)
54
+ if pulse != lastpulse:
55
+ RenderCentered([ExitLine(phase)], bias=0.5)
56
+ lastpulse = pulse
57
+ try:
58
+ ready, _, _ = select([sys.stdin], [], [], 1 / 60)
59
+ except KeyboardInterrupt:
60
+ return
61
+ if ready:
62
+ try:
63
+ ReadKey()
64
+ except KeyboardInterrupt:
65
+ pass
66
+ return
67
+ finally:
68
+ termios.tcsetattr(filedescriptor, termios.TCSADRAIN, original)
69
+ Clear()
70
+ sys.stdout.write(ShowCursor)
71
+ sys.stdout.flush()
72
+
73
+
74
+ def MutateShell() -> Dict[str, str]:
75
+ state = {"environment": "Den", "depth": "12321", "mutation": "1", "head": "A"}
76
+ fieldstep = 0
77
+ awakensock: Optional[socket.socket] = None
78
+ awakeheads: Set[str] = set()
79
+ expectedheads: Set[str] = set()
80
+ awakevalue = ""
81
+ awakenready = False
82
+ lastawakesent = 0.0
83
+
84
+ def CurrentHeads() -> List[str]:
85
+ return BaseHeads[:int(state["mutation"])]
86
+
87
+ def ClampHead() -> None:
88
+ heads = CurrentHeads()
89
+ if state["head"] not in heads:
90
+ state["head"] = heads[0]
91
+
92
+ def Network() -> Tuple[int, List[Tuple[str, int]]]:
93
+ heads = CurrentHeads()
94
+ depth = int(state["depth"])
95
+ return BuildDen(heads, depth, state["head"]) if state["environment"] == "Den" else BuildSwamp(heads, depth, state["head"])
96
+
97
+ def AwakeSend() -> None:
98
+ if awakensock is None:
99
+ return
100
+ message = json.dumps({"type": "AWAKE", "head": state["head"], "heads": CurrentHeads()}, separators=(",", ":")).encode("utf-8")
101
+ _, peers = Network()
102
+ for host, peerport in peers:
103
+ try:
104
+ awakensock.sendto(message, (host, peerport))
105
+ except Exception:
106
+ pass
107
+
108
+ def OpenAwakening() -> None:
109
+ nonlocal awakensock, awakeheads, expectedheads, awakevalue, awakenready, lastawakesent
110
+ global RosterCache
111
+ if awakensock is not None:
112
+ return
113
+ port, _ = Network()
114
+ awakensock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
115
+ awakensock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
116
+ if state["environment"] == "Swamp":
117
+ awakensock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
118
+ try:
119
+ awakensock.bind(("0.0.0.0", port))
120
+ except OSError as exc:
121
+ try:
122
+ awakensock.close()
123
+ except Exception:
124
+ pass
125
+ awakensock = None
126
+ raise ExitSignal() from exc
127
+ awakensock.setblocking(False)
128
+ expectedheads = set(CurrentHeads())
129
+ awakeheads = {item for item in RosterCache if item in expectedheads}
130
+ awakeheads.add(state["head"])
131
+ RosterCache = set(awakeheads)
132
+ awakevalue = AwakeField(awakeheads)
133
+ awakenready = False
134
+ lastawakesent = 0.0
135
+ for _ in range(3):
136
+ AwakeSend()
137
+ lastawakesent = Now()
138
+
139
+ def CloseAwakening() -> None:
140
+ nonlocal awakensock, awakeheads, expectedheads, awakevalue, awakenready, lastawakesent
141
+ global RosterCache
142
+ if awakeheads:
143
+ RosterCache = set(awakeheads)
144
+ if awakensock is not None:
145
+ try:
146
+ awakensock.close()
147
+ except Exception:
148
+ pass
149
+ awakensock = None
150
+ awakeheads = set()
151
+ expectedheads = set()
152
+ awakevalue = ""
153
+ awakenready = False
154
+ lastawakesent = 0.0
155
+
156
+ def PollAwakening() -> None:
157
+ nonlocal awakevalue, awakenready, lastawakesent, expectedheads, awakeheads
158
+ if awakensock is None:
159
+ return
160
+ now = Now()
161
+ if not awakenready and now - lastawakesent >= AwakeInterval:
162
+ AwakeSend()
163
+ lastawakesent = now
164
+ while True:
165
+ try:
166
+ data, _ = awakensock.recvfrom(1024)
167
+ except BlockingIOError:
168
+ break
169
+ except Exception:
170
+ break
171
+ try:
172
+ message = json.loads(data.decode("utf-8"))
173
+ except Exception:
174
+ continue
175
+ if not isinstance(message, dict):
176
+ continue
177
+ incoming = str(message.get("head", "") or "").strip().upper()
178
+ incomingheads = {str(item).upper() for item in list(message.get("heads", []) or []) if str(item).strip()}
179
+ messagetype = str(message.get("type", "") or "")
180
+ if messagetype == "AWAKE":
181
+ if incomingheads:
182
+ expectedheads = set(incomingheads)
183
+ if incoming and incoming not in awakeheads:
184
+ awakeheads.add(incoming)
185
+ AwakeSend()
186
+ lastawakesent = Now()
187
+ continue
188
+ if messagetype == "ROSTER":
189
+ if incomingheads:
190
+ expectedheads = set(incomingheads)
191
+ awakeheads.update(incomingheads)
192
+ if incoming:
193
+ awakeheads.add(incoming)
194
+ awakevalue = AwakeField(awakeheads)
195
+ awakenready = bool(expectedheads) and awakeheads >= expectedheads
196
+
197
+ filedescriptor = sys.stdin.fileno()
198
+ original = termios.tcgetattr(filedescriptor)
199
+ tty.setcbreak(filedescriptor)
200
+ start = Now()
201
+ lastpulse = None
202
+ lastfield = None
203
+ lastvalue = None
204
+
205
+ try:
206
+ sys.stdout.write(HideCursor)
207
+ Clear()
208
+ while True:
209
+ field = Fields[fieldstep]
210
+ if field == "awakening":
211
+ OpenAwakening()
212
+ PollAwakening()
213
+ phase = Phase(start)
214
+ value = awakevalue if field == "awakening" else state[field]
215
+ pulse = Index(phase, 9)
216
+ if pulse != lastpulse or field != lastfield or value != lastvalue:
217
+ RenderField("Mutate", Labels[field], value, phase)
218
+ lastpulse = pulse
219
+ lastfield = field
220
+ lastvalue = value
221
+ if field == "awakening" and awakenready:
222
+ CloseAwakening()
223
+ return state
224
+ try:
225
+ ready, _, _ = select([sys.stdin], [], [], 1 / 60)
226
+ except KeyboardInterrupt:
227
+ raise ExitSignal
228
+ if not ready:
229
+ continue
230
+ try:
231
+ key = ReadKey()
232
+ except KeyboardInterrupt:
233
+ raise ExitSignal
234
+ field = Fields[fieldstep]
235
+ if key == "\x03" or field == "awakening":
236
+ raise ExitSignal
237
+ if key in ("\n", "\r", "C"):
238
+ fieldstep = min(len(Fields) - 1, fieldstep + 1)
239
+ continue
240
+ if key == "D":
241
+ fieldstep = max(0, fieldstep - 1)
242
+ continue
243
+ if key in ("A", "B"):
244
+ direction = 1 if key == "A" else -1
245
+ if field == "depth":
246
+ state["depth"] = f"{max(0, min(99999, int(state['depth']) + direction)):05d}"
247
+ continue
248
+ fieldoptions = Options.get(field)
249
+ if fieldoptions is not None:
250
+ index = fieldoptions.index(state[field])
251
+ if field == "head":
252
+ direction = -direction
253
+ state[field] = fieldoptions[(index + direction) % len(fieldoptions)]
254
+ if field == "mutation":
255
+ ClampHead()
256
+ continue
257
+ if not key.isprintable():
258
+ continue
259
+ if field == "mutation" and key in "12345":
260
+ state["mutation"] = key
261
+ ClampHead()
262
+ elif field == "head":
263
+ typed = key.upper()
264
+ if typed in BaseHeads:
265
+ state["head"] = typed
266
+ ClampHead()
267
+ finally:
268
+ CloseAwakening()
269
+ termios.tcsetattr(filedescriptor, termios.TCSADRAIN, original)
270
+ Clear()
271
+ sys.stdout.write(ShowCursor)
272
+ sys.stdout.flush()
273
+
274
+
275
+ def Mutate() -> None:
276
+ try:
277
+ state = MutateShell()
278
+ heads = BaseHeads[:int(state["mutation"])]
279
+ head = state["head"] if state["head"] in heads else heads[0]
280
+ depth = int(state["depth"])
281
+ port, peers = BuildDen(heads, depth, head) if state["environment"] == "Den" else BuildSwamp(heads, depth, head)
282
+ RunBody(heart=Plexus(head=head, heads=heads), head=head, port=port, peers=peers, heads=heads)
283
+ except ExitSignal:
284
+ ExitScreen()
285
+
286
+
287
+ if __name__ == "__main__":
288
+ Mutate()
@@ -0,0 +1,193 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass
3
+ from typing import Any, Dict, List, Optional, Literal
4
+
5
+ Gems = {1: "Onyx", 2: "Jade", 3: "Opal"}
6
+
7
+ def GemName(g: int) -> str:
8
+ return Gems.get(int(g or 1), "G?")
9
+
10
+ def CrownNext(c: int) -> int:
11
+ return 1 if int(c) >= 3 else int(c) + 1
12
+
13
+ IntentType = Literal["Propagate", "RequestSync", "Envy"]
14
+
15
+ @dataclass(frozen=True)
16
+ class Intent:
17
+ type: IntentType
18
+ payload: Dict[str, Any]
19
+
20
+ @dataclass
21
+ class Tetron:
22
+ tallies: Dict[str, int]
23
+
24
+ def Snapshot(self) -> Dict[str, Any]:
25
+ return {
26
+ "tallies": dict(self.tallies),
27
+ "is_dream": True,
28
+ }
29
+
30
+ @dataclass
31
+ class PlexusState:
32
+ tallies: Dict[str, int]
33
+ crown: int
34
+ head: Optional[str] = None
35
+
36
+ class Plexus:
37
+ def __init__(self, head: str, heads: List[str]) -> None:
38
+ self.head = str(head)
39
+ self.heads = list(heads)
40
+
41
+ tallies = {h: 10 for h in self.heads}
42
+
43
+ self.tetron = Tetron(tallies=dict(tallies))
44
+ self.state = PlexusState(
45
+ tallies=dict(tallies),
46
+ crown=1,
47
+ head=None,
48
+ )
49
+
50
+ self.tail: Optional[Dict[str, Any]] = None
51
+ self.envy: bool = False
52
+
53
+ def Snapshot(self) -> Dict[str, Any]:
54
+ return {
55
+ "head": self.head,
56
+ "tallies": dict(self.state.tallies),
57
+ "crown": int(self.state.crown),
58
+ "is_dream": False,
59
+ }
60
+
61
+ def Emotions(self) -> Dict[str, Any]:
62
+ return {"envy": bool(self.envy)}
63
+
64
+ def DreamState(self) -> Dict[str, Any]:
65
+ d = self.tetron.Snapshot()
66
+ d["crown"] = int(self.state.crown)
67
+ return d
68
+
69
+ def EnvyReanchor(self) -> Dict[str, Any]:
70
+ return {
71
+ "head": self.head,
72
+ "tallies": dict(self.tetron.tallies),
73
+ "crown": int(self.state.crown),
74
+ "is_dream": True,
75
+ "mode": "Envy",
76
+ }
77
+
78
+ def ExpectedTotal(self) -> int:
79
+ return 10 * len(self.heads)
80
+
81
+ def TalliesTotal(self, tallies: Dict[str, Any]) -> int:
82
+ return sum(int(value or 0) for value in dict(tallies or {}).values())
83
+
84
+ def ValidTotal(self, tallies: Dict[str, Any]) -> bool:
85
+ return self.TalliesTotal(tallies) == self.ExpectedTotal()
86
+
87
+ def Propose(self, tohead: str, amount: int) -> Dict[str, Any]:
88
+ if self.envy:
89
+ return self.EnvyReanchor()
90
+
91
+ tallies = dict(self.state.tallies)
92
+ tallies[self.head] = tallies.get(self.head, 0) - int(amount)
93
+ tallies[tohead] = tallies.get(tohead, 0) + int(amount)
94
+
95
+ return {
96
+ "head": self.head,
97
+ "tallies": tallies,
98
+ "crown": CrownNext(self.state.crown),
99
+ }
100
+
101
+ def Ingest(self, tailin: Dict[str, Any]) -> List[Intent]:
102
+ intents: List[Intent] = []
103
+
104
+ if tailin.get("is_dream"):
105
+ dreamtallies = dict(tailin.get("tallies", {}) or {})
106
+
107
+ # ================= LINCHPIN ================= #
108
+ if dreamtallies and not self.ValidTotal(dreamtallies):
109
+ # ============================================ #
110
+ if not self.envy:
111
+ self.envy = True
112
+ intents.append(Intent("Envy", {
113
+ "expectedtotal": self.ExpectedTotal(),
114
+ "incomingtotal": self.TalliesTotal(dreamtallies),
115
+ }))
116
+
117
+ intents.append(Intent("RequestSync", {
118
+ "crown": int(self.state.crown),
119
+ "gem": GemName(self.state.crown),
120
+ "needtail": True,
121
+ }))
122
+ return intents
123
+
124
+ if dreamtallies and dreamtallies != self.state.tallies:
125
+ self.state.tallies = dict(dreamtallies)
126
+ self.tetron.tallies = dict(dreamtallies)
127
+
128
+ if self.envy:
129
+ self.envy = False
130
+
131
+ return intents
132
+
133
+ incomingtallies = dict(tailin.get("tallies", {}))
134
+ inccrown = int(tailin.get("crown", self.state.crown))
135
+
136
+ cur = int(self.state.crown)
137
+ exp = CrownNext(cur)
138
+
139
+ # ================= LINCHPIN ================= #
140
+ if inccrown not in (cur, exp):
141
+ # ============================================ #
142
+ if not self.envy:
143
+ self.envy = True
144
+ intents.append(Intent("Envy", {
145
+ "currentcrown": cur,
146
+ "incomingcrown": inccrown,
147
+ }))
148
+
149
+ intents.append(Intent("RequestSync", {
150
+ "crown": cur,
151
+ "gem": GemName(cur),
152
+ }))
153
+ return intents
154
+
155
+ # ================= LINCHPIN ================= #
156
+ if not self.ValidTotal(incomingtallies):
157
+ # ============================================ #
158
+ if not self.envy:
159
+ self.envy = True
160
+ intents.append(Intent("Envy", {
161
+ "expectedtotal": self.ExpectedTotal(),
162
+ "incomingtotal": self.TalliesTotal(incomingtallies),
163
+ }))
164
+
165
+ intents.append(Intent("RequestSync", {
166
+ "crown": cur,
167
+ "gem": GemName(cur),
168
+ "needtail": True,
169
+ }))
170
+ return intents
171
+
172
+ if self.envy:
173
+ self.envy = False
174
+
175
+ if incomingtallies == self.state.tallies:
176
+ return intents
177
+
178
+ self.state.tallies = dict(incomingtallies)
179
+ self.state.crown = inccrown
180
+ self.state.head = str(tailin.get("head", "")) or None
181
+
182
+ self.tetron.tallies = dict(incomingtallies)
183
+ self.tail = dict(tailin)
184
+
185
+ intents.append(Intent("Propagate", {"tail": dict(self.tail)}))
186
+
187
+ if inccrown == exp:
188
+ intents.append(Intent("RequestSync", {
189
+ "crown": inccrown,
190
+ "gem": GemName(inccrown),
191
+ }))
192
+
193
+ return intents
@@ -0,0 +1,144 @@
1
+ from __future__ import annotations
2
+ import re
3
+ import shutil
4
+ import sys
5
+ import time
6
+ from typing import Iterable, Sequence, Set, Tuple
7
+
8
+ Reset = "\x1b[0m"
9
+ Ash = "\x1b[90m"
10
+ Blue = "\x1b[36m"
11
+ Green = "\x1b[92m"
12
+ Teal = "\x1b[38;2;0;150;130m"
13
+ HideCursor = "\x1b[?25l"
14
+ ShowCursor = "\x1b[?25h"
15
+ AnsiPattern = re.compile(r"\x1b\[[0-9;]*m")
16
+ PulseRate = 7.0
17
+
18
+ BubbleFrames = [
19
+ "",
20
+ "0",
21
+ "o0o",
22
+ ".o0o.",
23
+ "o.o0o.o",
24
+ "0o.o0o.o0",
25
+ "o0o.o0o.o0o",
26
+ ".o0o.o0o.o0o.",
27
+ "..o0o.o0o.o0o..",
28
+ ]
29
+
30
+ ExitFrames = ["", ".", "..", "..."]
31
+
32
+ def Now() -> float:
33
+ return time.monotonic()
34
+
35
+ def Step(rate: float = PulseRate) -> int:
36
+ return int(Now() * float(rate or 0.0))
37
+
38
+ def Phase(start: float, speed: float = PulseRate) -> float:
39
+ return (Now() - float(start or 0.0)) * float(speed or 0.0)
40
+
41
+ def Index(phase: float, frames: int) -> int:
42
+ if frames <= 1:
43
+ return 0
44
+ span = (frames * 2) - 2
45
+ index = int(phase) % span
46
+ if index >= frames:
47
+ index = span - index
48
+ return index
49
+
50
+ def FlickerCycle(sequence: Sequence[str], rate: float = PulseRate, fallback: str = Teal) -> str:
51
+ if not sequence:
52
+ return fallback
53
+ return sequence[Step(rate) % len(sequence)]
54
+
55
+ def Flicker1() -> str:
56
+ return FlickerCycle((Green, Blue, Teal))
57
+
58
+ def Flicker2() -> str:
59
+ return FlickerCycle((Green, Teal))
60
+
61
+ def Flicker3() -> str:
62
+ return FlickerCycle((Teal, Blue))
63
+
64
+ def Flicker4() -> str:
65
+ return FlickerCycle((Blue, Teal))
66
+
67
+ def VisibleLength(text: str) -> int:
68
+ return len(AnsiPattern.sub("", str(text or "")))
69
+
70
+ def PadLine(text: str, width: int) -> str:
71
+ built = str(text or "")
72
+ visible = VisibleLength(built)
73
+ if visible < width:
74
+ built += " " * (width - visible)
75
+ return built
76
+
77
+ def Center(text: str, width: int = 80) -> str:
78
+ line = str(text or "")
79
+ gap = max(0, (width - VisibleLength(line)) // 2)
80
+ built = (" " * gap) + line
81
+ visible = VisibleLength(built)
82
+ if visible < width:
83
+ built += " " * (width - visible)
84
+ return built
85
+
86
+ def TerminalSize() -> Tuple[int, int]:
87
+ size = shutil.get_terminal_size(fallback=(80, 24))
88
+ return size.columns, size.lines
89
+
90
+ def VerticalOffset(lines: int, height: int = 24, bias: float = 0.35) -> int:
91
+ return max(0, int((height - lines) * bias))
92
+
93
+ def Clear() -> None:
94
+ sys.stdout.write("\x1b[2J\x1b[H")
95
+ sys.stdout.flush()
96
+
97
+ def CursorLeft(count: int) -> str:
98
+ return f"\x1b[{count}D" if count > 0 else ""
99
+
100
+ def ReadKey() -> str:
101
+ key = sys.stdin.read(1)
102
+ if key == "\x1b" and sys.stdin.read(1) in ("[", "O"):
103
+ return sys.stdin.read(1)
104
+ return key
105
+
106
+ def RenderCentered(lines: Iterable[str], *, width: int = 80, minimumheight: int = 24, bias: float = 0.35) -> None:
107
+ linelist = list(lines)
108
+ _, terminalheight = TerminalSize()
109
+ height = max(minimumheight, terminalheight)
110
+ topgap = VerticalOffset(len(linelist), height, bias)
111
+ sys.stdout.write("\x1b[H")
112
+ if topgap:
113
+ sys.stdout.write("\n" * topgap)
114
+ sys.stdout.write("\n".join(Center(line, width) for line in linelist))
115
+ sys.stdout.flush()
116
+
117
+ def TitleLine(text: str) -> str:
118
+ return f"{Green}.:{Reset}{Teal}{text}{Reset}{Green}:.{Reset}"
119
+
120
+ def LabelLine(text: str) -> str:
121
+ return f"{Ash}{text}{Reset}"
122
+
123
+ def DotField(value: str) -> str:
124
+ return f"{Green}.{Reset}{Teal}{value}{Reset}{Green}.{Reset}"
125
+
126
+ def AwakeField(heads: Set[str]) -> str:
127
+ return "" if not heads else f"{Teal}" + f"{Reset}{Green}.{Reset}{Teal}".join(sorted(heads))
128
+
129
+ def RenderField(title: str, label: str, value: str, phase: float, bias: float = 0.35) -> None:
130
+ RenderCentered([
131
+ TitleLine(title),
132
+ "",
133
+ BubbleLine(phase),
134
+ LabelLine(label),
135
+ DotField(value),
136
+ ], bias=bias)
137
+
138
+ def BubbleLine(phase: float) -> str:
139
+ return f"{Ash}{BubbleFrames[Index(phase, len(BubbleFrames))]}{Reset}"
140
+
141
+ def ExitLine(phase: float) -> str:
142
+ dots = ExitFrames[Index(phase, len(ExitFrames))]
143
+ message = f"{Teal}Sniff{Green}.{Teal}Snort{Green}..{Teal}RAWR{Green}...{Teal}bye{Reset}"
144
+ return f"{Green}{dots}{Reset}{Teal}{message}{Reset}{Green}{dots}{Reset}"
@@ -0,0 +1,7 @@
1
+ from Game import Mutate
2
+
3
+ def Hydra():
4
+ Mutate.Mutate()
5
+
6
+ if __name__ == "__main__":
7
+ Hydra()
@@ -0,0 +1,4 @@
1
+ Metadata-Version: 2.4
2
+ Name: Hydra-Game
3
+ Version: 1.0.0
4
+ Summary: Terminal entrypoint for Hydra
@@ -0,0 +1,12 @@
1
+ Hydra.py
2
+ README.md
3
+ pyproject.toml
4
+ Game/Body.py
5
+ Game/Mutate.py
6
+ Game/Plexus.py
7
+ Game/Pulse.py
8
+ Hydra_Game.egg-info/PKG-INFO
9
+ Hydra_Game.egg-info/SOURCES.txt
10
+ Hydra_Game.egg-info/dependency_links.txt
11
+ Hydra_Game.egg-info/entry_points.txt
12
+ Hydra_Game.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ Hydra = Hydra:main
@@ -0,0 +1,2 @@
1
+ Game
2
+ Hydra
@@ -0,0 +1,4 @@
1
+ Metadata-Version: 2.4
2
+ Name: Hydra-Game
3
+ Version: 1.0.0
4
+ Summary: Terminal entrypoint for Hydra
@@ -0,0 +1,80 @@
1
+ # 🐍 Hydra 🐍
2
+
3
+ **Hands-on network toy where packets collide and state flows.**
4
+
5
+ ---
6
+
7
+ <img src="../Relics/Awake.gif"/>
8
+
9
+ ---
10
+
11
+ ## 🪵 Swamp Thing
12
+
13
+ In **Hydra**, the state is *fluid*. Nodes can leave, rejoin, and rehydrate instantly with almost no ceremony. Move between a **den** or a **swamp**, across machines or terminals.
14
+
15
+ As long as your **head is unique** and you share the same **depth**, your node will simply **snap to what is**. *What holds is what remains.*
16
+
17
+ Hydra is a distributed expression of the **Oblivious Compute system**. It is not a coordinated network, but a field of independent nodes sharing a single **admissible state**. Each node emits and each node observes, and what persists is simply what the network accepts.
18
+
19
+ **There is no leader and no history—only convergence.**
20
+
21
+ > Simply put, this is a distributed packet collider in under a thousand lines of code.
22
+
23
+ ---
24
+
25
+ ## ⬇️ Install
26
+
27
+ To run Hydra, install it with:
28
+ ```bash
29
+ pipx install Hydra-Braid
30
+ Hydra
31
+ ```
32
+ You’ll need **Python 3.9 or newer** and an **80x24 UNIX-like terminal environment.**
33
+
34
+ ---
35
+
36
+ ## 🐧 Operating System Support
37
+
38
+ - ✅ Linux
39
+ - ✅ macOS
40
+ - ❌ Windows *(sorry, but not sorry)*
41
+
42
+ ---
43
+
44
+ ## 🌐 Networking
45
+
46
+ Hydra runs in two modes.
47
+
48
+ **Den** is local—multiple terminals on the same machine. *(sandbox)*
49
+ **Swamp** runs across a LAN, allowing multiple machines to share the same **Braid**.
50
+
51
+ > *All nodes must use the same depth (port), and the same number of mutated heads.*
52
+ > *Each node must use a unique head.*
53
+
54
+ <img src="../Relics/Spam.gif"/>
55
+
56
+ > *Run Hydra in a swamp if you want to use it as a packet collider.*
57
+ > *Push state with multiple machines and see what holds.*
58
+
59
+ ---
60
+
61
+ 🎯 **Intent**
62
+
63
+ The Hydra Demo has been published as a **public technical disclosure**.
64
+
65
+ This demo exists to show that **oblivious convergence through an admissability gate** is possible.
66
+
67
+ If it fails, it fails cleanly.
68
+ If it works, it demonstrates a **new computational primitive**.
69
+
70
+ <img src="../Relics/Alpha.png" width="400"/>
71
+
72
+ <img src="../Relics/Bye.gif" width="400"/>
73
+
74
+ ---
75
+
76
+ ## 📜 License
77
+
78
+ This project is released under the terms of the [LICENSE](../LICENSE).
79
+
80
+ Use it, study it, modify it—just respect the terms outlined there.
@@ -0,0 +1,14 @@
1
+ [project]
2
+ name = "Hydra-Game"
3
+ version = "1.0.0"
4
+ description = "Terminal entrypoint for Hydra"
5
+
6
+ [project.scripts]
7
+ Hydra = "Hydra:main"
8
+
9
+ [tool.setuptools]
10
+ py-modules = ["Hydra"]
11
+
12
+ [tool.setuptools.packages.find]
13
+ where = ["."]
14
+ include = ["Game"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+