rpp-protocol 0.1.5__py3-none-any.whl

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.
rpp/visual.py ADDED
@@ -0,0 +1,389 @@
1
+ """
2
+ RPP Visual Output Module
3
+
4
+ ASCII art and optional ANSI visualizations for RPP operations.
5
+ Designed for terminal-based feedback that works in any environment.
6
+
7
+ Two modes:
8
+ - Default (ASCII): Works everywhere (SSH, PuTTY, serial, emulators)
9
+ - Fancy (ANSI): Color and styling for modern terminals (opt-in)
10
+ """
11
+
12
+ from typing import Optional
13
+
14
+ # ANSI color codes (only used when fancy=True)
15
+ ANSI = {
16
+ "reset": "\033[0m",
17
+ "bold": "\033[1m",
18
+ "dim": "\033[2m",
19
+ "green": "\033[32m",
20
+ "red": "\033[31m",
21
+ "yellow": "\033[33m",
22
+ "blue": "\033[34m",
23
+ "cyan": "\033[36m",
24
+ "magenta": "\033[35m",
25
+ "bg_green": "\033[42m",
26
+ "bg_red": "\033[41m",
27
+ "bg_blue": "\033[44m",
28
+ "bg_yellow": "\033[43m",
29
+ }
30
+
31
+
32
+ def _c(text: str, color: str, fancy: bool) -> str:
33
+ """Apply color if fancy mode is enabled."""
34
+ if fancy and color in ANSI:
35
+ return f"{ANSI[color]}{text}{ANSI['reset']}"
36
+ return text
37
+
38
+
39
+ def _bold(text: str, fancy: bool) -> str:
40
+ """Apply bold if fancy mode is enabled."""
41
+ if fancy:
42
+ return f"{ANSI['bold']}{text}{ANSI['reset']}"
43
+ return text
44
+
45
+
46
+ def bit_layout_diagram(shell: int, theta: int, phi: int, harmonic: int,
47
+ raw: int, fancy: bool = False) -> str:
48
+ """
49
+ Generate ASCII diagram showing the 28-bit address layout.
50
+
51
+ Uses ASCII-only characters for Windows compatibility.
52
+
53
+ Example output:
54
+ +-------------------------------------------------------------+
55
+ | 28-bit RPP Address: 0x0018A041 |
56
+ +------+----------+----------+----------+--------------------+
57
+ | Rsrv | Shell | Theta | Phi | Harmonic |
58
+ | 4bit | 2bit | 9bit | 9bit | 8bit |
59
+ +------+----------+----------+----------+--------------------+
60
+ | 0000 | 00 | 000001100| 100101000| 01000001 |
61
+ | (0) | (0) | (12) | (296) | (65) |
62
+ | | Hot | Gene | Abstract | |
63
+ +------+----------+----------+----------+--------------------+
64
+ """
65
+ # Convert to binary strings
66
+ shell_bin = f"{shell:02b}"
67
+ theta_bin = f"{theta:09b}"
68
+ phi_bin = f"{phi:09b}"
69
+ harmonic_bin = f"{harmonic:08b}"
70
+
71
+ # Get semantic names
72
+ shell_names = {0: "Hot", 1: "Warm", 2: "Cold", 3: "Frozen"}
73
+ shell_name = shell_names[shell]
74
+
75
+ if theta < 64:
76
+ sector = "Gene"
77
+ elif theta < 128:
78
+ sector = "Memory"
79
+ elif theta < 192:
80
+ sector = "Witness"
81
+ elif theta < 256:
82
+ sector = "Dream"
83
+ elif theta < 320:
84
+ sector = "Bridge"
85
+ elif theta < 384:
86
+ sector = "Guardian"
87
+ elif theta < 448:
88
+ sector = "Emergence"
89
+ else:
90
+ sector = "Meta"
91
+
92
+ if phi < 128:
93
+ grounding = "Grounded"
94
+ elif phi < 256:
95
+ grounding = "Transit"
96
+ elif phi < 384:
97
+ grounding = "Abstract"
98
+ else:
99
+ grounding = "Ethereal"
100
+
101
+ # Color the header and key values
102
+ header = _c(f"28-bit RPP Address: 0x{raw:07X}", "cyan", fancy)
103
+ shell_val = _c(shell_name, "green" if shell == 0 else "yellow" if shell < 3 else "red", fancy)
104
+ sector_val = _c(sector, "blue", fancy)
105
+ ground_val = _c(grounding, "magenta", fancy)
106
+
107
+ lines = [
108
+ "+-------------------------------------------------------------+",
109
+ f"| {header:<58} |",
110
+ "+------+----------+----------+----------+--------------------+",
111
+ "| Rsrv | Shell | Theta | Phi | Harmonic |",
112
+ "| 4bit | 2bit | 9bit | 9bit | 8bit |",
113
+ "+------+----------+----------+----------+--------------------+",
114
+ f"| 0000 | {shell_bin} | {theta_bin}| {phi_bin}| {harmonic_bin} |",
115
+ f"| (0) | ({shell}) | ({theta:^3}) | ({phi:^3}) | ({harmonic:^3}) |",
116
+ f"| | {shell_val:^8} | {sector_val:^8} | {ground_val:^8} | |",
117
+ "+------+----------+----------+----------+--------------------+",
118
+ ]
119
+
120
+ return "\n".join(lines)
121
+
122
+
123
+ def routing_diagram(shell: int, allowed: bool, route: Optional[str],
124
+ reason: str, fancy: bool = False) -> str:
125
+ """
126
+ Generate ASCII diagram showing routing decision.
127
+
128
+ Uses ASCII-only characters for Windows compatibility.
129
+
130
+ Example output:
131
+ +-----------------------------------------+
132
+ | ROUTING DECISION |
133
+ +-----------------------------------------+
134
+ | +-----+ |
135
+ | | REQ | --> [RESOLVER] --> [ALLOWED] |
136
+ | +-----+ | |
137
+ | v |
138
+ | +---------+ |
139
+ | | HOT | |
140
+ | | STORAGE | |
141
+ | +---------+ |
142
+ +-----------------------------------------+
143
+ """
144
+ shell_names = {0: "HOT", 1: "WARM", 2: "COLD", 3: "FROZEN"}
145
+ shell_name = shell_names.get(shell, "???")
146
+
147
+ if allowed:
148
+ status_ascii = _c("[ALLOWED]", "green", fancy)
149
+ else:
150
+ status_ascii = _c("[DENIED]", "red", fancy)
151
+
152
+ route_display = route if route else "null"
153
+ shell_box = _c(shell_name, "cyan", fancy)
154
+
155
+ lines = [
156
+ "+-----------------------------------------+",
157
+ "| ROUTING DECISION |",
158
+ "+-----------------------------------------+",
159
+ "| +-----+ |",
160
+ f"| | REQ | --> [RESOLVER] --> {status_ascii:<10}|",
161
+ "| +-----+ | |",
162
+ "| v |",
163
+ "| +---------+ |",
164
+ f"| | {shell_box:^7} | |",
165
+ "| | STORAGE | |",
166
+ "| +---------+ |",
167
+ "+-----------------------------------------+",
168
+ f"| Route: {route_display:<30}|",
169
+ f"| Reason: {reason:<30}|",
170
+ "+-----------------------------------------+",
171
+ ]
172
+
173
+ return "\n".join(lines)
174
+
175
+
176
+ def consent_meter(phi: int, fancy: bool = False) -> str:
177
+ """
178
+ Generate ASCII consent/grounding meter based on phi value.
179
+
180
+ Uses ASCII-only characters for Windows compatibility.
181
+
182
+ Example output:
183
+ Consent Level: [########............] 40/511 (Grounded)
184
+ """
185
+ # Normalize to 20-char bar
186
+ filled = int((phi / 511) * 20)
187
+ empty = 20 - filled
188
+
189
+ if phi < 128:
190
+ level = "Grounded"
191
+ color = "green"
192
+ elif phi < 256:
193
+ level = "Transitional"
194
+ color = "yellow"
195
+ elif phi < 384:
196
+ level = "Abstract"
197
+ color = "yellow"
198
+ else:
199
+ level = "Ethereal"
200
+ color = "red"
201
+
202
+ bar_filled = _c("#" * filled, color, fancy)
203
+ bar_empty = "." * empty
204
+ level_text = _c(level, color, fancy)
205
+
206
+ return f"Consent Level: [{bar_filled}{bar_empty}] {phi}/511 ({level_text})"
207
+
208
+
209
+ def shell_tier_visual(shell: int, fancy: bool = False) -> str:
210
+ """
211
+ Generate ASCII representation of shell tier.
212
+
213
+ Uses ASCII-only characters for Windows compatibility.
214
+
215
+ Example output:
216
+ o Frozen (3)
217
+ o Cold (2)
218
+ o Warm (1)
219
+ > * Hot (0) < ACTIVE
220
+ """
221
+ lines = []
222
+ shell_info = [
223
+ (0, "Hot", "In-memory, immediate access"),
224
+ (1, "Warm", "Fast storage, quick retrieval"),
225
+ (2, "Cold", "Archive storage, slower access"),
226
+ (3, "Frozen", "Deep archive, consent required"),
227
+ ]
228
+
229
+ for s, name, desc in reversed(shell_info):
230
+ if s == shell:
231
+ marker = _c("*", "green", fancy)
232
+ arrow = _c(">", "cyan", fancy)
233
+ suffix = _c("< ACTIVE", "cyan", fancy)
234
+ lines.append(f" {arrow} {marker} {name} ({s}) {suffix}")
235
+ else:
236
+ marker = "o"
237
+ lines.append(f" {marker} {name} ({s})")
238
+
239
+ return "\n".join(lines)
240
+
241
+
242
+ def theta_wheel(theta: int, fancy: bool = False) -> str:
243
+ """
244
+ Generate ASCII representation of theta sectors as a wheel.
245
+
246
+ Uses ASCII-only characters for Windows compatibility.
247
+ Shows the 8 semantic sectors with the active one highlighted.
248
+ """
249
+ sectors = [
250
+ (0, 63, "Gene"),
251
+ (64, 127, "Memory"),
252
+ (128, 191, "Witness"),
253
+ (192, 255, "Dream"),
254
+ (256, 319, "Bridge"),
255
+ (320, 383, "Guardian"),
256
+ (384, 447, "Emergence"),
257
+ (448, 511, "Meta"),
258
+ ]
259
+
260
+ # Find active sector
261
+ active_idx = 0
262
+ for i, (start, end, name) in enumerate(sectors):
263
+ if start <= theta <= end:
264
+ active_idx = i
265
+ break
266
+
267
+ # Simple linear representation
268
+ parts = []
269
+ for i, (start, end, name) in enumerate(sectors):
270
+ if i == active_idx:
271
+ parts.append(_c(f"[{name}]", "cyan", fancy))
272
+ else:
273
+ parts.append(f" {name} ")
274
+
275
+ wheel = " -> ".join(parts[:4]) + "\n" + " -> ".join(parts[4:])
276
+ header = f"Theta Sector: {theta}/511"
277
+
278
+ return f"{header}\n{wheel}"
279
+
280
+
281
+ def operation_status(operation: str, success: bool, fancy: bool = False) -> str:
282
+ """
283
+ Generate operation status indicator.
284
+
285
+ Example: [ENCODE] OK / [ENCODE] FAIL
286
+ """
287
+ op_text = _c(f"[{operation.upper()}]", "bold", fancy)
288
+
289
+ if success:
290
+ # Use ASCII-safe symbols for Windows compatibility
291
+ check = "[OK]" if not fancy else "OK"
292
+ status = _c(check, "green", fancy)
293
+ else:
294
+ cross = "[FAIL]" if not fancy else "FAIL"
295
+ status = _c(cross, "red", fancy)
296
+
297
+ return f"{op_text} {status}"
298
+
299
+
300
+ def spinner_frame(frame: int) -> str:
301
+ """
302
+ Get a spinner animation frame.
303
+
304
+ Uses ASCII-only characters for Windows compatibility.
305
+ For use in fancy mode with animation.
306
+ """
307
+ frames = ["|", "/", "-", "\\"]
308
+ return frames[frame % len(frames)]
309
+
310
+
311
+ def demo_banner(fancy: bool = False) -> str:
312
+ """
313
+ Generate RPP demo banner.
314
+
315
+ Uses ASCII-only characters for Windows compatibility.
316
+ """
317
+ if fancy:
318
+ banner = f"""{ANSI['cyan']}{ANSI['bold']}
319
+ +===========================================================+
320
+ | |
321
+ | RRRR PPPP PPPP |
322
+ | R R P P P P Rotational Packet Protocol |
323
+ | RRRR PPPP PPPP 28-bit Semantic Addressing |
324
+ | R R P P |
325
+ | R R P P Consent-Aware Routing |
326
+ | |
327
+ +===========================================================+
328
+ {ANSI['reset']}"""
329
+ else:
330
+ banner = """
331
+ +===========================================================+
332
+ | |
333
+ | RRRR PPPP PPPP |
334
+ | R R P P P P Rotational Packet Protocol |
335
+ | RRRR PPPP PPPP 28-bit Semantic Addressing |
336
+ | R R P P |
337
+ | R R P P Consent-Aware Routing |
338
+ | |
339
+ +===========================================================+
340
+ """
341
+ return banner
342
+
343
+
344
+ def address_mini(addr_hex: str, shell_name: str, sector: str,
345
+ grounding: str, fancy: bool = False) -> str:
346
+ """
347
+ Generate compact single-line address summary.
348
+
349
+ Uses ASCII-only characters for Windows compatibility.
350
+
351
+ Example: 0x0018A041 | Hot | Gene | Abstract
352
+ """
353
+ parts = [
354
+ _c(addr_hex, "cyan", fancy),
355
+ _c(shell_name, "green", fancy),
356
+ _c(sector, "blue", fancy),
357
+ _c(grounding, "magenta", fancy),
358
+ ]
359
+ return " | ".join(parts)
360
+
361
+
362
+ def success_box(message: str, fancy: bool = False) -> str:
363
+ """Generate a success message box. Uses ASCII-only characters."""
364
+ width = len(message) + 4
365
+ border = "=" * width
366
+
367
+ if fancy:
368
+ return f"""{ANSI['green']}+{border}+
369
+ | {message} |
370
+ +{border}+{ANSI['reset']}"""
371
+ else:
372
+ return f"""+{border}+
373
+ | {message} |
374
+ +{border}+"""
375
+
376
+
377
+ def error_box(message: str, fancy: bool = False) -> str:
378
+ """Generate an error message box. Uses ASCII-only characters."""
379
+ width = len(message) + 4
380
+ border = "=" * width
381
+
382
+ if fancy:
383
+ return f"""{ANSI['red']}+{border}+
384
+ | {message} |
385
+ +{border}+{ANSI['reset']}"""
386
+ else:
387
+ return f"""+{border}+
388
+ | {message} |
389
+ +{border}+"""