summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-29 01:33:37 +0900
committerAdam Malczewski <[email protected]>2026-06-29 01:33:37 +0900
commit4ee00a78fbbf5ff6e493dbffbf1b2d3083b90cfa (patch)
treebb91ae1e6cb4e0c8538f9422076975d8c9a320c0
parentc1ed35b6e11fd89006ac18f7893f07d4920e9dfc (diff)
downloadstudy-player-4ee00a78fbbf5ff6e493dbffbf1b2d3083b90cfa.tar.gz
study-player-4ee00a78fbbf5ff6e493dbffbf1b2d3083b90cfa.zip
phase 2: update scar-tissue knowledge with mruby compatibility discoveries (no require, int32_t, keyword_init, ARGV, non-blocking seek)
-rw-r--r--.agents/knowledge/study-player.md163
1 files changed, 77 insertions, 86 deletions
diff --git a/.agents/knowledge/study-player.md b/.agents/knowledge/study-player.md
index a928dec..4142cec 100644
--- a/.agents/knowledge/study-player.md
+++ b/.agents/knowledge/study-player.md
@@ -1,88 +1,79 @@
-# Tribal knowledge: study-player rewrite (`StudyPlayer::`)
-
-Study-feature scar tissue for the rewrite of `../source/` into the
-raylib-jamstack template.
+# Tribal knowledge: Study Player (rewrite)
## At a glance
-- **Key files:** `game/study_player/study_player.rb` (composition root),
- `game/study_player/core.rb` (pure logic), `game/study_player/audio_adapter.rb`,
- `game/study_player/ui.rb` + `game/study_player/ui/main.{rml,rcss}`.
-- **Native helper:** `mrbgems/study_audio` (planned Phase 3) exposes raw silence
- gaps because raylib `Wave` sample data is not accessible from Ruby.
-- **Engine touch:** `src/main.c` needs a small patch to expose command-line args
- as `ARGV` (Phase 2); otherwise the `argv → study-player view` requirement is
- unreachable from Ruby.
-- **Cross-refs:** rules `mruby-rebuild`, `dont-edit-generated`, `wsl-toolchain`;
- knowledge `rmlui-binding`, `raylib-binding`, `flecs-binding`, `build-system`;
- `notes/study-player-rewrite-plan.md`.
-
-## Why a native silence scanner is needed
-
-The generated raylib bindings expose `Rl.load_wave` and `Rl.wave_format`, but
-`Wave.data` is a raw `void*` buffer that the generator intentionally skips.
-There is no Ruby-visible `Wave#samples`. Ported silence detection therefore needs
-a tiny C helper (`mrbgems/study_audio`) that does the I/O and returns a plain
-Ruby array of `[start_seconds, end_seconds]` gaps.
-
-The **decision logic** (threshold comparison, 10 ms chunking, padding shrink,
-portion math, auto-pause FSM) stays in pure Ruby under `StudyPlayer::Core`.
-The native helper only solves the *data access* problem.
-
-##ARGV flow
-
-`src/main.c` currently only uses `argv[1]` as the script path. To satisfy
-"audio file arg → study-player view", pass `argv[2..]` into the mruby VM as a
-constant named `ARGV`:
-
-```c
-mrb_value argv_ary = mrb_ary_new(mrb);
-for (int i = 2; i < argc; i++) {
- mrb_ary_push(mrb, argv_ary, mrb_str_new_cstr(mrb, argv[i]));
-}
-mrb_define_const(mrb, mrb->object_class, "ARGV", argv_ary);
-```
-
-Web builds inherit this because `Module.arguments` is mapped to `argc/argv` in
-emscripten's generated `main()`.
-
-## Non-blocking seek (`skip_auto_update`)
-
-After `Rl.seek_music_stream`, `Rl.get_music_time_played` can briefly report the
-old position. The original used `skipAutoUpdate = 3` frames to let the audio
-engine settle. We model this as a Flecs component field:
-
-```ruby
-PlaybackState = world.struct("PlaybackState", "{ ... int skip_auto_update; }")
-```
-
-A system applies the seek and sets the counter to 3; the update system skips
-`get_music_time_played` while the counter is positive.
-
-## RmlUi layout quirks
-
-- Use `left:` / `top:` for absolute elements. RmlUi miscomputes `right:` in this
- backend, so a panel `right: 60px; width: 240px` can render off-screen.
-- For progress-bar clicks, use the event's `mouse_x` minus the element's
- `absolute_left`, divided by `client_width`.
-- Checkboxes in RmlUi read/write the `checked` attribute, not just the value.
-- The RmlUi context should be initialized **after** `Rl.init_window` and fonts
- must be loaded before the document.
-
-## Adding `mrbgems/study_audio`
-
-Because the RmlUi gem already forces `MRB_USE_CXX_EXCEPTION`, adding any gem is
-an ABI toggle. Follow rule `mruby-rebuild`:
-
-```sh
-rm -rf vendor/mruby/build
-./rebuild.sh
-```
-
-## Constants to preserve
-
-- Silence threshold: **0.015**.
-- Minimum silence duration: **0.75 seconds**.
-- Padding zone: **0.25 seconds** on each side of a silence region.
-- Default seek lead-in: **2 render frames** = `2.0 / 60.0` seconds
- (ported from original; may tune later).
-- Default `skip_auto_update`: **3 frames** at target 60 FPS.
+- **Key files:** `game/study_player/study_player.rb` (monolithic entry point),
+ `src/main.c` (ARGV patch), `mrbgems/raylib/src/raylib_bindings.c`
+ (FilePathList#path_at).
+- **Run:** `./zig-out/bin/game game/study_player/study_player.rb [audio.mp3]`
+- **Cross-refs:** plan `notes/study-player-rewrite-plan.md`, template rules
+ `.agents/rules/*`, build `.agents/knowledge/build-system.md`.
+
+## mruby compatibility discoveries (Phase 2 scar tissue)
+
+### No require/require_relative/load in default gembox
+The mruby default gembox (`conf.gembox 'default'`) does NOT include file
+loading. `require`, `require_relative`, and `load` are all undefined. The
+template's existing game scripts (ragdoll_demo.rb, etc.) are monolithic
+single-file scripts for this reason.
+
+**Fix for Phase 2:** Inlined all modules into `game/study_player/study_player.rb`
+(single ~440-line script). Individual module files (`components.rb`, `core.rb`,
+etc.) are retained on disk as design documentation plus a `systems/` directory;
+they are NOT loaded at runtime.
+
+**Future:** Adding `conf.gem 'mruby-require'` to `build_config.rb` would enable
+multi-file loading. This triggers the `rm -rf vendor/mruby/build` rebuild rule
+and is deferred to Phase 3 (plan §8 mentions `mrbgems/study_audio` which already
+requires a full rebuild).
+
+### Flecs struct descriptor: `int` is invalid → use `int32_t`
+Flecs meta descriptor parser does NOT recognise `int` as a type name. Valid
+primitive type names in the descriptor string are:
+- `int8_t`, `int16_t`, `int32_t`, `int64_t` (signed integers)
+- `uint8_t`, `uint16_t`, `uint32_t`, `uint64_t` (unsigned)
+- `float`, `double`
+- `bool`
+- `string`
+- `char`, `byte`
+
+`int` → `int32_t` was required in `PlaybackState` and `StudyState` descriptors.
+
+### Struct.new keyword_init not supported
+mruby's `Struct` does not support `keyword_init: true`. Use a plain class with
+`attr_accessor` + `initialize` instead (see `StudyPlayer::Runtime`).
+
+### TOPLEVEL_BINDING not available
+CRuby's `TOPLEVEL_BINDING` constant does not exist in mruby.
+
+### defined? not reliable as a guard
+`defined?(ARGV)` is not available; use `begin/rescue` or just reference `ARGV`
+directly (it is always set by the `src/main.c` patch).
+
+## Drag-drop: FilePathList#path_at hand-written method
+The generated raylib bindings only expose `FilePathList#count`/`count=`. The
+`paths` member is `char**` — a raw pointer the generator skips. A small
+hand-written method `path_at(index)` was added to
+`mrbgems/raylib/src/raylib_bindings.c` to enable indexed path access for
+drag-and-drop. This method is registered on `Rl::FilePathList` after the
+generated registrar runs.
+
+## Non-blocking seek (skip_auto_update)
+After `Rl.seek_music_stream`, raylib's miniaudio stream may briefly report the
+old position. The `skip_auto_update` counter (set to 3 frames = ~50ms at 60fps)
+skips `Rl.get_music_time_played` sampling while decrementing each frame. The
+`UpdateSystem` (ON_UPDATE) handles this cooldown; `SeekSystem` (PRE_UPDATE)
+applies the seek and sets the counter. This mirrors the original C source's
+`skipAutoUpdate` pattern.
+
+## ARGV patch (src/main.c)
+Added `#include <mruby/array.h>` and a block in `main()` that builds an
+`mrb_value` array from `argv[2..]` and defines it as `ARGV` constant. This
+is the only way Ruby game code can access command-line arguments in this
+architecture (there is no `ENV` for process args in the default gembox).
+
+## Wayland/WSLg screenshot path quirk
+`JAMSTACK_SCREENSHOT=...` with a path starting with `/` resolves relative to
+CWD, not as an absolute filesystem path. Use a bare filename (e.g.
+`JAMSTACK_SCREENSHOT=output.png`) to write in the project root, or use a full
+path without a leading `/` quirk. This is a raylib/Wayland interaction, not
+a study-player-specific issue, but it surfaces during verification.