Job Portfolio

Professional Portfolio

A focused collection of selected technical and creative projects for prospective employers. Use the project tabs to review goals, process notes, screenshots, results, and the skills behind each piece of work.

Try Radio
Radio Server Checking...

Resume

LeRoy Scott

Download Resume
Indiana, United States 1 (260) 358-7551 [email protected]

Professional Summary

Hands-on technical support professional with experience installing, configuring, and troubleshooting systems used in emergency communications, field deployments, and aviation electronics environments. Comfortable working with workstations, network connectivity, telecom-adjacent systems, secure communications equipment, and hardware/software issues that require careful troubleshooting.

Strongest in practical field support, documentation, client coordination, preventative maintenance, and learning unfamiliar systems quickly. Building deeper skills in networking, Windows Server, Active Directory, cloud services, and security tools while bringing steady operational experience from high-pressure, reliability-focused work.

Networking Exposure

Working familiarity with TCP/IP, DNS, DHCP, VPN access, routing, switching, and connectivity troubleshooting.

Systems Support

Hands-on support exposure with Windows Server environments, Active Directory, Group Policy, and workstation setup.

Cloud and Security Familiarity

Basic working knowledge of Microsoft 365, Azure concepts, Fortinet, SonicWall, Cisco, and Meraki environments.

Operations Tooling

Exposure to VMware, ServiceNow, ConnectWise, Kaseya, incident workflows, and field deployment support processes.

Professional Experience

Field Technician

INDIGITAL | Indiana, United States

Jan 2025 - Present
  • Installed and configured emergency call center infrastructure, including network systems and workstations.
  • Supported deployment of mission-critical 911 systems with a focus on high availability.
  • Troubleshot network connectivity, system configuration, routing, switching, and telecom issues.
  • Collaborated with teams and clients to establish reliable deployment environments.

Aviation Electronics Technician

U.S. Navy | Florida, United States

Dec 2019 - Oct 2022
  • Maintained flight, radar, acoustic, and communications systems for MH-60R helicopters.
  • Diagnosed and repaired complex computing and electronic systems.
  • Performed preventative maintenance to reduce failures in high-pressure operational environments.
  • Worked with secure communications systems under strict reliability and process standards.
  • Completed two operational deployments supporting aviation electronics maintenance and shipboard mission readiness.
  • Deployed aboard USS Paul Ignatius (DDG-117), May 2020 - Nov 2020.
  • Deployed aboard USS Wasp (LHD-1), Aug 2021 - Sep 2021.
Colony Radio desktop client Channels tab with device selectors, channel frequencies, volume controls, and scan toggles
Desktop channel control
Colony Radio desktop client Connection tab showing server IP, port, auto connect, and signed update controls
Connection and updates
Colony Radio desktop client Encryption tab showing key request controls and stored encryption key table
Channel encryption tools
Radio Server admin tool showing live server snapshot, diagnostics buttons, and connected client table
Server/admin dashboard
Colony Radio handset overlay with PTT button, channel lamps, LCD frequency display, scan button, keypad, and volume knob
Radio handset overlay
Colony Radio message tablet overlay showing a latest message on frequency 100.0 MHz
Message tablet overlay

Featured Work

Radio Project

A multi-application radio communication suite built around real-time UDP voice, text chat, server administration, release packaging, and shared regression testing. The project is organized as a Windows desktop client, a unified server/admin tool, an Android prototype, and an umbrella integration project for shared protocol work.

Python Tk Desktop UI UDP Networking Opus Audio AES-GCM SQLite Android Prototype

Desktop Client Feature Detail

01 Connection and Presence Registers identity, version, channel state, and key readiness with the UDP server.

The client sends a structured registration payload instead of only opening a socket. That gives the server enough metadata to track client identity, supported protocol version, tuned channels, monitored channels, loopback state, and encryption readiness.

Important source lines
# udp_client.py
def _send_register(self) -> None:
    data = {
        "nick": self.nick,
        "net": self.net,
        "ssrc": self.ssrc,
        "protocol_version": int(PROTOCOL_VERSION),
        "min_protocol_version": int(PROTOCOL_MIN_VERSION),
        "client_version": self.client_version,
        "device_id": getattr(self, "device_id", None),
        "channels": self.channels,
        "monitor": self.monitor,
        "active_tx": self.active_tx,
    }
    data.update(self._keys_ready_payload())
    msg = pack_hdr(VER, MT_CTRL, self.seq.next(), now_ts48(), self.ssrc) + ctrl_hdr + b
    self.sock.sendto(msg, self.server)
02 Radio Channel Controls Maintains A-D channel frequencies, active transmit channel, and scan/monitor state.

The desktop client keeps a local mirror of the four radio channels, then normalizes and sends channel changes to the server as a dedicated channel-update control packet. Scan state is represented as monitored channels, which lets the server route audio for active and scanned frequencies.

Important source lines
# udp_client.py
self.channels = {
    "A": {"freq": 100.0, "mode": "PTT"},
    "B": {"freq": 101.0, "mode": "PTT"},
    "C": {"freq": 102.0, "mode": "PTT"},
    "D": {"freq": 111.1, "mode": "PTT"},
}
self.active_tx = "A"
self.monitor = {"A": True, "B": False, "C": False, "D": False}

payload_obj = {
    "active": int(active),
    "freqs": freqs_list,
    "scan": bool(any(sc)),
    "scan_channels": sc,
    "monitor_channels": sc,
}
ctrl_hdr = struct.pack("!BH", CTRL_CHAN_UPD, len(payload) & 0xFFFF)
03 Voice Audio Pipeline PTT-gated 48 kHz audio uses Opus first, PCM fallback, and optional AES-GCM encryption.

Audio is intentionally gated by push-to-talk before encoding. The transport prefers Opus for voice, falls back to PCM16 when Opus is unavailable, and can wrap the payload with AES-GCM when the selected network requires encrypted audio.

Important source lines
# udp_client.py
def send_audio(self, pcm_float32) -> None:
    if not self._ptt:
        return

    data = self.enc.encode_float32(buf) if self.enc.enabled else b""
    if not data:
        pcm16 = (buf * 32767.0).astype("<i2")
        data = pcm16.tobytes()
        flags |= AUDIO_FLAG_CODEC_PCM | AUDIO_FLAG_PCM_I16

    if self._audio_require_encrypt:
        key_id, key_bytes = self._select_tx_key()
        nonce = os.urandom(_AUDIO_NONCE_LEN)
        ciphertext = crypto.encrypt(nonce, data, _audio_aad(self.ssrc, seq_num))
        data = struct.pack("!I", int(key_id) & 0xFFFFFFFF) + nonce + ciphertext
04 Transport Health Tracks RTT, packet-loss estimates, late frames, queue depth, and active RX channels.

The client exposes link metrics from the UDP layer and receive-side mixer metrics from the jitter buffer. This makes connection quality visible in the UI and gives diagnostics enough data to explain audio gaps, packet loss, late frames, or queue buildup.

Important source lines
# udp_client.py
def get_link_metrics(self) -> dict:
    total = self._audio_rx_packets + self._audio_rx_est_lost
    loss_pct = (float(self._audio_rx_est_lost) / float(total) * 100.0) if total > 0 else 0.0
    return {
        "rtt_ms": self._rtt_ms,
        "packet_loss_est_pct": loss_pct,
        "late_frames": self._audio_rx_late,
        "tx_packets": self._audio_tx_packets,
        "active_key_net": self.active_key_net(),
    }

# app/rx_audio.py
def snapshot(self) -> dict:
    return {"queue_depth": int(total_depth), "late_frames": int(self._late_frames),
            "active_channels": sorted(set(active_channels))}
05 Chat and Overlay Frequency chat syncs monitored channels and sends messages through the same UDP control path.

The message tab derives current and monitored frequencies, keeps subscription state synchronized with the server, and sends messages as frequency-scoped control payloads. Incoming chat can also feed the overlay toast system so radio messages appear outside the main window.

Important source lines
# app/app.py
udp.send_frequency_chat({
    "action": "sync",
    "frequencies": subs,
    "selected_frequency": selected_for_sync,
})

udp.send_frequency_chat({
    "action": "send",
    "frequency": freq,
    "text": text,
})

# udp_client.py
ctrl_hdr = struct.pack("!BH", CTRL_FREQ_CHAT, len(payload) & 0xFFFF)
msg = pack_hdr(VER, MT_CTRL, self.seq.next(), now_ts48(), self.ssrc) + ctrl_hdr + payload
06 Keys, Updates, and Diagnostics Handles key requests, signed update checks, mic checks, test tones, and trace captures.

Security and support tooling are part of the client instead of separate scripts. The client can request channel keys, verify signed update manifests before accepting server offers, and run admin-triggered diagnostics that report microphone levels, playback tests, and short link traces.

Important source lines
# udp_client.py
def send_key_request(self, payload_obj: dict) -> None:
    self._send_ctrl_json(CTRL_KEY_REQUEST, dict(payload_obj))

# app/app.py
ok, err, candidate_payload = verify_signed_update_manifest(signed_manifest, public_key)
if int(payload.get("protocol_version") or PROTOCOL_VERSION) < int(PROTOCOL_MIN_VERSION):
    return False, "protocol_too_old", payload

def _diagnostic_capture_trace(self, payload: dict) -> tuple[str, dict]:
    start = self._diagnostic_runtime_snapshot()
    time.sleep(duration_s)
    end = self._diagnostic_runtime_snapshot()
    details = {"rx_packets_delta": end_link.get("rx_packets", 0) - start_link.get("rx_packets", 0),
               "packet_loss_est_pct": round(float(end_link.get("packet_loss_est_pct", 0.0)), 2)}

Server and Admin Tool Feature Detail

01 Unified Admin Workflow One admin surface can relaunch the server process and reconnect to local or LAN targets.

The admin app owns the operator workflow. For local restarts it resolves a fresh launch command, starts a new admin/server pair, and keeps the server side hidden from the desktop.

cmd, cwd = self._resolve_admin_launch_command(auto_restart_server=True)
env = os.environ.copy()
env.setdefault("PYTHONUNBUFFERED", "1")
creationflags = int(getattr(subprocess, "CREATE_NO_WINDOW", 0))
subprocess.Popen(cmd, cwd=cwd, env=env, stdin=subprocess.DEVNULL,
                 stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
                 creationflags=creationflags)
02 UDP Radio Backend Control packets drive registration, PTT, channel updates, chat, and server-side actions.

The server parses the compact control header, decodes JSON payloads where needed, and branches by protocol code for registration, channel updates, and other radio control traffic.

code, length = struct.unpack("!BH", payload[:3])
body = payload[3 : 3 + length] if len(payload) >= 3 + length else b""
self._log(f"[CTRL][RX] addr={addr} ssrc={ssrc} code={code} len={len(body)}")

if code == CTRL_REGISTER:
    info = _decode_json(body)
elif code == CTRL_CHAN_UPD:
    # Apply channel/frequency updates from the client.
03 Session and Frequency Routing Desktop clients and browser clients share the same backend session model.

The server embeds the web gateway in the same process and points it back to loopback UDP, so browser sessions register like radio clients without exposing an extra backend service.

gateway_udp_host = "127.0.0.1"
self.web_gateway = RadioWebGateway(
    WEB_GATEWAY_HOST,
    WEB_GATEWAY_PORT,
    gateway_udp_host,
    self.port,
)
self.web_gateway.start()
04 Security and Updates Admin controls and release uploads are signed before the server accepts them.

Admin commands are wrapped in signed payloads. Update uploads also include a signed manifest with file size, SHA-256, app version, and protocol compatibility fields.

signed = build_signed_admin_control(
    private_key,
    int(code),
    body_obj,
    admin_id=f"{socket.gethostname()}:{os.getpid()}",
)
signed_manifest = build_signed_update_manifest(
    self._load_admin_signing_key(), name=name,
    size=len(data), sha256=sha256_hex(data),
    protocol_version=PROTOCOL_VERSION,
)
05 Chat Persistence Frequency chat is stored in SQLite and mirrored to active sessions.

Chat is normalized by frequency, held in bounded memory for active users, and inserted into SQLite so frequency history survives server restarts.

bucket = self._chat_history.get(freq)
if bucket is None:
    bucket = deque(maxlen=CHAT_HISTORY_LIMIT)
    self._chat_history[freq] = bucket
record = _normalize_chat_message_record(message, default_frequency=freq)
bucket.append(record)
if not self._chat_store.append_message(record):
    self._increment_counter("chat_store_errors")
INSERT OR REPLACE INTO messages
(id, frequency, ts, sender, sender_kind, sender_client_id,
 sender_ssrc, text, attachment_json)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
06 Admin Visibility and Diagnostics The admin console reads live server snapshots, gateway health, and diagnostic counters.

Status snapshots combine server state with the embedded web gateway snapshot, so the admin UI can show uptime, connected rows, gateway clients, and recent operational activity.

def _web_gateway_snapshot(self) -> dict:
    gateway = getattr(self, "web_gateway", None)
    if gateway is None:
        return {"enabled": bool(WEB_GATEWAY_ENABLED), "running": False}
    return gateway.snapshot()

def _admin_status_payload(self, requester_addr=None) -> dict:
    uptime_s = max(0, int(time.time() - float(self._started_at)))
    row_count = len(self.mgr.presence_snapshot())

Radio Web Emulator Scaffold

The browser emulator has been moved to its own page so the client application mock, radio overlay, and message overlay have more room. The current page explains the plan; the dedicated emulator page holds the scaffold.

Try Radio
01 Purpose The website gives visitors a hands-on radio client without requiring the desktop app.

The emulator is connected through a WebSocket bridge. Browser actions are sent as small JSON messages, and the gateway translates them into the UDP radio protocol.

function sendBridgeMessage(payload) {
  const socket = state.bridge.socket;
  if (!socket || socket.readyState !== WebSocket.OPEN) {
    return false;
  }
  socket.send(JSON.stringify(payload));
  return true;
}
02 Browser Radio Bridge The gateway accepts connect, channel, PTT, audio, chat, and ping messages.

The server-side gateway keeps the browser contract explicit. It registers a web radio session, updates channels, forwards push-to-talk, and bridges browser PCM audio into radio packets.

if msg_type == "connect":
    await self.connect(message)
elif msg_type == "channels":
    self.update_channels(message)
    self.send_channel_update()
elif msg_type == "ptt":
    self.send_ctrl_json(CTRL_PTT, {"ptt": bool(message.get("active"))})
elif msg_type == "audio_pcm16":
    self.send_audio_pcm16(message)
flags = AUDIO_FLAG_PTT | AUDIO_FLAG_CODEC_PCM | AUDIO_FLAG_PCM_I16
body = struct.pack("!BH", flags, len(data) & 0xFFFF) + data
self.send_packet(pack_hdr(VER, MT_AUDIO, self.seq.next(),
                          now_ts48(), self.ssrc) + body)
03 Current Interactive State The emulator now has real controls for channels, push-to-talk, audio, chat, and overlays.

The page updates channel state immediately, pushes changes to the bridge, and requests microphone audio only while transmitting. Received PCM is decoded in the browser and played through the Web Audio API.

sendBridgeMessage({
  type: "audio_pcm16",
  sampleRate: state.audio.txSampleRate,
  data: bytesToBase64(floatSamplesToPcm16Bytes(frame)),
});
if (state.bridge.realMode) {
  sendBridgeMessage({ type: "ptt", active: state.transmitting });
  if (state.transmitting) {
    startMicStream();
  } else {
    setMicTrackEnabled(false);
    clearTransmitBuffer();
  }
}

Mobile Prototype Feature Detail

01

Protocol-Compatible Android Client

The Android prototype preserves the same on-air protocol as the desktop client so it can communicate with the existing radio server.

02

Mobile Voice Transport

Supports UDP control and audio packets, Opus 48 kHz mono voice audio, AES-256-GCM encryption with key IDs, basic A-D channel selection, and hold-to-talk push-to-talk input.

03

Prototype Scope

The current mobile version is a proof of concept, with known gaps around channel editing, in-app update handling, and background service behavior.

Project Goal
Create a practical radio-style communication stack for coordinated multiplayer use.
What It Demonstrates
Networking, audio handling, encryption, desktop tooling, mobile prototyping, packaging, and automated regression coverage.
Back to Radio Project

Radio Web Emulator

Try Radio

This is the dedicated scaffold for the future browser emulator. It separates the client application mock from the radio overlay mock so there is room to build controls, state, and interaction logic in later passes.

Quick Start

Connect to the radio server at 68.54.128.30 on port 8765, then allow microphone access when the browser asks.

Transmit

Use CapsLock as the default push-to-talk key, or tap the radio overlay PTT button to toggle transmit on phones and tablets.

Chat Frequency

Channel A starts on 100.0 MHz and is the default place to chat. Any matching frequency can work if other users are tuned to it.

Public Voice

Public browser transmit needs HTTPS for microphone access and WSS for the radio gateway. A public HTTP link can load the page, but most browsers will block mic audio.

Colony Radio Clean voice, channel, and text control in one comms console.
Client / NET-1 Offline
Occupancy: Demo Server / Talker: None
Occupancy: Scout-2 / Talker: None
Occupancy: Empty / Talker: None
Occupancy: Global Relay / Talker: None
Active: A (100.0 MHz)
Current Frequencies
Monitored Frequencies
Frequency Chat 100.0 MHz monitored
Identity

Your Username is also your Device ID for keys, auth, and client identity. Use letters, numbers, dash, underscore, or period.

Steam ID / SSRC

Optional numeric SteamID64. When set, it overrides your SSRC on connect and is reported to the server as your client ID. Leave blank to use a random SSRC.

Profile ready.
Server
Browser voice connects to the Radio Server web gateway on port 8781.

Enter the IP/Port of the UDP server machine. The browser connects to that server's WebSocket gateway, and the gateway registers this web client with the same UDP radio session system used by the desktop client.

RTT -- Loss -- Jitter --
Client Update

Check this server for a signed client update. Downloads are verified, then saved to your Windows Downloads folder.

No update check has been run. Update status idle.
0%
Request Channel Encryption

Request access for a specific encrypted frequency. If the server has not marked that frequency for encryption, the request will be rejected.

Keystore loaded for NET-1.
Stored Encryption Keys
Key ID Channel Net Start End Added
Appearance

Dark mode now uses the cleaner modern shell. Light mode stays available as a softer fallback. The radio overlay keeps its own look.

Changes apply instantly and persist to config_user.json.
Sound Effects

Adjust key up/down and channel switch sounds. 0% mutes, 50% is normal loudness, up to 200% boost.

Effects volume: 6% (12% of normal)
Overlay Scale

Adjust the radio overlay and message overlay sizes without opening the overlay context menu.

Push-to-Talk
PTT: RELEASED
PTT Combo(s): CapsLock
Channel Keybinds
Next: F7 Prev: F6 Vol +: F8 Vol -: F9 Overlay: F10

Direct channel keybinds:

A: Unbound B: Unbound C: Unbound D: Unbound
Input

Poll joystick/gamepad buttons for keybinds (PTT, channel hotkeys). Disable if a noisy device keeps firing inputs.

Use this tab to validate the current client pipeline: selected mic capture, live input tokens, local RX simulation, and server loopback.

Runtime Status
Connection Offline Target 127.0.0.1:50000 Last Activity Idle
Link Health
RTT --    Loss --    Jitter --
Input Tools
Mic Input Level
Mic: 0%
Input Debugger Window: closed

Shows the keyboard and mouse tokens currently seen by the live keybind path.

Overlay UI Layout Status: closed

Tunes the message overlay boxes, message TX/RX lamps, and radio overlay display/lamp regions.

RX / Loopback

Active Channel Debug drives the same RX channel state used by the overlay and status text. Optional local clip playback loads from Audio\Test, Audio\Debug, or RADIO_DEBUG_AUDIO_DIR.

Clip playback unavailable: demo visual mode.
Status: idle

Use this while connected to verify the real network RX path instead of dry local sidetone.

Live Diagnostics

                  
                
Colony Radio handheld overlay UI
CH A 100.0 FREQ 100.0 VOL 80% SCAN A
Colony Radio message overlay UI

LPM launcher showing project index, task tree, and task editor

Featured Work

Project Manager

LPM, short for LeRoy's Project Manager, is a local workspace launcher and project record system built to keep long-running development work organized across many folders, tools, and repeatable work sessions.

What It Solves
It keeps project overview, memory, tasks, questions, bugs, guides, and work logs in one predictable structure.
How It Works
Each project gets structured JSON sources and readable markdown summaries that update together.
Current Use
This portfolio, the Radio suite, and game projects are managed through LPM's project directories and session launchers.

Project Index

Organizes work into directory buckets like DOD, Radio, SE1, SE2, Work, and MISC, with tools to create, move, archive, and open projects.

Project Records

Maintains overview, memory, reusable guides, questions, bugs, task progress, and daily work logs for every active project.

Session Launcher

Starts or resumes workspace and project-scoped work sessions with the right startup instructions and context files.

Backup Workflow

Includes Goodnight backup and Restore flows that package selected projects, upload to a NAS target, and keep logs for recovery.

NAS Server Deployment

Runs a lightweight backup server on a second computer with signed requests, optional HTTPS, device folders, and dated snapshot archives.

How LPM Supports Real Project Work

LPM is built around continuity. A project can be paused for days, reopened later, and still give the next work session a clear picture of what the project is, what has changed, what is blocked, and what should happen next.

01 Structured Project Memory Each project gets paired JSON source files and readable generated markdown records.

LPM standardizes the Codex record folder so every project has the same memory, overview, guide, task, question, bug, and hours locations. The launcher writes JSON as the durable source and regenerates markdown for fast human review at session start.

Important source lines
def overview_json_file(project_dir: Path) -> Path:
    return project_codex_dir(project_dir) / "OVERVIEW.json"

def progress_json_file(project_dir: Path) -> Path:
    return project_codex_dir(project_dir) / "PROGRESS.json"

def questions_json_file(project_dir: Path) -> Path:
    return project_codex_dir(project_dir) / "QUESTIONS.json"

def hours_dir(project_dir: Path) -> Path:
    return project_codex_dir(project_dir) / "Hours"
def write_progress_files(project_dir: Path, data: dict[str, Any]) -> None:
    progress_json_file(project_dir).write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
    progress_markdown_file(project_dir).write_text(render_progress_markdown(data), encoding="utf-8")
02 Nested Tasks and Subgoals The task tree supports top-level goals, nested subgoals, statuses, notes, and exports.

LPM keeps task hierarchy in `PROGRESS.json` and renders the same structure into readable markdown. In the GUI, each task path is tracked in the tree so nested subgoals can be selected, edited, saved, exported, or continued in later work sessions.

Important source lines
def render_goal_bullets(subgoals: list[dict[str, Any]], prefix: str, depth: int) -> list[str]:
    lines = []
    indent = "  " * depth
    for index, goal in enumerate(subgoals, start=1):
        number_text = f"{prefix}.{index}"
        lines.append(f"{indent}- {number_text}. {goal.get('title') or 'Untitled Task'} [{goal.get('status') or 'Not Started'}]")
        nested = goal.get("subgoals") or []
        if nested:
            lines.extend(render_goal_bullets(nested, number_text, depth + 1))
def add_subgoal(self) -> None:
    path = self.selected_goal_path()
    parent_goal = self.goal_by_path(path)
    subgoals = parent_goal.setdefault("subgoals", [])
    subgoals.append(new_goal(title))
    self.current_progress["last_updated"] = now_iso()
    self.commit_progress(selected_path=path + (len(subgoals) - 1,))
03 Questions, Bugs, and Guides Decision history, bug tracking, and reusable how-to notes stay with the project.

The launcher treats questions, bugs, and guides as first-class project records instead of loose notes. Questions keep status, answer, and notes fields; bugs keep a stable number plus resolution notes; guides keep repeatable steps and follow-up reminders.

Important source lines
def render_questions_markdown(data: dict[str, Any]) -> str:
    lines = [
        f"# {data.get('project_name') or 'Untitled Project'} Questions",
        "> This file is generated from `Codex\\QUESTIONS.json` by LPM - LeRoy's Project Manager.",
        "## Questions",
    ]
    for index, question in enumerate(data.get("questions") or [], start=1):
        lines.extend([
            f"### {index}. {summarize_question(question)}",
            f"- Status: {question.get('status') or QUESTION_STATUS_VALUES[0]}",
            "#### Answer",
            question.get("answer") or "Not answered yet.",
        ])
def render_guides_markdown(data: dict[str, Any]) -> str:
    for index, guide in enumerate(data.get("guides") or [], start=1):
        lines.extend([
            f"### {index}. {summarize_guide(guide)}",
            "#### Guide",
            guide.get("guide") or "No guide steps recorded yet.",
            "#### Notes",
            guide.get("notes") or "No notes recorded yet.",
        ])
04 Hours and Work Logs Completed work is logged by day with task summaries, notes, and generated markdown.

Launcher-started sessions inherit the hours logger path. When work is completed, the helper appends a timestamped entry into `Codex\Hours\YYYY-MM-DD.json` and regenerates the matching markdown report that the Hours tab can display or export.

Important source lines
def write_day(project_dir: Path, data: dict[str, Any]) -> Path:
    hours_dir = project_dir / "Codex" / "Hours"
    hours_dir.mkdir(parents=True, exist_ok=True)
    json_path = hours_dir / f"{day}.json"
    markdown_path = hours_dir / f"{day}.md"
    json_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
    markdown_path.write_text(render_markdown(data), encoding="utf-8")
data.setdefault("entries", []).append({
    "id": uuid4().hex,
    "task": task,
    "notes": args.notes.strip(),
    "started_at": started_at,
    "ended_at": ended_at,
    "duration_minutes": args.duration_minutes.strip(),
})
05 Workspace Operations LPM manages real workspace folders and launches focused Codex sessions from the selected project.

The launcher works against real project directories under the workspace root. It can start a broad LPM workspace session, start a fresh project-scoped session, or resume the last project session after persisting the current project state.

Important source lines
def launch_workspace_session(self) -> None:
    self.launch_codex(["new"])

def launch_project_session(self) -> None:
    if not self._persist_current_project("Continue project"):
        return
    self.launch_codex(["new", "--project", project_key(self.current_project_dir)])

def resume_project_session(self) -> None:
    if not self._persist_current_project("Resume project"):
        return
    self.launch_codex(["resume-last", "--project", project_key(self.current_project_dir)])
subprocess.Popen(
    ["cmd.exe", "/k", "call", str(LAUNCH_SCRIPT), *arguments],
    cwd=str(WORKSPACE),
    creationflags=creation_flags,
)
06 Goodnight and Restore Selected projects can be backed up to a NAS snapshot and restored later with logs and progress tracking.

The Goodnight flow validates NAS settings, writes a generated NAS client config, opens progress tracking, and runs the bundled NAS client in a worker process. Restore uses the same NAS settings to list devices, dated runs, directories, and project archives before pulling selected projects back into the workspace.

Important source lines
def goodnight_backup(self) -> None:
    if self.goodnight_backup_running:
        self.status_var.set("Goodnight backup is already running.")
        return
    if self.restore_running:
        self.status_var.set("Restore is already running. Wait for it to finish before starting Goodnight.")
        return
    config_data = self._build_goodnight_backup_config()
    validation_error = self._validate_goodnight_backup_config(config_data)
process = subprocess.Popen(
    [sys.executable, "-m", "lpm_nas.client",
     "--config", str(NAS_CLIENT_CONFIG_FILE),
     "--log-file", str(client_log_path),
     "backup-workspace"],
    cwd=str(CODEX_DIR),
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
)

NAS Server Deployment

The NAS server is a companion deployment for LPM that runs on a second computer or backup machine. The Project Manager workstation sends selected projects through the bundled NAS client, while the NAS server stores dated snapshots and exposes restore listings back to the launcher.

01 Backup Machine Role The server owns the storage root and groups backup runs by client device.

The NAS service runs on the backup PC and writes project archives under a configured storage folder. Each workstation has its own device folder, and every Goodnight run creates a dated snapshot folder such as `2026-04-23` or `2026-04-23_2`.

Important source lines
def create_backup_run_folder(self, device_name: str | None = None) -> str:
    run_root = self.storage_root if not device_name else self.storage_root / safe_device_name(device_name)
    run_root.mkdir(parents=True, exist_ok=True)
    base_name = datetime.now().strftime("%Y-%m-%d")
    run_name = base_name
    suffix = 2
    while (run_root / run_name).exists():
        run_name = f"{base_name}_{suffix}"
        suffix += 1
    (run_root / run_name).mkdir(parents=True, exist_ok=False)
    return run_name
def list_backup_devices(self) -> list[dict[str, Any]]:
    for child in self.storage_root.iterdir():
        if not child.is_dir():
            continue
        if not any(grandchild.is_dir() and is_backup_run_name(grandchild.name) for grandchild in child.iterdir()):
            continue
        devices.append({"name": child.name, "modified_time": int(child.stat().st_mtime)})
02 Deployment and TLS The NAS server can run from a command line or desktop app, with optional HTTPS certificates.

Deployment can be done directly with the Python module or through the desktop NAS server app. The server accepts host, port, storage root, shared secret, and optional certificate/key files. HTTPS is used when both certificate and key paths are configured.

Important source lines
def resolve_server_settings(args: argparse.Namespace) -> dict[str, Any]:
    config = load_config(args.config)
    host = require_string(coalesce_value(args.host, config.get("host"), "0.0.0.0"), "host")
    port = int(coalesce_value(args.port, config.get("port"), 8785))
    storage_root = require_string(coalesce_value(args.storage_root, config.get("storage_root")), "storage_root")
    shared_secret = require_string(coalesce_value(args.token, config.get("shared_secret") or config.get("token")), "token")
    cert_file = str(coalesce_value(args.cert_file, config.get("cert_file"), "") or "").strip()
    key_file = str(coalesce_value(args.key_file, config.get("key_file"), "") or "").strip()
if tls_enabled:
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    context.minimum_version = ssl.TLSVersion.TLSv1_2
    context.load_cert_chain(certfile=normalized_cert, keyfile=normalized_key)
    server.socket = context.wrap_socket(server.socket, server_side=True)
03 Signed Backup API Requests are signed, replay-resistant, and checked against uploaded content hashes.

The server rejects unsigned or stale requests before handling uploads or restore queries. Uploads are streamed to a temporary file, hashed while receiving, and only accepted when the received hash matches the signed digest.

Important source lines
valid, error_message = self.server.validate_auth(
    method=self.command,
    request_target=self.path,
    timestamp=timestamp,
    nonce=nonce,
    content_sha256=content_sha256,
    signature=signature,
)
if not valid:
    self.send_error(HTTPStatus.UNAUTHORIZED, error_message)
    return False
with temp_path.open("wb") as handle:
    remaining = content_length
    while remaining > 0:
        chunk = self.rfile.read(min(1024 * 1024, remaining))
        handle.write(chunk)
        digest.update(chunk)
        remaining -= len(chunk)

actual_sha256 = digest.hexdigest()
if actual_sha256 != declared_sha256:
    self.send_error(HTTPStatus.BAD_REQUEST, "Uploaded file digest mismatch")
04 Restore Listings The launcher can browse devices, backup runs, directories, archives, and downloadable ZIPs.

Restore is built around discoverable server endpoints. The NAS server lists available devices, dated runs, workspace directories, and project ZIP archives, then streams the selected archive back as a normal ZIP download for restore into the active workspace.

Important source lines
if parsed.path == "/backup-devices":
    self._handle_backup_devices()
    return
if parsed.path == "/backup-runs":
    self._handle_backup_runs(parse_qs(parsed.query))
    return
if parsed.path == "/backup-archives":
    self._handle_backup_archives(parse_qs(parsed.query))
    return
if parsed.path == "/download-backup":
    self._handle_download_backup(parse_qs(parsed.query))
    return
self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", "application/zip")
self.send_header("Content-Length", str(stat.st_size))
self.send_header("Content-Disposition", f'attachment; filename="{archive_path.name}"')
self.end_headers()
with archive_path.open("rb") as handle:
    while True:
        chunk = handle.read(1024 * 1024)
        if not chunk:
            break
        self.wfile.write(chunk)

Featured Work

Dawn of Demons

Dawn of Demons is a multiplayer sci-fi RTS about commanding fleets, managing ship logistics, sustaining crews, and fighting across star systems where internal ship design directly affects battlefield survival.

Genre
PC sci-fi RTS with fleet combat, economy, production, and resource logistics.
Lore
A fragile peace between Sol and Alicestine begins to collapse as rogue factions reignite an interstellar war.
Engine
Unity 6 baseline using Entities, Netcode for Entities, Entities Graphics, and dedicated-server planning.
Current Slice
Strategic ship scene, imported ship designs, faction teams, spawn tools, inventory, and ship-system simulation.
Dawn of Demons strategic scene with ships, solar scale debug panel, and selection UI

Current Slice Video

Strategic Ship Slice Demo

A video look at the current Dawn of Demons playable slice, showing the strategic ship scene, imported designs, faction tooling, inventory work, and live ship-system simulation.

Fleet Command

Players command ships at strategic scale, with selection panels, faction ownership, team visibility, and planned waypoint-based movement orders.

Ship Systems

Ships expose fuel, reactor power, battery capacitors, radar, data link, life support, crew assignment, and compartment power state.

Inventory and Logistics

Cargo holds, ammo racks, hangars, fuel tanks, reactors, life support, and lockers use specialized inventory rules and storage capacities.

Production Roadmap

The prototype scope includes mining stations, cargo ships, team stockpiles, tiered shipyards, The Rail, and flagship destruction as a win condition.

What Is Built Right Now

Dawn of Demons is already past a static concept stage. The current Unity project contains a working strategic scene, imported ship assets, runtime ship data, interactive debug tooling, and a growing ship-management layer.

01 Strategic Solar Scene Solar-scale command view, selection tools, debug readouts, and ship state displays.

The current playable slice focuses on a strategic space view where ships can be selected, inspected, filtered, and tested in a large-scale solar environment. It is built as the command layer for fleet decisions rather than a close-range cockpit view.

Implemented examples
  • Selectable ships expose live identity text, ownership, fuel bars, speed, current sphere-of-influence context, and tighter hover/click detection around the rendered ship.
  • The Debug panel can spawn configured ships, spawn inventory items, swap a selected ship between SOL and ROH, and switch between God, SOL, and ROH perspectives.
  • Faction perspective filtering hides opposing ships from normal player views while admin/God view keeps both teams visible for testing.
  • Imported ship visuals, selection colliders, waypoint lines, and camera focus now share the smaller strategic presentation scale so the scene reads better at solar-map distances.
  • High-level bug trace hooks can record camera zoom and strategic line-width behavior, making scene readability issues easier to diagnose during playtests.
02 Imported Ship Pipeline Ship designs move from authored layouts into Unity with runtime metadata.

Dawn of Demons is built around authored ship designs becoming usable runtime objects. Imported ships carry gameplay data forward so a design is not just a model; it becomes a configured vessel with identity, class, mass, hull, and systems.

Implemented examples
  • .shipasset imports carry tare weight and one-g burn rate through the importer, ship-definition UI, spawn request, and runtime flight-stat path.
  • Imported team and ship type metadata now drive SOL/ROH assignment and class selection instead of relying only on legacy name-keyword matching.
  • Configured ship spawning preserves the selected team through saved designs, spawn snapshots, and server spawn RPCs so the live ship gets the intended faction and home system.
  • Ship identity is split into hull number, vessel name, class name, hull type, and generated serial number so a spawned ship can be tracked separately from its reusable class template.
  • The ship detail UI resolves ownership from faction/team data and can show admin owner labels such as SOL ADMIN or ROH ADMIN.
03 Volume-Based Ship Design Internal volume determines what a ship can carry, power, protect, and sustain.

The design goal is that ship internals matter. Assigned volume drives practical capability instead of acting as decoration, so cargo, power, crew space, weapons support, and survivability all come from the physical plan of the ship.

Implemented examples
  • Imported volumes create live compartment/system stats for cargo holds, fuel tanks, reactors, batteries, thermal sinks, radar, life support, hangars, CIC, ECM, armory, infirmary, and other ship systems.
  • The Compartments tab builds a three-column grid of runtime-managed systems, with expandable full-width tiles, inline power buttons, and an all-compartments power toggle.
  • Power demand is calculated from real assigned volume: radar, life support, ammo racks, damage-control lockers, hangars, CIC, central computer, ECM, and other systems all add live load.
  • Crew assignment is authoritative per compartment, with minimum staffing rules and runtime effects when a compartment is understaffed.
  • Fuel tanks, battery capacitors, and fusion reactors already use staffing bonuses that alter capacity or output without creating free resources.
  • Players can double-click compartment headers to rename individual compartments, and those names synchronize through a dedicated rename RPC.
04 Inventory and Sustainment Cargo, ammunition, fuel, crew support, and ship stores are tracked as ship resources.

The logistics layer is being built so ships need to be supplied, maintained, and managed. Inventory is not only a UI panel; it connects to ship endurance, ammunition availability, crew survival, and the larger RTS economy.

Implemented examples
  • The ship inventory panel supports stack and grid workflows, left-side compartment lists, multi-open stack sections, per-ship tabs, and side-by-side same-team cross-deck transfers.
  • The Debug panel has a real item spawn browser with categories for raw resources, refined materials, alloys, components, ship parts, ammo, and biological items.
  • Spawned inventory items can be dragged into compatible grid slots, rotated with R, validated against compartment rules, and committed through authoritative server inventory state.
  • Fuel tanks can export Helium-3 into cargo as Helium-3 Storage Tank items, and those cargo tanks can be input back into selected fuel tanks with capacity clamping.
  • Ammo racks accept ammo-category items, damage-control lockers accept spare parts and fire bottles, reactors accept deuterium pellets, and life support accepts carbon scrubbers.
  • Life support tracks scrubber consumption, per-compartment quality, casualty risk, injured personnel, recovery, and casualty logs in the ship detail UI.
  • Stored craft such as the PDV and stealth drone bomber exist as inventory items with serial numbers, special icons, carried crew/marine counts, and early nested-inventory support.
05 SOL and ROH Factions Two faction framework with ownership, filtering, team swapping, and data-link rules.

The game already separates ships by faction so the strategic layer can test visibility, team control, and perspective. SOL and ROH are the current faction anchors for fleet identity and future campaign/system planning.

Implemented examples
  • The ship-spawn design flow can explicitly place configured ships on SOL or ROH, carrying that team into live server-side ship faction state.
  • SOL uses the existing blue faction identity tied to the Sol system, while ROH uses the orange faction identity tied to the AliceStine system direction.
  • The selected-ship Debug tab includes Swap Team for fast test setup, flipping faction, team, and home-system assignment together.
  • The perspective dropdown supports God view plus SOL and ROH player perspectives, with opposing ships hidden and normal focus/selection filtered by team.
  • Data Link owned-channel compatibility now requires both the same network owner and the same ship team, preventing admin visibility from becoming cross-team communication.
  • Cross-deck inventory candidate listing and execution stay same-team only, so logistics transfer rules already respect faction boundaries.
06 Multiplayer Foundation Dedicated-server RTS direction with staged scale testing for players and ships.

Dawn of Demons is being shaped around multiplayer from the start. The technical direction is a Unity dedicated-server RTS that can validate gameplay at small scale first, then grow toward larger fleet battles.

Implemented examples
  • The official Unity project exists under Source/DawnOfDemonsUnity and has been migrated from the SOL's Colony test-bed into the Dawn of Demons namespace and product identity.
  • Unity 6, Entities, Netcode for Entities, Entities Graphics, and Dedicated Server are the selected technical baseline for the official project.
  • Live ship systems already use authoritative RPC paths for spawning configured ships, moving inventory, spawning items, toggling compartment power, changing radar settings, renaming compartments, and assigning crew.
  • Runtime state is replicated through ship buffers/components for cargo, compartments, fuel tanks, reactors, radar, crew assignment, casualty logs, injured personnel, and faction/team state.
  • The first scale target is staged around two teams, two to twenty players, one dedicated server, and a twenty hertz server simulation before pushing toward one thousand active ships.
  • Inventory and UI performance work is already underway, including cargo segment coalescing, throttled inventory signatures, stack culling, and scratch-list reuse for dense inventories.
07 Lore and Setting A decade-long Sol and Alicestine war, militarized Rail gates, extremist factions, and people on both sides trying to survive the return of total war.

For over a decade, humanity has been locked in a brutal struggle between the ancient cradle of Sol and the distant frontier worlds of the Alicestine System. What began as political tension over resources, expansion, and independence slowly erupted into a devastating interstellar war that reshaped entire systems.

Though official peace now hangs by a thread, the scars of the conflict remain everywhere: shattered fleets drifting in forgotten orbits, militarized Rail gates standing watch over contested space, and generations raised knowing only war. To the people of Alicestine, Sol is a dying empire desperate to maintain control. To many within Sol, Alicestine is a rebellious frontier that grew too powerful to govern.

But peace is beginning to fail. Hidden within the fractured politics of Sol, extremist factions and rogue military groups have begun reigniting the conflict, launching attacks designed to drag both civilizations back into total war.

As tensions spiral across colonies, fleets, and border systems, the story follows soldiers, pilots, engineers, intelligence officers, civilians, and leaders on both sides of the divide, each struggling to survive a conflict far larger than themselves. Some fight for duty, some for revenge, some for survival, and others for the hope that humanity can still escape the cycle that has consumed it for generations.