Files
HanjaKanaMun/AGENTS.md
T
SansanandClaude Sonnet 5 0776e8e4b9
Build and Test / build (push) Failing after 25s
Build and Test / windows (push) Successful in 10m28s
Revert runner-contention theory, add gdb diagnostics for the test segfault
The windows job hadn't even started when build's odin test segfaulted
again on its own (run 132), so concurrency wasn't the cause. Add a
Debug Segfault step that runs the already-built test binary under gdb
to get a backtrace, since this doesn't reproduce locally or in a plain
ubuntu:24.04 container with the same Odin build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-03 18:02:11 +02:00

6.0 KiB

HanjaKanaMun - Project Plan

Overview

Odin + raylib multilingual memory card game for learning Japanese (Hiragana/Katakana), Korean (Hangul), and Thai scripts.

Commands (see justfile): just run, just check, just test, just package <version>, just package-windows <version>

Current Status

Completed

  • Code split into modules under src/ (themes, assets, audio, ui, card, decks, memory, profile, screens)
  • .odin files tracked in git (.gitignore used to ignore *.odin)
  • Fonts: M PLUS Rounded 1c (UI + kana), Jua (Hangul), Mali (Thai), all OFL, loaded via LoadFontEx with explicit codepoints
  • Per-language themes (themes.Theme) with flag-based card backs: Japan (Hinomaru), Korea (Taegukgi), Thailand (Trairong)
  • Card flip animation (art rendered to textures and squashed), lift, shadow, lighting; matched cards pulse; cards are dealt in at game start
  • Sound effects synthesized in code (flip, match, mismatch, win, button click), no audio files needed
  • Decks: Hiragana (46), Katakana (46), Hangul basic jamo (24), Thai consonants (42); symbol ↔ reading pairs
  • Difficulty levels: Easy 3x4 (6 pairs), Normal 4x6 (12), Hard 4x8 (16)
  • Keyboard play: arrows / WASD / hjkl move focus, Enter/Space flips; number keys pick menu entries; Esc goes back (quits from main menu)
  • High scores: best moves, best time and games played per deck + difficulty, plus last difficulty, saved as JSON in the user data folder
  • Screen fade-in transitions
  • Win panel with best score / "New best!", Play Again (Enter) / Menu
  • Tooling: justfile, ols/odinfmt config for neovim, tests, Gitea CI (build/test, Windows cross-build, tagged releases)

Ideas / Next

  • Hangul syllable blocks deck (e.g. 가 ga, 나 na), Thai vowels (needs care: combining marks)
  • Hiragana ↔ katakana pairing mode, dakuten (が, ぱ…) deck
  • Settings: sound on/off, volume
  • Test the Windows build on a real machine
  • macOS release build (would need a macOS runner or osxcross)

Known Issues / Gotchas

  1. Font glyphs must be listed: raylib only bakes the codepoints passed to LoadFontEx. Any new non-ASCII text needs its characters added in assets.codepoints_for (or the EXTRA_* strings), otherwise it renders as ?. The fonts_cover_every_deck_character test checks deck text.
  2. Old font failure root cause: DroidSansJapanese.ttf was a GitHub HTML page saved from a blob URL, not a font. Always download fonts from raw URLs and check them with file.
  3. Thai shaping: raylib does no text shaping, so Thai combining vowels/tone marks would be misplaced. Thai cards use standalone consonants; Thai UI text avoids marks (win message is English).
  4. Tests share the memory game's global state, so they must run single-threaded (just test sets ODIN_TEST_THREADS=1).
  5. The game changes directory to the executable's folder at startup so assets/ is found from anywhere.
  6. Windows builds are cross-compiled: Odin can't link Windows binaries on Linux, so just windows builds objects with -build-mode:obj and links with lld-link against an xwin SDK. The VC runtime is linked statically; only Windows system DLLs are needed. New Odin core usage can need new import libs (e.g. ole32.lib for os.user_data_dir); the CI windows job catches this. Not yet tested on a real Windows machine.
  7. Odin constant arrays can't be indexed at runtime: lookup tables (DECKS, LAYOUTS, …) are @(rodata) globals instead of :: constants.
  8. Profile memory: profile always allocates from the heap allocator, because the test runner frees each test's allocator afterwards.
  9. CI's build job segfaults on odin test (exit 139) on the self-hosted runner, even running alone — reproduces every push so far. Does not reproduce locally or in a plain ubuntu:24.04 container with the same Odin version, so it's specific to the runner's actual job image/environment, not the test code. Under investigation; see the Debug Segfault diagnostic step in gitea-build.yaml.

File Structure

HanjaKanaMun/
├── AGENTS.md              # Project plan (this file)
├── README.md
├── justfile               # build/run/check/test/package/windows recipes
├── ols.json               # ols language server settings (neovim)
├── odinfmt.json           # odinfmt formatter settings (4 spaces, LF)
├── .gitea/
│   ├── actions/setup/      # shared CI toolchain setup (apt, Odin, just)
│   └── workflows/
│       ├── gitea-build.yaml    # check + test + build, and Windows cross-build, on main/develop
│       └── gitea-release.yaml  # on tags v*: Linux + Windows packages, package registry upload, Gitea release
├── main.odin              # Window/audio setup, screen state machine, transitions
├── src/
│   ├── themes/themes.odin  # Theme struct, flag colors, Screen/Flag enums, layout constants
│   ├── assets/assets.odin  # Font styles and codepoint lists, font loading
│   ├── audio/audio.odin    # Synthesized sound effects, play()
│   ├── ui/ui.odin          # Text helpers, buttons (with hotkeys), hover cursor
│   ├── card/card.odin      # Card struct, flag card-back art, face textures, flip/deal animation
│   ├── decks/decks.odin    # Languages, decks and their entries (symbol, reading, hint)
│   ├── memory/memory.odin  # Memory game state, logic, keyboard focus, drawing
│   ├── profile/profile.odin # Best scores and settings, JSON in the user data folder
│   └── screens/screens.odin # Main menu and language menus
├── tests/
│   ├── memory_test.odin    # Decks, font coverage, game rules, layout, keyboard focus, card animation
│   └── profile_test.odin   # Score records, JSON round trip, win recording
└── assets/                 # Fonts (+ OFL license files)

Notes

  • NO rm -rf or starting over
  • Make small, incremental changes
  • Test after each change (just check, just test, then just run)
  • Keep existing functionality working
  • Commit after each working step