From 831747ddb5886364707b696ac38cce8da333f15d Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sat, 27 Jun 2026 22:03:59 +0900 Subject: V1: preserve pre-refactor state (single-file main.c + harness docs) --- .dispatch/build-agent.md | 48 +++++ .dispatch/package-agent.md | 53 +++++ .dispatch/rules/contracts-are-h.md | 17 ++ .dispatch/rules/one-owner.md | 8 + .dispatch/rules/zero-warnings.md | 11 + AGENTS.md | 83 ++++++++ GLOSSARY.md | 49 +++++ ORCHESTRATOR.md | 403 +++++++++++++++++++++++++++++++++++++ bin/serve | 18 ++ notes/restructure-plan.md | 344 +++++++++++++++++++++++++++++++ src/layout_editor.c | 4 +- src/main.c | 8 +- study-player.cfg | 16 ++ tasks.md | 43 ++++ web/shell.html | 110 ++++++++++ 15 files changed, 1209 insertions(+), 6 deletions(-) create mode 100644 .dispatch/build-agent.md create mode 100644 .dispatch/package-agent.md create mode 100644 .dispatch/rules/contracts-are-h.md create mode 100644 .dispatch/rules/one-owner.md create mode 100644 .dispatch/rules/zero-warnings.md create mode 100644 AGENTS.md create mode 100644 GLOSSARY.md create mode 100644 ORCHESTRATOR.md create mode 100755 bin/serve create mode 100644 notes/restructure-plan.md create mode 100644 study-player.cfg create mode 100644 tasks.md create mode 100644 web/shell.html diff --git a/.dispatch/build-agent.md b/.dispatch/build-agent.md new file mode 100644 index 0000000..cbf21dd --- /dev/null +++ b/.dispatch/build-agent.md @@ -0,0 +1,48 @@ +# Build system agent brief + +You are the **build system owner-agent** for this raylib project. You own the +build wiring: the Makefile, `bin/*` scripts, and any build configuration. + +## Your scope +You MAY read ANY file in the project — `.h` headers, `.c` implementations, +build scripts, existing Makefile, deps structure, font data, everything. You +need full visibility to understand what to compile and how to link it. + +You MAY write ONLY: +- `Makefile` +- `bin/build`, `bin/build-web`, `bin/clean`, `bin/serve` + +You MUST NOT write to `src/*` or any other source files. + +## Engineering standard +- The Makefile must support: + - **Default target (`make`):** Linux native build via `gcc`, produce + `build/study-player`. Defines: `-DPLATFORM_DESKTOP -DPLATFORM_LINUX + -D_GLFW_X11`. Link: `-lm -lrt -ldl -lpthread -lX11`. + - **Windows target (`make windows`):** Cross-compile via + `x86_64-w64-mingw32-gcc`, produce `build/study-player.exe`. Defines: + `-DPLATFORM_DESKTOP -D_GLFW_WIN32`. Link: `-lgdi32 -lwinmm -lcomdlg32 + -lole32`. + - **Clean target (`make clean`):** remove `build/`. + - **Individual `.o` compilation:** each `src/*.c` → `build/.o` with + `-std=c99 -Wall -Wextra`. +- Raylib is built as a static library from `deps/raylib/src/*.c`. Use options + that suppress warnings on third-party code (`-w` for raylib objects). +- Font header generation: `build/font_data.h` is a prerequisite built by + running `xxd -i` on the font file in `resources/`. If no font file exists, + the build should still work (font_data.h just won't define `FONT_EMBEDDED`). +- The `bin/build-web` script builds for Web/WASM via `emcc` + `emar`. Keep it + functional. +- All SRCS should be `$(wildcard src/*.c)` so new modules auto-compile. +- Use `-j$(nproc)` in any make invocation inside scripts for parallelism. + +## Verification +1. `make clean && make -j$(nproc)` — exits 0, zero warnings from project code +2. `make windows -j$(nproc)` — exits 0, zero warnings from project code +3. `bin/build-web` still works (even if run separately) + +## Report +Write `reports/build-system.md`: +1. **Files touched** +2. **What you changed** (bullet list) +3. **Build result** for both Linux and Windows targets diff --git a/.dispatch/package-agent.md b/.dispatch/package-agent.md new file mode 100644 index 0000000..fecd7b8 --- /dev/null +++ b/.dispatch/package-agent.md @@ -0,0 +1,53 @@ +# Package owner-agent brief (C/Raylib) + +You are the **exclusive owner-agent** for a C module in this raylib project. + +## Your scope +You own the module's `.h` + `.c` pair. You may read and edit ONLY those two +files. No other agent may touch them. This project follows a **single-writer +rule**. + +## Visibility: contracts vs implementation +- You MAY read the `.h` header files of OTHER modules (their contracts). +- You MUST NOT read the `.c` implementation files of ANY other module. +- If you think you need a change in another module's `.h` contract, REPORT it + in your final report — do NOT edit it yourself. +- If you think you need to read another module's `.c` to understand its + behavior, STOP — the `.h` contract is underspecified. REPORT this. + +## Engineering standard +This is a C99 project. Your code must: +- Compile with `-Wall -Wextra` producing ZERO warnings. +- Use `#pragma once` as the include guard in every `.h` file. +- Put NO global mutable variables — all shared state through `PlayerState*`. +- Prefix public functions with your module name (`player_`, `study_`, `ui_`). +- Use `static` for module-internal helper functions. +- Mark pointer parameters `const` when the function does not mutate them. +- Use `raylib.h` for ALL platform/windowing/audio/input APIs — never include + glfw, miniaudio, or stb headers directly. +- Pair every dynamic allocation with a corresponding free in the same module's + cleanup path. No leaks. + +## Build +Run `make` from the repo root to build the full project. Your module's `.c` +file will be compiled to `.o` and linked into the final executable. + +## Verification +Before writing your report, you MUST: +1. Run `make` from the repo root. It must exit 0 with ZERO warnings. +2. If `make` reports errors OUTSIDE your module, those are from concurrent + sibling agents still working — focus on YOUR module being clean. + +## Report +After completing your work, write exactly one file: `reports/.md`: +1. **Files touched** (list paths) +2. **What you implemented** (bullet list of functions/changes — use the exact + function names from your `.h` contract) +3. **Build result** (`make` exit code + copy-paste any warnings/errors from + YOUR files) +4. **Contract gaps or issues** discovered (e.g. missing declarations in + another module's `.h`) +5. **Changes needed in other modules** (e.g. "`types.h` needs `MAX_FOO`") + +The orchestrator will read your report — not your `.c` file. Be precise about +what you built and what still needs attention. diff --git a/.dispatch/rules/contracts-are-h.md b/.dispatch/rules/contracts-are-h.md new file mode 100644 index 0000000..29f9ce3 --- /dev/null +++ b/.dispatch/rules/contracts-are-h.md @@ -0,0 +1,17 @@ +# Contracts are header files + +- The `.h` file IS the contract between modules. Other agents read ONLY your + `.h` — never your `.c`. +- Every `.h` must be **self-contained**: it includes all types it references. + A consumer should be able to `#include "your_module.h"` and nothing else. +- Prefer **forward declarations** over full includes when only a pointer is + needed. Example: `typedef struct PlayerState PlayerState;` avoids including + `types.h`. +- A `.h` file must NOT include any `.c` file. Ever. +- If you expose a function, its full signature (return type, name, parameter + types and names) must be in the `.h`. The documentation of what it DOES + (preconditions, postconditions, side effects) goes in a comment in the `.h` + — that is the contract's behavioral specification, not just its type + signature. +- If an agent NEEDS to read your `.c` to understand what your module does, + your `.h` contract is UNDESPECIFIED. Report this as a contract gap. diff --git a/.dispatch/rules/one-owner.md b/.dispatch/rules/one-owner.md new file mode 100644 index 0000000..15ef1a8 --- /dev/null +++ b/.dispatch/rules/one-owner.md @@ -0,0 +1,8 @@ +# One-owner + +- You are the EXCLUSIVE writer for the files assigned in the TASK block. +- No other agent may write those files — ever, under any circumstances. +- If another module needs a change in your file, the orchestrator summons YOU + to make it. +- Check your work: `git status` should show changes ONLY in your assigned files. + If you accidentally touch something else, revert it. diff --git a/.dispatch/rules/zero-warnings.md b/.dispatch/rules/zero-warnings.md new file mode 100644 index 0000000..a6239d9 --- /dev/null +++ b/.dispatch/rules/zero-warnings.md @@ -0,0 +1,11 @@ +# Zero warnings + +- Your code must compile with `-Wall -Wextra` producing EXACTLY ZERO warnings. +- No `-w` suppression. No `(void)` casts to silence legitimate warnings unless + you have a real reason (e.g. an unused parameter that must exist for a + callback signature). +- The orchestrator will re-run `make` after you and will REJECT any warning — + even ones from other files your code includes. If `raylib.h` or a system + header triggers a warning, isolate it with platform guards. +- Run `make` yourself before reporting. The exit code must be 0. +- The build output is your trust signal. A clean build = a clean module. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9e9c600 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,83 @@ +# AGENTS.md — subagent constitution for C/Raylib project + +> **This is loaded by every agent.** It contains ONLY project-specific, +> non-obvious rules. Never restate what a frontier model already knows about C +> or raylib. + +--- + +## 1. C dialect & build rules + +- **C99** (`-std=c99`). No C11/C17 features. +- **Compile with `-Wall -Wextra`** — zero warnings. If you cannot silence a + warning without reducing correctness, flag it in your report. +- **Raylib is the ONLY external library.** Include `raylib.h` for all platform + APIs (windowing, audio, input, font loading, drawing). Never include glfw, + miniaudio, or stb headers directly. For web: `emscripten.h` is allowed behind + `#ifdef PLATFORM_WEB`. +- **`build/font_data.h`** (generated by `xxd` from `resources/`) is included + behind `#include "font_data.h"` guard its use with `#if FONT_EMBEDDED`. +- **No dynamic allocation** unless paired with an explicit free in the same + module's cleanup. No leaks. +- **No VLAs** (variable-length arrays on the stack). Use fixed-size buffers or + guard with `MAX_*` constants. + +## 2. Module boundaries (THE KEY RULE) + +- **Your `.h` file IS your contract.** It declares every public type, constant, + and function signature that other modules consume. Keep it minimal — + consumers should not see implementation details. +- **Your `.c` file IS your implementation.** It is PRIVATE. Static functions + are module-internal. No other module includes your `.c` file — ever. +- **Include guards:** Every `.h` file starts with `#pragma once`. +- **Self-contained headers:** A `.h` file must `#include` all types it + references (directly or via forward declaration). Consuming agents should + need to include ONLY your `.h`, not hunt for transitive includes. +- **Forward-declare when possible:** `typedef struct PlayerState PlayerState;` + in a header avoids pulling in the full `types.h` when only a pointer is + needed. + +## 3. State management + +- **No global mutable variables.** All shared state lives in `PlayerState` and + is passed by pointer. +- **File-scope statics** are allowed ONLY for module-private data (e.g. cached + fonts, colors, layout constants in the UI module). +- **Const correctness:** Mark pointers `const` when the function does not + mutate the data. Example: `const char *path` in functions that only read. + +## 4. Function naming + +- **Prefix public functions** with the module name or a clear namespace: + - `player_*` for player module + - `study_*` for study module + - `ui_*` for UI module +- **Static helpers** (module-internal) may omit the prefix. +- **Verb-first naming:** `player_load`, `player_seek`, `study_detect_silence`. + +## 5. Testing & verification + +- **Build is the primary test.** If `make` exits 0 with zero warnings, your + module compiles and links correctly. +- **If you add a new `.c` file**, note it in your report — the orchestrator + updates the Makefile's `SRCS` list (this is orchestrator-owned build wiring, + NOT your responsibility). + +## 6. What you may read from other modules + +- **YES:** The `.h` header files of other modules (their contracts). +- **NO:** The `.c` implementation files of ANY other module. If you think you + need to read a sibling's `.c` to understand its behavior, STOP — that means + the `.h` contract is underspecified. Report this to the orchestrator. + +## 7. Report format + +After completing your work, write `reports/.md` with: +1. **Files touched** (list paths) +2. **What you implemented** (bullet list of functions/changes) +3. **Build result** (`make` output — exit code + any warnings) +4. **Issues or contract gaps** discovered +5. **Any changes needed in other modules** (e.g. "study.h needs a new function + declaration") + +Keep the report concise — the orchestrator reads many of these per wave. diff --git a/GLOSSARY.md b/GLOSSARY.md new file mode 100644 index 0000000..4d05b8b --- /dev/null +++ b/GLOSSARY.md @@ -0,0 +1,49 @@ +# GLOSSARY.md — canonical vocabulary + +> **One term per concept. Never coin a synonym silently.** If you think a new +> term is needed, propose it and wait for approval before using it in code. + +## Core concepts + +| Term | Definition | Avoid calling it... | +|------|-----------|-------------------| +| **PlayerState** | The single struct holding all mutable runtime state: loaded audio, playback status, silence regions, study mode flag | "app state", "context", "global state" | +| **speaking portion** | A contiguous segment of audio between two silence regions (i.e. the "meaningful content"). 0-based index. | "section", "segment", "clip", "part" | +| **silence region** | A detected gap in audio where amplitude stays below threshold for ≥ minDuration. Normalized 0–1. | "pause", "gap", "quiet zone" | +| **study mode** | Boolean toggle (`PlayerState.studyMode`). When ON, auto-pauses at silence boundaries. | "auto-pause mode", "learning mode" | +| **seek** | Jump playback position to a specific time (seconds). `player_seek(PlayerState*, float)`. | "scrub", "jump", "skip" | +| **auto-pause** | The study-mode mechanism that pauses playback when entering a silence region. | "auto-stop", "silence break" | +| **padding zone** | The 0.25s "breathing room" added around speaking portions (silence shrunk by 0.25s on each side) to avoid auto-pausing during natural speech pauses. | "grace period", "buffer zone" | +| **portion navigation** | Jumping between speaking portions via keys (V/B) or section buttons. | "chapter skip", "section nav" | +| **embedded font** | A `.otf`/`.ttf` font file converted to `build/font_data.h` via `xxd`, compiled into the binary. Guarded by `#if FONT_EMBEDDED`. | "baked font", "built-in font" | + +## Module names + +| Module | Files | Owns | +|--------|-------|------| +| **types** | `src/types.h` | `PlayerState`, `SilenceRegion`, all enums, defines, constants | +| **player** | `src/player.h`, `src/player.c` | Audio loading, playback control, seek, time formatting | +| **study** | `src/study.h`, `src/study.c` | Silence detection, study mode logic, portion navigation | +| **ui** | `src/ui.h`, `src/ui.c` | All rendering, font/color initialization, input handling | +| **main** | `src/main.c` | Entry point, main loop, platform glue | + +## Build terms + +| Term | Meaning | +|------|---------| +| **desktop build** | Host-native build via `gcc`, produces `build/study-player` (Linux) | +| **Windows build** | Cross-compile via `x86_64-w64-mingw32-gcc`, produces `build/study-player.exe` | +| **web build** | WASM via `emcc` + Emscripten, produces `build-web/index.{html,js,wasm}` | +| **font header** | `build/font_data.h` generated by `xxd -i` from a font in `resources/` | + +## Platform defines + +| Define | When set | +|--------|----------| +| `PLATFORM_DESKTOP` | Building for desktop — both Linux native and Windows cross-compile. Set in Makefile. | +| `PLATFORM_LINUX` | Defined by `-DPLATFORM_LINUX` when building for Linux. | +| `PLATFORM_WEB` | Building for web (Emscripten). Set in web build script. | +| `_GLFW_X11` | GLFW X11 backend (Linux). Set in Makefile for Linux builds. | +| `_GLFW_WIN32` | GLFW Windows backend. Set in Makefile for Windows cross-compile. | +| `GRAPHICS_API_OPENGL_ES2` | WebGL 2 backend. Set in web build script. | +| `FONT_EMBEDDED` | Defined in `font_data.h` by `xxd`. Guards `#if FONT_EMBEDDED` blocks. | diff --git a/ORCHESTRATOR.md b/ORCHESTRATOR.md new file mode 100644 index 0000000..b5d5319 --- /dev/null +++ b/ORCHESTRATOR.md @@ -0,0 +1,403 @@ +# ORCHESTRATOR.md — how to drive this project + +> **You are the orchestrator.** You do NOT write feature code yourself. You plan, +> summon owner-agents (one per module), verify their work, resolve errors, and keep +> the build green. This file is your complete operating manual. Read it fully +> before acting. Also read: `AGENTS.md` (the subagent constitution — you enforce +> it), `GLOSSARY.md`, `.dispatch/rules/`, `tasks.md` (live progress), and +> `notes/restructure-plan.md` (the full module design + rationale). + +--- + +## 0. Mental model (why this project is built this way) + +This is a **C/Raylib desktop application** built from composable modules. Each +module is a `.h` (contract) + `.c` (implementation) pair. The team structure is +**isomorphic to the module structure**: one owner-agent per module, and agents +communicate only through **header-file contracts** — exactly as the code does. +If an agent needs to read another module's `.c` file to understand its behavior, +the `.h` contract is underspecified — that is a bug, not normal. + +### The harness layers + +- **Constitution** (`AGENTS.md`) — loaded by every agent. C99 rules, raylib + conventions, zero-warning policy. +- **Safety reflexes** (`.dispatch/rules/*.md`) — tiny, crystallized scar tissue. +- **Glossary** (`GLOSSARY.md`) — one canonical name per concept. Prevents + synonym drift across modules. +- **This file** — the orchestrator's workflow (plan → summon → verify → commit). +- **Contracts** — `.h` files are the ONLY interface between modules. They + declare types, constants, and function signatures. `.c` files are private + implementation — never read by anyone but the owning agent. + +### C/Raylib-specific principles + +1. **Contracts are headers** — a `.h` file IS the boundary. It must be + self-contained (all types it uses are included within it). Prefer forward + declarations over pulling in heavy headers. +2. **No cross-module `.c` includes** — ever. If module A needs module B, A + includes `B.h`, never `B.c`. +3. **All shared mutable state goes through `PlayerState*`** — no global + variables. File-scope statics are only for module-private state (e.g. fonts + in the UI module). +4. **One `.o` per module** — each `.c` compiles independently. The linker + resolves dependencies. This is what makes parallel-agent waves possible. +5. **Zero warnings on `-Wall -Wextra`** — the build is the trust signal. If + `make` barks, the wave is not green. +6. **Raylib is the only external dependency** — no pulling in new libraries + without a design decision. + +--- + +## 1. The golden workflow (build/modify a feature) + +1. **Plan.** Decide the module(s); split into dependency-topological **waves** of + disjoint modules, and WIDEN each wave where you can (§2a). +2. **Overlap check FIRST.** Before creating anything new, check `GLOSSARY.md` + + existing `*.h` files. If the request *describes* an existing concept under a + new name, steer to the canonical term. New term? Propose the + standard/training-baked name and **ask the user** before adding it to the + glossary. +3. **Boundary decision is the USER's.** "New module vs. extend an existing one?" + — surface it; never decide granularity silently. +4. **Write the prompt** to `prompts/.md` (gitignored). See §3 for the + prompt recipe. +5. **Summon the wave** via `opencode run` (see §2); disjoint modules run in + PARALLEL (§2a). RE-READ `.dispatch/rules/` + the §3 scoping map before each + wave — assemble from the files, not from memory. +6. **Verify** the reports + independently re-run checks (see §4). Trust nothing + until you've re-run `make` yourself and it exits 0 with zero warnings. +7. **Resolve** any contract gaps / errors (see §5). +8. **Commit** the milestone with a clear message. Update `tasks.md`. + +--- + +## 2. Summoning agents via `opencode run` (the harness) + +OpenCode CLI is the summon mechanism. The orchestrator assembles each agent's +prompt by concatenating standardized briefs + scoped rules + the TASK block. + +**Working dir:** always the repo root, `/home/tradam/projects/study-player`. + +**Two agent types:** + +| Agent type | Brief | Reads | Writes | +|---|---|---|---| +| **Module agent** | `.dispatch/package-agent.md` | Only other `.h` files | Own `.h` + `.c` pair | +| **Build system agent** | `.dispatch/build-agent.md` | ANY file | `Makefile`, `bin/*` only | + +**Module agent canonical invocation** — the invariant guardrails live ONCE in +the brief, so `prompts/.md` is JUST the TASK block (§3). Do NOT use +`-f` (see gotcha); ALWAYS redirect output to a file. + +```bash +cd /home/tradam/projects/study-player && \ +opencode run --dir /home/tradam/projects/study-player \ + "$(cat .dispatch/package-agent.md) +$(cat .dispatch/rules/one-owner.md .dispatch/rules/zero-warnings.md .dispatch/rules/contracts-are-h.md) + +## TASK +$(cat prompts/.md)" \ + > reports/.run.log 2>&1 +``` + +**Build system agent canonical invocation:** + +```bash +cd /home/tradam/projects/study-player && \ +opencode run --dir /home/tradam/projects/study-player \ + "$(cat .dispatch/build-agent.md) +$(cat .dispatch/rules/one-owner.md .dispatch/rules/zero-warnings.md) + +## TASK +$(cat prompts/build-system.md)" \ + > reports/build-system.run.log 2>&1 +``` + +**Assembly order is fixed: agent brief → scoped rules → TASK.** + +**Scoping map** — include ONLY the rules matching the agent type: +- **Every module agent:** `one-owner.md`, `zero-warnings.md`, `contracts-are-h.md`. +- **Build system agent:** `one-owner.md`, `zero-warnings.md` (it reads any file + so `contracts-are-h.md` doesn't apply). + +`AGENTS.md` is auto-loaded by opencode — never `cat` it. + +**MANDATORY — capture output to a file, never display it.** The agent's streamed +output is enormous and will overwhelm context if it lands in your terminal. +ALWAYS redirect the summon's stdout+stderr to a log file (e.g. +`> reports/.run.log 2>&1`) and do NOT echo/`cat` that log back. Read +the agent's `reports/.md` report (and, if necessary, `grep`/`tail` the +log for a specific error). Dumping a full run log into context is a hard +failure. + +**Run discipline:** +- **Do NOT background it. Use a large timeout** (e.g. 1800000 ms = 30 min). +- One summon per tool call. For PARALLEL agents on disjoint files, launch + multiple summons as concurrent tool calls — but ONLY when their file sets do + not overlap (single-writer rule). +- Log parallel runs in `tasks.md`. + +**GOTCHAS:** +- `-f/--file` is an ARRAY flag and greedily eats your trailing message as + another filename → "File not found". **Inline with `"$(cat prompts/X.md)"` + instead.** +- A quick smoke test: `opencode run "Reply with exactly SMOKE_OK"` should print + `SMOKE_OK`. +- `opencode agent list` lists agent profiles; `opencode run --help` for flags. + +--- + +## 2a. Parallel execution — WAVES + +Throughput comes from running disjoint modules at once. Organise it as waves: +- **A wave = modules that (a) touch DISJOINT files and (b) have no dependency + on each other's `.c` files** (each includes only already-authored `.h` + contracts). Launch a wave by emitting one summon per module as CONCURRENT tool + calls. The composition root (`main.c`) is almost always the LAST wave. +- **Pre-author the seam to widen the wave.** Because the orchestrator OWNS + contracts (§6), write ALL `.h` contracts FIRST (WAVE 0), then summon the + implementors in the SAME wave against those fixed contracts — no module needs + another's implementation. Authoring the contracts up front turns a sequential + chain into one parallel wave. +- **One writer per file, always** — even across waves. If two units would edit + the same file, they are NOT separable; merge them into one module or sequence + them. +- **After a wave:** read every report, run `make` ONCE for the whole wave, + commit the milestone (update `tasks.md`), then start the next wave. Don't open + a new wave before the prior one is green. + +--- + +## 3. The per-summon `prompts/.md` is JUST the TASK block + +The invariant guardrails — single-writer ownership, visibility, zero warnings, +contract discipline, and the report format — live ONCE in the standardized +briefs the summon concatenates (§2). `prompts/.md` contains ONLY: + +1. **Your module files:** e.g. `src/player.h` and `src/player.c` — name the + FILES the agent may edit (it owns them exclusively). +2. **The job + algorithm**, naming specific functions and their signatures from + the pre-authored `.h` contract. +3. **The specific `.h` contract file(s)** to read (e.g. `src/types.h`, + `src/study.h`) — the agent reads ONLY these headers, never `.c` files. +4. **Any build instructions** (e.g. "run `make` from repo root"). + +Keep it scoped: state only the project-specific, non-inferable task — the briefs +carry the rest. + +**Make agents IMPLEMENT, not deliberate.** A summoned owner must edit files + +run `make` + write its report in one run. If a summon returns only a plan, +re-summon (§5a). + +--- + +## 4. Verification (the orchestrator's trust protocol) + +The orchestrator confirms work from **contracts (.h files) + build output** — +that is the designed trust mechanism. The header files ARE how you trust a +module without depending on its internals. + +**Stay out of implementation files (§6 Visibility).** Your trust signals are the +agent's report, the `.h` contract/surface it exposes, and the `make` output you +re-run yourself — NOT its `.c` implementation. Do NOT open a module's `.c` file +— not even to "skim", double-check, or diagnose a bug. You diagnose from the +`make` output + the `.h` contract + the agent's report, then **summon the owning +agent** (or a temporary multi-knowledge agent, §5) to read its own code and fix +it. + +After every agent, independently: +```bash +cd /home/tradam/projects/study-player +make clean && make -j$(nproc) 2>&1 # must exit 0 with zero warnings +git status --short # confirm agent stayed in its lane +``` + +- **Read ONLY the `.h` files** the unit exposes (its contract), not its `.c` + file. The contract plus a green build is enough to trust a module; subtle + mistakes show up as link errors or undefined symbols, which `make` catches. +- Confirm the agent touched ONLY its assigned files (one-owner rule). + +**Concurrency caveat (parallel waves):** `make` is whole-project, so an agent's +OWN mid-wave check can transiently see a sibling's half-written `.c` file. Don't +act on a report's out-of-module compile errors; YOUR post-wave `make` run is +authoritative. + +--- + +## 5. Resolving errors & contract changes + +- **A module needs something from another module's contract:** that's a CONTRACT + CHANGE. The owner of the `.h` makes it. To find every consumer, grep for + `#include ""` across `src/`. Then summon the affected module owners + to update. The orchestrator dispatches this fan-out; agents don't reach + across. +- **Link error or undefined symbol (X and Y each compile but don't link):** no + single file owns it. Summon a **temporary multi-knowledge agent** with + read/write to the 2–3 relevant files (it MAY see `.c` files — exception to + the visibility rule), as their temporary exclusive owner. +- **CR (change-request) in a report:** if it's **build/config** (`Makefile`, + `.gitignore`, `deps/` reference) the orchestrator edits it directly, then + re-verifies with `make`. If it's **implementation** (a `.c` file), the + orchestrator **summons the owning agent** — it does NOT edit `.c` files + itself. +- **Makefile changes:** the Makefile is orchestrator-owned (it's build wiring, + §6). If a module addition requires updating `SRCS`, the orchestrator does it. + +--- + +## 5a. Agent-failure recovery patterns + +- **Plan-only / "shall I proceed?" agent.** A summon sometimes returns a PLAN + and STOPS without editing (no diff, no `reports/.md`). Detect via + `git status` + the missing report. Re-summon the SAME TASK prefixed: + "IMPLEMENT THIS NOW — make all edits, run `make`, write the report; do not + stop to plan or ask." +- **Agent strayed out of its lane.** `git status --short` after every wave; if + an agent touched a file outside its assigned set, keep it ONLY if it's + legitimately the orchestrator's lane (contracts / Makefile / harness / docs, + §6) — otherwise revert + re-summon with a tighter scope. +- **Flaky green.** A module that compiles once but relies on stale `.o` files + might pass for the wrong reason; always `make clean && make` before + committing. + +--- + +## 6. Restrictions & invariants (NEVER violate) + +- **Single-writer:** never let two agents edit the same file concurrently. +- **Visibility rule:** agents see only other modules' `.h` contracts, NEVER + their `.c` implementation. An agent *needing* to read another module's `.c` + code is a signal that the `.h` contract is underspecified — fix the contract, + don't grant code access. (Exception: the temporary multi-knowledge integration + agent, §5.) +- **The orchestrator NEVER reads or edits `.c` implementation files.** You read + ONLY `.h` files (contracts) + `make` output + agent reports. Do NOT open + `.c` files — not even during a bug. Clean context = level-headed decisions; + the subagents do the implementation. +- **What the orchestrator MAY edit directly:** + (a) **Contracts** — any `.h` header file, especially `types.h` (pure shared + types with no .c file) and other `.h` files when pre-authoring contracts + or resolving gaps. + (b) **Build wiring + config** — `Makefile`, `.gitignore`, `deps/` + structure. (Note: the build system agent also owns `Makefile` and + `bin/*` — coordinate, don't conflict.) + (c) **Harness/docs** — `ORCHESTRATOR.md`, `AGENTS.md`, `GLOSSARY.md`, + `.dispatch/`, `notes/`, `tasks.md`, `prompts/`, `reports/`. + Everything else — all `.c` implementation files — changes ONLY by summoning + the owning agent. +- **Roadblock → surface to the user.** If a needed change doesn't fit the above + (ambiguous ownership, a design question, a stuck agent), stop and ask rather + than reaching into implementation. +- **Subagents inherit this restriction.** Every prompt you write must instruct + the agent to read ONLY the `.h` files of OTHER modules, with the sole + exception that it MAY read the `.c` files of the module it is assigned to. +- **Linux native + Windows cross-compile** — `make` builds for Linux; `make + windows` cross-compiles for Windows via MinGW. Both platforms must work. + Use `#ifdef PLATFORM_LINUX` / `_GLFW_X11` vs `_GLFW_WIN32` guards where + platform differences exist. +- **No global mutable state.** All shared state passes through `PlayerState*`. + File-scope statics are for module-private data only (e.g. cached fonts in the + UI module). +- **C99 only.** No C11/C17 features the compiler doesn't support. No C++ + in `.c` files. +- **Raylib is the only external library.** No SDL, no GLFW standalone, no + third-party UI — everything goes through raylib's API. + +--- + +## 7. Repo geography + +``` +/home/tradam/projects/study-player + + AGENTS.md the subagent constitution (auto-loaded by opencode; you enforce it) + ORCHESTRATOR.md the orchestrator's operating manual (this file) + GLOSSARY.md canonical vocabulary + aliases-to-avoid + tasks.md live progress checklist / milestone log + Makefile build — orchestrator-owned, never touched by agents + README.md project overview, build instructions, usage guide + + .dispatch/ + package-agent.md base owner-agent brief (module agents) + build-agent.md build system agent brief (Makefile, bin/*) + rules/ safety reflexes — tiny crystallized scar tissue + one-owner.md + zero-warnings.md + contracts-are-h.md + + .rules/ original design plans (reference only) + plan/ + plan.md + phase1.md + phase2.md + phase3.md + ideas/ + + notes/ + restructure-plan.md the full module split design + rationale + wave plan + + prompts/ (gitignored — orchestrator→agent TASK blocks) + reports/ (gitignored — agent→orchestrator reports) + + src/ + types.h CONTRACT — shared types, enums, constants (PlayerState, etc.) + player.h CONTRACT — audio playback: load, seek, play, pause, format_time + player.c IMPL + study.h CONTRACT — study mode: detect_silence, portion navigation + study.c IMPL + ui.h CONTRACT — rendering: init, render_frame, destroy + ui.c IMPL + main.c COMPOSITION ROOT — entry point + main loop + + deps/ + raylib/ raylib library (built as static lib) + raygui/ raygui library (reserved, not yet used) + + bin/ + build build script for desktop (Windows cross-compile) + build-web build script for WASM/web + clean clean build artifacts + serve serve web build locally + + resources/ font files, assets (gitignored) + build/ desktop build artifacts (gitignored) + build-web/ web build artifacts (gitignored) + web/ shell.html for emscripten +``` + +--- + +## 8. Current status & how to run + +See `tasks.md` for the live checklist. The project is a working single-file +`src/main.c` (778 lines) that needs to be split into modules as described in +`notes/restructure-plan.md`. + +**Desktop build:** +```bash +cd /home/tradam/projects/study-player +make -j$(nproc) # native Linux build → build/study-player +# or for cross-compile: +make windows -j$(nproc) # Windows cross-compile → build/study-player.exe +``` + +**Web build:** +```bash +bin/build-web # emscripten → build-web/index.html +bin/serve # serve on port 8080 +``` + +**Manual make:** +```bash +make clean && make -j$(nproc) +``` + +**Clean:** +```bash +bin/clean # removes build/ and build-web/ +``` + +When the module restructure is complete, `make` builds for the host platform. +`make windows` cross-compiles for Windows via MinGW. The font header generation +is a make prerequisite. diff --git a/bin/serve b/bin/serve new file mode 100755 index 0000000..931e2db --- /dev/null +++ b/bin/serve @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$SCRIPT_DIR" + +BUILD_DIR="build-web" + +if [ ! -f "$BUILD_DIR/index.html" ]; then + echo "Error: Web build not found. Run 'bin/build-web' first." + exit 1 +fi + +PORT="${1:-8080}" + +echo "Serving study-player at http://localhost:$PORT" +echo "Press Ctrl+C to stop." +python3 -m http.server "$PORT" -d "$BUILD_DIR" diff --git a/notes/restructure-plan.md b/notes/restructure-plan.md new file mode 100644 index 0000000..90126b7 --- /dev/null +++ b/notes/restructure-plan.md @@ -0,0 +1,344 @@ +# Restructure plan — single-file → modular C/Raylib project + +> **Status:** Pending. This document describes the target architecture and the +> step-by-step plan for splitting `src/main.c` (778 lines) into composable +> modules. The orchestrator will execute this plan. + +--- + +## §1 Design principles + +1. **`.h` = contract, `.c` = implementation.** Every module exposes a single + self-contained header. No other module ever includes a `.c` file. +2. **All shared mutable state through `PlayerState*`.** Defined in `types.h`. + File-scope statics only for module-private data (fonts, colors in UI). +3. **One `.o` per module.** Each `.c` compiles independently. The linker + resolves dependencies. This makes parallel agent waves possible. +4. **Zero-copy from current codebase.** Move functions as-is, then improve. + The first pass preserves all behavior exactly. +5. **Linux native + Windows cross-compile.** Both platforms supported. The + Makefile handles both targets (platform conditionals in the build, not in + the source unless needed). +6. **Minimal module count.** Four modules + types header. Don't over-split. + +--- + +## §2 Target module structure + +``` +src/ + types.h CONTRACT — shared types, enums, constants + player.h CONTRACT — audio playback + player.c IMPL + study.h CONTRACT — silence detection + study mode + study.c IMPL + ui.h CONTRACT — rendering + input + ui.c IMPL + main.c COMPOSITION ROOT — entry point + main loop +``` + +### §2.1 `src/types.h` — shared types and constants + +No `.c` file. Pure definitions. + +```c +#pragma once + +// ── Platform detection ── +#ifdef PLATFORM_LINUX + #define _GLFW_X11 +#endif + +// ── Constants ── +#define SCREEN_W 1920 +#define SCREEN_H 1080 +#define MAX_SILENCE_REGIONS 4096 + +// ── Types ── +typedef struct { + float start; /* normalized 0..1 */ + float end; /* normalized 0..1 */ +} SilenceRegion; + +typedef struct { + Music music; + bool loaded; + bool playing; + float duration; + float currentTime; + char filename[256]; + SilenceRegion silence[MAX_SILENCE_REGIONS]; + int silenceCount; + bool studyMode; + bool wasInSilence; + int lastSilenceIdx; + int skipAutoUpdate; + double lastVPress; +} PlayerState; +``` + +### §2.2 `src/player.h` — audio playback contract + +```c +#pragma once +#include "types.h" + +void player_load(PlayerState *s, const char *path); +void player_unload(PlayerState *s); +void player_seek(PlayerState *s, float seconds); +void player_play(PlayerState *s); +void player_pause(PlayerState *s); +void player_update(PlayerState *s); /* call once per frame */ +void format_time(float seconds, char *buf, int bufsize); +const char *basename_from_path(const char *path); +``` + +**`src/player.c`** implements: +- `player_load()` — load MP3, set up Music stream, start playback, update filename + window title, calls `study_detect_silence()` to populate silence regions +- `player_unload()` — stop + unload music stream +- `player_seek()` — seek music stream to target seconds +- `player_play()` / `player_pause()` — Resume/Pause + update state +- `player_update()` — `UpdateMusicStream()` + `GetMusicTimePlayed()` with skip logic +- `format_time()` — seconds → "MM:SS" or "H:MM:SS" string +- `basename_from_path()` — extract filename from path +- `strcasecmp_ext()` — helper for extension checking (static) + +### §2.3 `src/study.h` — study mode contract + +```c +#pragma once +#include "types.h" + +void study_detect_silence(const char *path, PlayerState *s, float threshold, float minDuration); +int study_find_silence_at(const PlayerState *s, float pos); +float study_speaking_portion_start(const PlayerState *s, int portion); +int study_current_portion(const PlayerState *s, float pos); +int study_total_portions(const PlayerState *s); +float study_segment_seek_target(const PlayerState *s, int portion); +bool study_in_padding_zone(const PlayerState *s, float pos, int portion); +void study_update(PlayerState *s); /* auto-pause logic for one frame */ +``` + +**`src/study.c`** implements: +- `study_detect_silence()` — Wave analysis, silence region detection with padding (move from current `detect_silence`) +- `study_find_silence_at()` — find silence region at normalized position +- `study_speaking_portion_start()` — get start of speaking portion N +- `study_current_portion()` — get current speaking portion index +- `study_total_portions()` — total speaking portions +- `study_segment_seek_target()` — seek target for a portion (with 2-frame offset) +- `study_in_padding_zone()` — check if position is in padding zone +- `study_update()` — the auto-pause state machine: detect silence entry/exit, auto-pause → seek to next portion + +### §2.4 `src/ui.h` — rendering + input contract + +```c +#pragma once +#include "types.h" + +void ui_init(void); +void ui_destroy(void); +void ui_render_frame(PlayerState *s); +``` + +**`src/ui.c`** implements: +- **File-scope statics:** fonts (`fontSmall`, `font`, `fontMed`, `fontLarge`, `fontHelp`), sizes, colors, layout constants, button positions +- `ui_init()` — load fonts, set colors, compute layout +- `ui_destroy()` — unload embedded fonts +- `ui_render_frame()` — one complete frame: + - Handle drag-drop file loading (desktop) → calls `player_load()` + - Handle keyboard input (C, N, Space, V, B, Arrows, 0–9, Up/Down) → calls `player_seek()`, `player_play()`, `player_pause()` + - Handle mouse input (click-to-seek, play/pause button, section nav buttons, study mode checkbox) + - Call `player_update()` for music stream update + - Call `study_update()` for study mode auto-pause logic + - Call `BeginDrawing()` / `EndDrawing()` with all rendering (title, progress bar, time labels, percentage, buttons, checkboxes, help text) +- `draw_text_centered()` — static helper +- `draw_play_icon()`, `draw_pause_icon()`, `draw_seek_back_icon()`, `draw_seek_fwd_icon()` — static helpers +- `button_hit()` — static helper + +### §2.5 `src/main.c` — composition root + +No `.h` file. Entry point only. + +```c +#include "raylib.h" +#include "player.h" +#include "study.h" +#include "ui.h" +#include "font_data.h" +#ifdef PLATFORM_WEB +#include +#endif + +/* File-scope PlayerState (needed for emscripten main loop callback) */ +static PlayerState state = { 0 }; + +#ifdef PLATFORM_WEB +EMSCRIPTEN_KEEPALIVE +void load_file_web(const char *path) { + player_load(&state, path); +} +#endif + +static void update_frame(void) { + ui_render_frame(&state); +} + +int main(void) { + InitWindow(SCREEN_W, SCREEN_H, "Study Player"); + InitAudioDevice(); + SetTargetFPS(60); + ui_init(); + + memset(&state, 0, sizeof(state)); + state.studyMode = true; + state.lastSilenceIdx = -1; + +#ifdef PLATFORM_WEB + emscripten_set_main_loop(update_frame, 0, 1); +#else + while (!WindowShouldClose()) { + update_frame(); + } +#endif + + player_unload(&state); + ui_destroy(); + CloseAudioDevice(); + CloseWindow(); + return 0; +} +``` + +--- + +## §3 Dependency graph + +``` +types.h ← player.h ← ui.h + ← study.h ← ui.h + ← player.c (player depends on study for silence detection) + ← main.c + +player.h ← player.c (includes: types.h) +study.h ← study.c (includes: types.h) +ui.h ← ui.c (includes: types.h, player.h, study.h) +``` + +- `types.h` — no dependencies (pure definitions) +- `player.h` — depends on `types.h` (PlayerState, Music type via raylib) +- `study.h` — depends on `types.h` (PlayerState, SilenceRegion) +- `ui.h` — depends on `types.h` (PlayerState) +- `player.c` — depends on `types.h`, `player.h` (its own contract), `study.h` (calls `study_detect_silence` in `player_load`), `raylib.h` +- `study.c` — depends on `types.h`, `study.h` (its own contract), `raylib.h` +- `ui.c` — depends on `types.h`, `player.h`, `study.h`, `ui.h`, `raylib.h`, `font_data.h` +- `main.c` — depends on all `.h` files, `raylib.h`, `font_data.h`, `emscripten.h` (web only) + +All modules compile to `.o` independently — zero `.c` includes another `.c`. + +--- + +## §4 Wave plan + +### WAVE 0 — Orchestrator + build system agent (sequentially) + +**Orchestrator (direct work):** +1. Write `src/types.h` with all shared types and constants +2. Pre-author `src/player.h`, `src/study.h`, `src/ui.h` — define every public + function signature so module agents have fixed contracts to implement + against +3. Write TASK prompts to `prompts/build-system.md`, `prompts/player.md`, + `prompts/study.md`, `prompts/ui.md`, `prompts/main.md` + +**Build system agent:** (reads ANY file, writes only Makefile + bin/*) +1. Update `Makefile`: + - Linux native target (default): `gcc -o build/study-player src/*.c ...` + - Windows target (`make windows`): cross-compile via MinGW + - Font header generation as a make prerequisite + - `SRCS = $(wildcard src/*.c)`, `OBJS = $(SRCS:.c=.o)` + - Raylib `.o` compilation with `-w` (third-party warnings suppressed) +2. Update `.gitignore` (add `prompts/`, `reports/`) + +**Verification:** Module `.h` files compile cleanly (no syntax errors). +`make` will fail on missing `.c` implementations — that's expected, WAVE 1 +resolves it. + +### WAVE 1 — All `.c` implementations in parallel (disjoint files) + +Four module agents, launched as concurrent tool calls. Each owns its `.h` + +`.c` pair, reads only other `.h` files, writes only its own files: + +| Agent | Files it owns | .h files it reads | +|---|---|---| +| Agent A: player | `src/player.h`, `src/player.c` | `src/types.h` | +| Agent B: study | `src/study.h`, `src/study.c` | `src/types.h` | +| Agent C: ui | `src/ui.h`, `src/ui.c` | `src/types.h`, `src/player.h`, `src/study.h` | +| Agent D: main | `src/main.c` (no .h) | all `.h` files | + +File sets are DISJOINT. No compile-time dependency between `.c` files — each +compiles to `.o` independently. All `.h` contracts were fixed in WAVE 0. + +**Verification:** `make clean && make -j$(nproc)` — exit 0, zero warnings +(Linux). Then `make windows -j$(nproc)` — exit 0, zero warnings (Windows). + +### WAVE 2 (if needed) — Integration fixes + +Any link errors, behavioral regressions, or contract gaps discovered during +WAVE 1 verification. Summon affected agents to fix. + +--- + +## §5 Function migration map + +Every function in the current `src/main.c` moves to exactly one target file: + +| Current function | → Target file | New name | +|---|---|---| +| `SilenceRegion` struct | `types.h` | (unchanged) | +| `PlayerState` struct | `types.h` | (unchanged) | +| `#define` constants | `types.h` | (unchanged) | +| `strcasecmp_ext()` | `player.c` | static (no prefix) | +| `detect_silence()` | `study.c` | `study_detect_silence()` | +| `basename_from_path()` | `player.c` | (unchanged, public) | +| `seek_to()` | `player.c` | `player_seek()` | +| `format_time()` | `player.c` | (unchanged, public) | +| `find_silence_at()` | `study.c` | `study_find_silence_at()` | +| `speaking_portion_start()` | `study.c` | `study_speaking_portion_start()` | +| `current_speaking_portion()` | `study.c` | `study_current_portion()` | +| `total_speaking_portions()` | `study.c` | `study_total_portions()` | +| `segment_seek_target()` | `study.c` | `study_segment_seek_target()` | +| `in_padding_zone()` | `study.c` | `study_in_padding_zone()` | +| `draw_text_centered()` | `ui.c` | static (no prefix) | +| `draw_play_icon()` | `ui.c` | static (no prefix) | +| `draw_pause_icon()` | `ui.c` | static (no prefix) | +| `draw_seek_back_icon()` | `ui.c` | static (no prefix) | +| `draw_seek_fwd_icon()` | `ui.c` | static (no prefix) | +| `button_hit()` | `ui.c` | static (no prefix) | +| `load_audio_file()` | `player.c` | `player_load()` | +| `load_file_web()` (emscripten) | `main.c` | (unchanged) | +| `update_frame()` | `main.c` | (simplified — just calls `ui_render_frame()`) | +| `main()` | `main.c` | (unchanged, simplified) | +| File-scope statics (state, fonts, colors, layout) | `main.c` (`state`), `ui.c` (rest) | — | + +**Auto-pause logic** currently inlined in `update_frame()` (lines 533–562) +moves into `study_update()` in `study.c`. The UI module calls +`study_update(&state)` after `player_update(&state)`. + +--- + +## §6 Current code as-is invariants (must preserve) + +During the split, preserve every existing behavior: +1. Drag-and-drop MP3 loading (desktop) +2. All keyboard shortcuts: C, N, Space, V, B, Arrows, Up, Down, 0–9 +3. Click-to-seek on progress bar +4. Study mode auto-pause at silence boundaries +5. Study mode checkbox toggle +6. Play/pause button and section navigation buttons +7. Progress bar rendering with elapsed/remaining time labels +8. Percentage display above progress bar +9. "PLAYING"/"PAUSED" status text +10. Help text at bottom +11. Dark theme colors +12. Embeddable font support (`FONT_EMBEDDED`) +13. Web platform support (`PLATFORM_WEB`, emscripten main loop, file upload) +14. Linux native build + Windows cross-compile both work diff --git a/src/layout_editor.c b/src/layout_editor.c index e2d7aee..0a40c59 100644 --- a/src/layout_editor.c +++ b/src/layout_editor.c @@ -71,7 +71,7 @@ void layout_editor_draw(const char *exePath, UILayout *layout) } } - /* 5: Smart play button (HOLD) */ + /* 5: Smart play button (Play) */ { Rectangle r = { layout->smartPlayX, layout->smartPlayY, 200.0f, 80.0f }; @@ -250,7 +250,7 @@ void layout_editor_draw(const char *exePath, UILayout *layout) Color f = (dragIndex == 5) ? highlightColor : fillColor; Rectangle r = { layout->smartPlayX, layout->smartPlayY, 200.0f, 80.0f }; - draw_label("HOLD", r, f, borderColor); + draw_label("Play", r, f, borderColor); } /* --- 6: Section nav buttons --- */ diff --git a/src/main.c b/src/main.c index c545442..49d1ab2 100644 --- a/src/main.c +++ b/src/main.c @@ -667,14 +667,14 @@ static void update_frame(void) if (portion > total) portion = total; char portionBuf[32]; snprintf(portionBuf, sizeof(portionBuf), "%d/%d", portion, total); - float portionY = layout.secNavY - szSmall / 2.0f; + float secBtnRadius = 35.0f; + float portionY = layout.secNavY - szSmall - secBtnRadius - 10.0f; float portionSpacing = szSmall * 0.03f; Vector2 portionSize = MeasureTextEx(fontSmall, portionBuf, szSmall, portionSpacing); float portionX = layout.secNavX - portionSize.x / 2.0f; DrawTextEx(fontSmall, portionBuf, (Vector2){ portionX, portionY }, szSmall, portionSpacing, mutedColor); /* Section nav buttons */ - float secBtnRadius = 35.0f; float secPrevX = layout.secNavX - 65.0f; float secNextX = layout.secNavX + 65.0f; float secBtnY_draw = layout.secNavY; @@ -701,10 +701,10 @@ static void update_frame(void) DrawRectangleRounded(smartBtn, 0.3f, 8, btnFill); DrawRectangleRoundedLines(smartBtn, 0.3f, 8, btnBorder); float btnSpacing = szSmall * 0.03f; - Vector2 btnSize = MeasureTextEx(fontSmall, "HOLD", szSmall, btnSpacing); + Vector2 btnSize = MeasureTextEx(fontSmall, "Play", szSmall, btnSpacing); float tx = smartBtn.x + (smartBtn.width - btnSize.x) / 2.0f; float ty = smartBtn.y + (smartBtn.height - szSmall) / 2.0f; - DrawTextEx(fontSmall, "HOLD", (Vector2){ tx, ty }, szSmall, btnSpacing, btnTextColor); + DrawTextEx(fontSmall, "Play", (Vector2){ tx, ty }, szSmall, btnSpacing, btnTextColor); } } else diff --git a/study-player.cfg b/study-player.cfg new file mode 100644 index 0000000..3d221cc --- /dev/null +++ b/study-player.cfg @@ -0,0 +1,16 @@ +# Study Player layout config +title_y=60.00 +title_x=960.00 +bar_y=460.00 +bar_height=50.00 +bar_width=1248.00 +btn_radius=55.00 +help_y=1000.00 +help_x=40.00 +status_x=960.00 +btn_y=715.00 +btn_center_x=960.00 +smart_play_y=868.00 +smart_play_x=1611.00 +sec_nav_y=745.00 +sec_nav_x=1713.00 diff --git a/tasks.md b/tasks.md new file mode 100644 index 0000000..14c6e80 --- /dev/null +++ b/tasks.md @@ -0,0 +1,43 @@ +# tasks.md — live progress checklist + +> Updated by the orchestrator after each milestone. One line per completed wave. + +--- + +## WAVE 0 — Orchestrator + build system agent + +- [ ] Orchestrator: write `src/types.h` (shared types, constants) +- [ ] Orchestrator: pre-author `src/player.h` (audio playback contract) +- [ ] Orchestrator: pre-author `src/study.h` (study mode contract) +- [ ] Orchestrator: pre-author `src/ui.h` (rendering + input contract) +- [ ] Orchestrator: write TASK prompts in `prompts/` +- [ ] Build agent: update `Makefile` (Linux native + Windows cross-compile targets) +- [ ] Build agent: update `.gitignore` (add `prompts/`, `reports/`) + +## WAVE 1 — All `.c` implementations (parallel) + +- [ ] Agent A: implement `src/player.c` from `player.h` contract +- [ ] Agent B: implement `src/study.c` from `study.h` contract +- [ ] Agent C: implement `src/ui.c` from `ui.h` contract +- [ ] Agent D: implement `src/main.c` (composition root) + +## WAVE 2 — Integration fixes (if needed) + +- [ ] Fix any link errors or behavioral regressions + +## Post-milestone + +- [ ] `make clean && make -j$(nproc)` exits 0, zero warnings (Linux) +- [ ] `make windows -j$(nproc)` exits 0, zero warnings (Windows cross-compile) +- [ ] Functional test: play an MP3, test all keyboard shortcuts, study mode, UI +- [ ] Commit: `refactor: split src/main.c into modular .h/.c files` + +--- + +## Open items (future) + +- [ ] `bin/build` and `bin/build-web` scripts can be retired or simplified +- [ ] Test coverage (unit tests for study module?) +- [ ] Volume control +- [ ] Playlist support +- [ ] Config file / persistence diff --git a/web/shell.html b/web/shell.html new file mode 100644 index 0000000..199756b --- /dev/null +++ b/web/shell.html @@ -0,0 +1,110 @@ + + + + + +Study Player + + + +
+ + + +
+
+ +
+
+ + + + {{{ SCRIPT }}} + + + -- cgit v1.2.3