gemba 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/THIRD_PARTY_NOTICES +113 -0
  3. data/assets/JetBrainsMonoNL-Regular.ttf +0 -0
  4. data/assets/ark-pixel-12px-monospaced-ja.ttf +0 -0
  5. data/bin/gemba +14 -0
  6. data/ext/gemba/extconf.rb +185 -0
  7. data/ext/gemba/gemba_ext.c +1051 -0
  8. data/ext/gemba/gemba_ext.h +15 -0
  9. data/gemba.gemspec +38 -0
  10. data/lib/gemba/child_window.rb +62 -0
  11. data/lib/gemba/cli.rb +384 -0
  12. data/lib/gemba/config.rb +621 -0
  13. data/lib/gemba/core.rb +121 -0
  14. data/lib/gemba/headless.rb +12 -0
  15. data/lib/gemba/headless_player.rb +206 -0
  16. data/lib/gemba/hotkey_map.rb +202 -0
  17. data/lib/gemba/input_mappings.rb +214 -0
  18. data/lib/gemba/locale.rb +92 -0
  19. data/lib/gemba/locales/en.yml +157 -0
  20. data/lib/gemba/locales/ja.yml +157 -0
  21. data/lib/gemba/method_coverage_service.rb +265 -0
  22. data/lib/gemba/overlay_renderer.rb +109 -0
  23. data/lib/gemba/player.rb +1515 -0
  24. data/lib/gemba/recorder.rb +156 -0
  25. data/lib/gemba/recorder_decoder.rb +325 -0
  26. data/lib/gemba/rom_info_window.rb +346 -0
  27. data/lib/gemba/rom_loader.rb +100 -0
  28. data/lib/gemba/runtime.rb +39 -0
  29. data/lib/gemba/save_state_manager.rb +155 -0
  30. data/lib/gemba/save_state_picker.rb +199 -0
  31. data/lib/gemba/settings_window.rb +1173 -0
  32. data/lib/gemba/tip_service.rb +133 -0
  33. data/lib/gemba/toast_overlay.rb +128 -0
  34. data/lib/gemba/version.rb +5 -0
  35. data/lib/gemba.rb +17 -0
  36. data/test/fixtures/test.gba +0 -0
  37. data/test/fixtures/test.sav +0 -0
  38. data/test/shared/screenshot_helper.rb +113 -0
  39. data/test/shared/simplecov_config.rb +59 -0
  40. data/test/shared/teek_test_worker.rb +388 -0
  41. data/test/shared/tk_test_helper.rb +354 -0
  42. data/test/support/input_mocks.rb +61 -0
  43. data/test/support/player_helpers.rb +77 -0
  44. data/test/test_cli.rb +281 -0
  45. data/test/test_config.rb +897 -0
  46. data/test/test_core.rb +401 -0
  47. data/test/test_gamepad_map.rb +116 -0
  48. data/test/test_headless_player.rb +205 -0
  49. data/test/test_helper.rb +19 -0
  50. data/test/test_hotkey_map.rb +396 -0
  51. data/test/test_keyboard_map.rb +108 -0
  52. data/test/test_locale.rb +159 -0
  53. data/test/test_mgba.rb +26 -0
  54. data/test/test_overlay_renderer.rb +199 -0
  55. data/test/test_player.rb +903 -0
  56. data/test/test_recorder.rb +180 -0
  57. data/test/test_rom_loader.rb +149 -0
  58. data/test/test_save_state_manager.rb +289 -0
  59. data/test/test_settings_hotkeys.rb +434 -0
  60. data/test/test_settings_window.rb +1039 -0
  61. data/test/test_tip_service.rb +138 -0
  62. data/test/test_toast_overlay.rb +216 -0
  63. data/test/test_virtual_keyboard.rb +39 -0
  64. data/test/test_xor_delta.rb +61 -0
  65. metadata +234 -0
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Gemba
6
+ # Virtual keyboard device that tracks key press/release state.
7
+ # Presents the same interface as an SDL gamepad: +button?+ and +closed?+.
8
+ class VirtualKeyboard
9
+ def initialize
10
+ @held = Set.new
11
+ end
12
+
13
+ def press(keysym) = @held.add(keysym)
14
+ def release(keysym) = @held.delete(keysym)
15
+ def button?(keysym) = @held.include?(keysym)
16
+ def closed? = false
17
+ end
18
+
19
+ # GBA button label → bitmask (shared by KeyboardMap and GamepadMap)
20
+ GBA_BTN_BITS = {
21
+ a: KEY_A, b: KEY_B,
22
+ l: KEY_L, r: KEY_R,
23
+ up: KEY_UP, down: KEY_DOWN,
24
+ left: KEY_LEFT, right: KEY_RIGHT,
25
+ start: KEY_START, select: KEY_SELECT,
26
+ }.freeze
27
+
28
+ # Manages keyboard keysym → GBA bitmask mappings.
29
+ #
30
+ # Shares the same interface as {GamepadMap} so that Player can
31
+ # delegate to either without knowing which device type is active.
32
+ class KeyboardMap
33
+ DEFAULT_MAP = {
34
+ 'z' => KEY_A,
35
+ 'x' => KEY_B,
36
+ 'BackSpace' => KEY_SELECT,
37
+ 'Return' => KEY_START,
38
+ 'Right' => KEY_RIGHT,
39
+ 'Left' => KEY_LEFT,
40
+ 'Up' => KEY_UP,
41
+ 'Down' => KEY_DOWN,
42
+ 'a' => KEY_L,
43
+ 's' => KEY_R,
44
+ }.freeze
45
+
46
+ def initialize(config)
47
+ @config = config
48
+ @map = DEFAULT_MAP.dup
49
+ @device = nil
50
+ load_config
51
+ end
52
+
53
+ attr_writer :device
54
+
55
+ def mask
56
+ return 0 unless @device
57
+ m = 0
58
+ @map.each { |key, bit| m |= bit if @device.button?(key) }
59
+ m
60
+ end
61
+
62
+ def set(gba_btn, input_key)
63
+ bit = GBA_BTN_BITS[gba_btn] or return
64
+ @map.delete_if { |_, v| v == bit }
65
+ @map[input_key.to_s] = bit
66
+ end
67
+
68
+ def reset!
69
+ @map = DEFAULT_MAP.dup
70
+ end
71
+
72
+ def load_config
73
+ cfg = @config.mappings(Config::KEYBOARD_GUID)
74
+ if cfg.empty?
75
+ @map = DEFAULT_MAP.dup
76
+ else
77
+ @map = {}
78
+ cfg.each do |gba_str, keysym|
79
+ bit = GBA_BTN_BITS[gba_str.to_sym]
80
+ next unless bit
81
+ @map[keysym] = bit
82
+ end
83
+ end
84
+ end
85
+
86
+ def reload!
87
+ @config.reload!
88
+ load_config
89
+ end
90
+
91
+ def labels
92
+ result = {}
93
+ @map.each do |input, bit|
94
+ gba_btn = GBA_BTN_BITS.key(bit)
95
+ result[gba_btn] = input if gba_btn
96
+ end
97
+ result
98
+ end
99
+
100
+ def save_to_config
101
+ @map.each do |input, bit|
102
+ gba_btn = GBA_BTN_BITS.key(bit)
103
+ @config.set_mapping(Config::KEYBOARD_GUID, gba_btn, input) if gba_btn
104
+ end
105
+ end
106
+
107
+ def supports_deadzone? = false
108
+ def dead_zone_pct = 0
109
+
110
+ def set_dead_zone(_)
111
+ raise NotImplementedError, "keyboard does not support dead zones"
112
+ end
113
+ end
114
+
115
+ # Manages SDL gamepad button → GBA bitmask mappings.
116
+ #
117
+ # Shares the same interface as {KeyboardMap} so that Player can
118
+ # delegate to either without knowing which device type is active.
119
+ class GamepadMap
120
+ DEFAULT_MAP = {
121
+ a: KEY_A,
122
+ b: KEY_B,
123
+ back: KEY_SELECT,
124
+ start: KEY_START,
125
+ dpad_up: KEY_UP,
126
+ dpad_down: KEY_DOWN,
127
+ dpad_left: KEY_LEFT,
128
+ dpad_right: KEY_RIGHT,
129
+ left_shoulder: KEY_L,
130
+ right_shoulder: KEY_R,
131
+ }.freeze
132
+
133
+ DEFAULT_DEAD_ZONE = 8000
134
+
135
+ def initialize(config)
136
+ @config = config
137
+ @map = DEFAULT_MAP.dup
138
+ @device = nil
139
+ @dead_zone = DEFAULT_DEAD_ZONE
140
+ end
141
+
142
+ attr_accessor :device
143
+ attr_reader :dead_zone
144
+
145
+ def mask
146
+ return 0 unless @device && !@device.closed?
147
+ m = 0
148
+ @map.each { |btn, bit| m |= bit if @device.button?(btn) }
149
+ m
150
+ end
151
+
152
+ def set(gba_btn, gp_btn)
153
+ bit = GBA_BTN_BITS[gba_btn] or return
154
+ @map.delete_if { |_, v| v == bit }
155
+ @map[gp_btn] = bit
156
+ end
157
+
158
+ def reset!
159
+ @map = DEFAULT_MAP.dup
160
+ @dead_zone = DEFAULT_DEAD_ZONE
161
+ end
162
+
163
+ def load_config
164
+ return unless @device
165
+ guid = @device.guid rescue return
166
+ gp_cfg = @config.gamepad(guid, name: @device.name)
167
+
168
+ @map = {}
169
+ gp_cfg['mappings'].each do |gba_str, gp_str|
170
+ bit = GBA_BTN_BITS[gba_str.to_sym]
171
+ next unless bit
172
+ @map[gp_str.to_sym] = bit
173
+ end
174
+
175
+ pct = gp_cfg['dead_zone']
176
+ @dead_zone = (pct / 100.0 * 32767).round
177
+ end
178
+
179
+ def reload!
180
+ @config.reload!
181
+ load_config
182
+ end
183
+
184
+ def labels
185
+ result = {}
186
+ @map.each do |input, bit|
187
+ gba_btn = GBA_BTN_BITS.key(bit)
188
+ result[gba_btn] = input.to_s if gba_btn
189
+ end
190
+ result
191
+ end
192
+
193
+ def save_to_config
194
+ return unless @device
195
+ guid = @device.guid rescue return
196
+ @config.gamepad(guid, name: @device.name)
197
+ @config.set_dead_zone(guid, dead_zone_pct)
198
+ @map.each do |gp_btn, bit|
199
+ gba_btn = GBA_BTN_BITS.key(bit)
200
+ @config.set_mapping(guid, gba_btn, gp_btn) if gba_btn
201
+ end
202
+ end
203
+
204
+ def supports_deadzone? = true
205
+
206
+ def dead_zone_pct
207
+ (@dead_zone.to_f / 32767 * 100).round
208
+ end
209
+
210
+ def set_dead_zone(threshold)
211
+ @dead_zone = threshold.to_i
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Gemba
6
+ # Lightweight YAML-backed localization. No external gem dependencies.
7
+ #
8
+ # @example
9
+ # Locale.load('ja')
10
+ # Locale.translate('menu.file') #=> "ファイル"
11
+ # Locale.translate('toast.state_saved', slot: 3) #=> "スロット3にステートをセーブしました"
12
+ module Locale
13
+ # Load translations for the given language code.
14
+ # Falls back to English if the requested locale file doesn't exist.
15
+ #
16
+ # @param lang [String, nil] two-letter language code (e.g. 'en', 'ja').
17
+ # When nil, auto-detects from the OS environment.
18
+ # @return [String] the language code that was loaded
19
+ def self.load(lang = nil)
20
+ lang = detect_language if lang.nil? || lang == 'auto'
21
+ path = locale_path(lang)
22
+ path = locale_path('en') unless File.exist?(path)
23
+ @strings = YAML.safe_load_file(path)
24
+ @lang = lang
25
+ end
26
+
27
+ # Look up a translation by dot-separated key, with optional variable
28
+ # interpolation. Returns the key itself if no translation is found.
29
+ #
30
+ # @param key [String] dot-separated path (e.g. 'menu.file', 'toast.state_saved')
31
+ # @param vars [Hash] interpolation variables — replaces `{name}` in the string
32
+ # @return [String]
33
+ def self.translate(key, **vars)
34
+ parts = key.split('.')
35
+ str = @strings&.dig(*parts)
36
+ return key unless str.is_a?(String)
37
+
38
+ vars.each { |k, v| str = str.gsub("{#{k}}", v.to_s) }
39
+ str
40
+ end
41
+
42
+ class << self
43
+ alias_method :t, :translate
44
+ end
45
+
46
+ # @return [String] the currently loaded language code
47
+ def self.language
48
+ @lang
49
+ end
50
+
51
+ # @return [Array<String>] sorted list of available language codes
52
+ def self.available_languages
53
+ Dir[locale_path('*')].map { |f| File.basename(f, '.yml') }.sort
54
+ end
55
+
56
+ # Detect the user's preferred language from environment variables.
57
+ # @return [String] two-letter language code (e.g. 'en', 'ja')
58
+ # @api private
59
+ private_class_method def self.detect_language
60
+ env = ENV['LANG'] || ENV['LC_ALL'] || ENV['LANGUAGE'] || 'en'
61
+ env[0, 2].downcase
62
+ end
63
+
64
+ # @api private
65
+ private_class_method def self.locale_path(lang)
66
+ File.join(__dir__, 'locales', "#{lang}.yml")
67
+ end
68
+
69
+ # Auto-load English on first require so translate() always works,
70
+ # even when loaded outside the full Gemba boot path.
71
+ load('en') unless @strings
72
+
73
+ # Mixin for classes that need translation access.
74
+ # Include this to call `translate` / `t` as instance methods.
75
+ #
76
+ # @example
77
+ # class Player
78
+ # include Gemba::Locale::Translatable
79
+ # def build_menu
80
+ # label: translate('menu.file')
81
+ # end
82
+ # end
83
+ module Translatable
84
+ private
85
+
86
+ def translate(key, **vars)
87
+ Locale.translate(key, **vars)
88
+ end
89
+ alias_method :t, :translate
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,157 @@
1
+ menu:
2
+ file: "File"
3
+ open_rom: "Open ROM…"
4
+ recent: "Recent"
5
+ quit: "Quit"
6
+ settings: "Settings"
7
+ view: "View"
8
+ fullscreen: "Fullscreen"
9
+ rom_info: "ROM Info…"
10
+ emulation: "Emulation"
11
+ pause: "Pause"
12
+ resume: "Resume"
13
+ reset: "Reset"
14
+ quick_save: "Quick Save"
15
+ quick_load: "Quick Load"
16
+ save_states: "Save States…"
17
+ start_recording: "Start Recording"
18
+ stop_recording: "Stop Recording"
19
+
20
+ toast:
21
+ save_blocked: "Save blocked (too fast)"
22
+ state_saved: "State saved to slot {slot}"
23
+ save_failed: "Failed to save state"
24
+ no_state: "No state in slot {slot}"
25
+ state_loaded: "State loaded from slot {slot}"
26
+ load_failed: "Failed to load state"
27
+ loaded_sav: "Loaded {name}"
28
+ created_sav: "Created {name}"
29
+ screenshot_saved: "Screenshot saved: {name}"
30
+ screenshot_failed: "Screenshot failed"
31
+ rewound: "Rewound"
32
+ no_rewind: "No rewind data"
33
+ paused: "Paused"
34
+ waiting_for: "Waiting for {label}…"
35
+ recording_started: "Recording started"
36
+ recording_stopped: "Recorded {frames} frames"
37
+
38
+ dialog:
39
+ game_running_title: "Game Running"
40
+ game_running_msg: "Another game is running. Switch to {name}?"
41
+ drop_error_title: "Drop Error"
42
+ drop_single_file_only: "Please drop a single ROM file."
43
+ drop_unsupported_type: "Unsupported file type: {ext}"
44
+ no_rom_in_zip: "No ROM file found in {name}"
45
+ multiple_roms_in_zip: "Multiple ROM files found in {name}"
46
+ zip_read_error: "Cannot read ZIP file: {detail}"
47
+ rom_not_found_title: "ROM Not Found"
48
+ rom_not_found_msg: "The ROM file no longer exists:\n{path}"
49
+ reset_gamepad_title: "Reset Gamepad"
50
+ reset_gamepad_msg: "Reset all gamepad mappings and dead zone to defaults?"
51
+ reset_hotkeys_title: "Reset Hotkeys"
52
+ reset_hotkeys_msg: "Reset all hotkey bindings to defaults?"
53
+ key_conflict_title: "Key Conflict"
54
+
55
+ settings:
56
+ video: "Video"
57
+ audio: "Audio"
58
+ gamepad: "Gamepad"
59
+ save_states: "Save States"
60
+ window_scale: "Window Scale:"
61
+ turbo_speed: "Turbo Speed:"
62
+ toast_duration: "Toast Duration:"
63
+ maintain_aspect: "Maintain aspect ratio"
64
+ show_fps: "Show FPS"
65
+ pause_on_focus_loss: "Pause when window loses focus"
66
+ volume: "Volume:"
67
+ mute: "Mute"
68
+ dead_zone: "Dead zone:"
69
+ undo: "Undo"
70
+ reset_defaults: "Reset to Defaults"
71
+ quick_save_slot: "Quick Save Slot:"
72
+ keep_backup: "Keep backup of previous save state"
73
+ save: "Save"
74
+ key_up: "↑ Up"
75
+ key_down: "↓ Down"
76
+ key_left: "← Left"
77
+ key_right: "→ Right"
78
+ keyboard_only: "Keyboard Only"
79
+ no_gamepad: "No gamepad detected"
80
+ uncapped: "Uncapped"
81
+ press: "Press…"
82
+ open_config_folder: "Open Config Folder…"
83
+ hotkeys: "Hotkeys"
84
+ hk_quit: "Quit"
85
+ hk_pause: "Pause"
86
+ hk_fast_forward: "Fast Forward"
87
+ hk_fullscreen: "Fullscreen"
88
+ hk_show_fps: "Show FPS"
89
+ hk_quick_save: "Quick Save"
90
+ hk_quick_load: "Quick Load"
91
+ hk_save_states: "Save States"
92
+ hk_screenshot: "Screenshot"
93
+ hk_rewind: "Rewind"
94
+ hk_record: "Record"
95
+ hk_reset_defaults: "Reset to Defaults"
96
+ pixel_filter: "Pixel Filter:"
97
+ filter_nearest: "Nearest Neighbor"
98
+ filter_linear: "Bilinear"
99
+ integer_scale: "Integer scaling (no fractional pixels)"
100
+ color_correction: "GBA color correction"
101
+ frame_blending: "Frame blending (LCD ghosting)"
102
+ rewind: "Rewind (jump back in time)"
103
+ tip_rewind: "Saves periodic snapshots during gameplay.\nPress the rewind hotkey to jump back ~10 seconds."
104
+ per_game: "Per-game settings"
105
+ tip_pixel_filter: "Nearest: sharp pixels (original look). Bilinear: smoothed, slightly blurry."
106
+ tip_integer_scale: "Scales by whole numbers only to avoid blurry sub-pixel artifacts."
107
+ tip_color_correction: "Adjusts colors to match the original GBA LCD gamma and color crosstalk."
108
+ tip_frame_blending: "Blends consecutive frames to simulate the GBA LCD's slow pixel response.\nFixes flickering transparency effects in some games (Golden Sun, F-Zero)."
109
+ tip_dead_zone: "How far the analog stick must move before registering input.\nIncrease if you experience phantom inputs."
110
+ tip_keep_backup: "Saves a backup copy before overwriting a save state."
111
+ tip_per_game: "Save separate video, audio, and save state settings for each ROM."
112
+ tip_turbo_speed: "Fast-forward speed when holding the turbo hotkey."
113
+ tip_toast_duration: "How long on-screen notifications stay visible."
114
+ recording: "Recording"
115
+ recording_compression: "Compression:"
116
+ tip_recording_compression: "Zlib level for .grec files.\n1 = fastest (default), 6+ has diminishing returns."
117
+ recordings_folder: "Recordings folder:"
118
+ open_recordings_folder: "Open Recordings Folder…"
119
+ gp_a: "A"
120
+ gp_b: "B"
121
+ gp_l: "L"
122
+ gp_r: "R"
123
+ gp_up: "Up"
124
+ gp_down: "Down"
125
+ gp_left: "Left"
126
+ gp_right: "Right"
127
+ gp_start: "Start"
128
+ gp_select: "Select"
129
+
130
+ picker:
131
+ title: "Save States"
132
+ empty: "Empty"
133
+ no_preview: "No preview"
134
+ slot: "Slot {n}"
135
+ close: "Close"
136
+
137
+ rom_info:
138
+ title: "ROM Info"
139
+ field_title: "Title:"
140
+ game_code: "Game Code:"
141
+ publisher: "Publisher:"
142
+ platform: "Platform:"
143
+ rom_size: "ROM Size:"
144
+ checksum: "Checksum:"
145
+ rom_file: "ROM File:"
146
+ save_file: "Save File:"
147
+ resolution: "Resolution:"
148
+ close: "Close"
149
+ na: "N/A"
150
+
151
+ player:
152
+ open_rom_hint: "File > Open ROM…"
153
+ fps: "{fps} fps"
154
+ ff: ">> {speed}x"
155
+ ff_max: ">> MAX"
156
+ none: "(none)"
157
+ clear: "Clear"
@@ -0,0 +1,157 @@
1
+ menu:
2
+ file: "ファイル"
3
+ open_rom: "ROMを開く…"
4
+ recent: "最近使ったファイル"
5
+ quit: "終了"
6
+ settings: "設定"
7
+ view: "表示"
8
+ fullscreen: "フルスクリーン"
9
+ rom_info: "ROM情報…"
10
+ emulation: "エミュレーション"
11
+ pause: "一時停止"
12
+ resume: "再開"
13
+ reset: "リセット"
14
+ quick_save: "クイックセーブ"
15
+ quick_load: "クイックロード"
16
+ save_states: "ステートセーブ…"
17
+ start_recording: "録画開始"
18
+ stop_recording: "録画停止"
19
+
20
+ toast:
21
+ save_blocked: "セーブ制限中(間隔が短すぎます)"
22
+ state_saved: "スロット{slot}にステートをセーブしました"
23
+ save_failed: "ステートセーブに失敗しました"
24
+ no_state: "スロット{slot}にステートがありません"
25
+ state_loaded: "スロット{slot}からステートをロードしました"
26
+ load_failed: "ステートロードに失敗しました"
27
+ loaded_sav: "{name}を読み込みました"
28
+ created_sav: "{name}を作成しました"
29
+ screenshot_saved: "スクリーンショットを保存: {name}"
30
+ screenshot_failed: "スクリーンショットに失敗しました"
31
+ rewound: "巻き戻しました"
32
+ no_rewind: "巻き戻しデータがありません"
33
+ paused: "一時停止"
34
+ waiting_for: "{label}を待機中…"
35
+ recording_started: "録画を開始しました"
36
+ recording_stopped: "{frames}フレームを録画しました"
37
+
38
+ dialog:
39
+ game_running_title: "ゲーム実行中"
40
+ game_running_msg: "別のゲームが実行中です。{name}に切り替えますか?"
41
+ drop_error_title: "ドロップエラー"
42
+ drop_single_file_only: "ROMファイルを1つだけドロップしてください。"
43
+ drop_unsupported_type: "対応していないファイル形式: {ext}"
44
+ no_rom_in_zip: "{name}にROMファイルが見つかりません"
45
+ multiple_roms_in_zip: "{name}に複数のROMファイルがあります"
46
+ zip_read_error: "ZIPファイルを読み込めません: {detail}"
47
+ rom_not_found_title: "ROMが見つかりません"
48
+ rom_not_found_msg: "ROMファイルが存在しません:\n{path}"
49
+ reset_gamepad_title: "ゲームパッドをリセット"
50
+ reset_gamepad_msg: "すべてのゲームパッド設定をデフォルトに戻しますか?"
51
+ reset_hotkeys_title: "ホットキーをリセット"
52
+ reset_hotkeys_msg: "すべてのホットキー設定をデフォルトに戻しますか?"
53
+ key_conflict_title: "キーの競合"
54
+
55
+ settings:
56
+ video: "映像"
57
+ audio: "音声"
58
+ gamepad: "ゲームパッド"
59
+ save_states: "ステートセーブ"
60
+ window_scale: "ウィンドウ倍率:"
61
+ turbo_speed: "早送り速度:"
62
+ toast_duration: "通知表示時間:"
63
+ maintain_aspect: "アスペクト比を維持"
64
+ show_fps: "FPSを表示"
65
+ pause_on_focus_loss: "ウィンドウのフォーカスが外れたら一時停止"
66
+ volume: "音量:"
67
+ mute: "ミュート"
68
+ dead_zone: "デッドゾーン:"
69
+ undo: "元に戻す"
70
+ reset_defaults: "デフォルトに戻す"
71
+ quick_save_slot: "クイックセーブスロット:"
72
+ keep_backup: "ステートセーブのバックアップを保持"
73
+ save: "保存"
74
+ key_up: "↑ 上"
75
+ key_down: "↓ 下"
76
+ key_left: "← 左"
77
+ key_right: "→ 右"
78
+ keyboard_only: "キーボードのみ"
79
+ no_gamepad: "ゲームパッドが検出されません"
80
+ uncapped: "無制限"
81
+ press: "入力待ち…"
82
+ open_config_folder: "設定フォルダを開く…"
83
+ hotkeys: "ホットキー"
84
+ hk_quit: "終了"
85
+ hk_pause: "一時停止"
86
+ hk_fast_forward: "早送り"
87
+ hk_fullscreen: "フルスクリーン"
88
+ hk_show_fps: "FPS表示"
89
+ hk_quick_save: "クイックセーブ"
90
+ hk_quick_load: "クイックロード"
91
+ hk_save_states: "ステートセーブ"
92
+ hk_screenshot: "スクリーンショット"
93
+ hk_rewind: "巻き戻し"
94
+ hk_record: "録画"
95
+ hk_reset_defaults: "デフォルトに戻す"
96
+ pixel_filter: "ピクセルフィルタ:"
97
+ filter_nearest: "ニアレストネイバー"
98
+ filter_linear: "バイリニア"
99
+ integer_scale: "整数スケーリング(端数ピクセルなし)"
100
+ color_correction: "GBA色補正"
101
+ frame_blending: "フレームブレンド(LCD残像)"
102
+ rewind: "巻き戻し(過去に戻る)"
103
+ tip_rewind: "プレイ中に定期的にスナップショットを保存します。\n巻き戻しホットキーで約10秒前に戻れます。"
104
+ per_game: "ゲーム別設定"
105
+ tip_pixel_filter: "ニアレスト: シャープなピクセル(原寸)。バイリニア: 滑らか、やや不鮮明。"
106
+ tip_integer_scale: "整数倍のみでスケーリングし、ぼやけたサブピクセルを防止します。"
107
+ tip_color_correction: "GBA液晶のガンマとカラークロストークに合わせて色を補正します。"
108
+ tip_frame_blending: "連続フレームをブレンドしてGBA液晶の遅い応答を再現します。\n一部のゲーム(黄金の太陽、F-Zero)のちらつき透明効果を修正します。"
109
+ tip_dead_zone: "入力を認識するまでのアナログスティックの遊び幅。\n誤入力が発生する場合は増やしてください。"
110
+ tip_keep_backup: "ステートを上書きする前にバックアップを保存します。"
111
+ tip_per_game: "ROM毎に映像・音声・ステートセーブの設定を個別に保存します。"
112
+ tip_turbo_speed: "早送りホットキー使用時の速度。"
113
+ tip_toast_duration: "画面上の通知の表示時間。"
114
+ recording: "録画"
115
+ recording_compression: "圧縮レベル:"
116
+ tip_recording_compression: ".grecファイルのzlib圧縮レベル。\n1 = 最速(デフォルト)、6以上は効果が小さくなります。"
117
+ recordings_folder: "録画フォルダ:"
118
+ open_recordings_folder: "録画フォルダを開く…"
119
+ gp_a: "A"
120
+ gp_b: "B"
121
+ gp_l: "L"
122
+ gp_r: "R"
123
+ gp_up: "上"
124
+ gp_down: "下"
125
+ gp_left: "左"
126
+ gp_right: "右"
127
+ gp_start: "スタート"
128
+ gp_select: "セレクト"
129
+
130
+ picker:
131
+ title: "ステートセーブ"
132
+ empty: "空"
133
+ no_preview: "プレビューなし"
134
+ slot: "スロット{n}"
135
+ close: "閉じる"
136
+
137
+ rom_info:
138
+ title: "ROM情報"
139
+ field_title: "タイトル:"
140
+ game_code: "ゲームコード:"
141
+ publisher: "メーカー:"
142
+ platform: "プラットフォーム:"
143
+ rom_size: "ROMサイズ:"
144
+ checksum: "チェックサム:"
145
+ rom_file: "ROMファイル:"
146
+ save_file: "セーブファイル:"
147
+ resolution: "解像度:"
148
+ close: "閉じる"
149
+ na: "N/A"
150
+
151
+ player:
152
+ open_rom_hint: "ファイル > ROMを開く…"
153
+ fps: "{fps} fps"
154
+ ff: ">> {speed}x"
155
+ ff_max: ">> 最大"
156
+ none: "(なし)"
157
+ clear: "クリア"