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.
- checksums.yaml +7 -0
- data/THIRD_PARTY_NOTICES +113 -0
- data/assets/JetBrainsMonoNL-Regular.ttf +0 -0
- data/assets/ark-pixel-12px-monospaced-ja.ttf +0 -0
- data/bin/gemba +14 -0
- data/ext/gemba/extconf.rb +185 -0
- data/ext/gemba/gemba_ext.c +1051 -0
- data/ext/gemba/gemba_ext.h +15 -0
- data/gemba.gemspec +38 -0
- data/lib/gemba/child_window.rb +62 -0
- data/lib/gemba/cli.rb +384 -0
- data/lib/gemba/config.rb +621 -0
- data/lib/gemba/core.rb +121 -0
- data/lib/gemba/headless.rb +12 -0
- data/lib/gemba/headless_player.rb +206 -0
- data/lib/gemba/hotkey_map.rb +202 -0
- data/lib/gemba/input_mappings.rb +214 -0
- data/lib/gemba/locale.rb +92 -0
- data/lib/gemba/locales/en.yml +157 -0
- data/lib/gemba/locales/ja.yml +157 -0
- data/lib/gemba/method_coverage_service.rb +265 -0
- data/lib/gemba/overlay_renderer.rb +109 -0
- data/lib/gemba/player.rb +1515 -0
- data/lib/gemba/recorder.rb +156 -0
- data/lib/gemba/recorder_decoder.rb +325 -0
- data/lib/gemba/rom_info_window.rb +346 -0
- data/lib/gemba/rom_loader.rb +100 -0
- data/lib/gemba/runtime.rb +39 -0
- data/lib/gemba/save_state_manager.rb +155 -0
- data/lib/gemba/save_state_picker.rb +199 -0
- data/lib/gemba/settings_window.rb +1173 -0
- data/lib/gemba/tip_service.rb +133 -0
- data/lib/gemba/toast_overlay.rb +128 -0
- data/lib/gemba/version.rb +5 -0
- data/lib/gemba.rb +17 -0
- data/test/fixtures/test.gba +0 -0
- data/test/fixtures/test.sav +0 -0
- data/test/shared/screenshot_helper.rb +113 -0
- data/test/shared/simplecov_config.rb +59 -0
- data/test/shared/teek_test_worker.rb +388 -0
- data/test/shared/tk_test_helper.rb +354 -0
- data/test/support/input_mocks.rb +61 -0
- data/test/support/player_helpers.rb +77 -0
- data/test/test_cli.rb +281 -0
- data/test/test_config.rb +897 -0
- data/test/test_core.rb +401 -0
- data/test/test_gamepad_map.rb +116 -0
- data/test/test_headless_player.rb +205 -0
- data/test/test_helper.rb +19 -0
- data/test/test_hotkey_map.rb +396 -0
- data/test/test_keyboard_map.rb +108 -0
- data/test/test_locale.rb +159 -0
- data/test/test_mgba.rb +26 -0
- data/test/test_overlay_renderer.rb +199 -0
- data/test/test_player.rb +903 -0
- data/test/test_recorder.rb +180 -0
- data/test/test_rom_loader.rb +149 -0
- data/test/test_save_state_manager.rb +289 -0
- data/test/test_settings_hotkeys.rb +434 -0
- data/test/test_settings_window.rb +1039 -0
- data/test/test_tip_service.rb +138 -0
- data/test/test_toast_overlay.rb +216 -0
- data/test/test_virtual_keyboard.rb +39 -0
- data/test/test_xor_delta.rb +61 -0
- metadata +234 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require_relative "shared/tk_test_helper"
|
|
5
|
+
|
|
6
|
+
class TestTipService < Minitest::Test
|
|
7
|
+
include TeekTestHelper
|
|
8
|
+
|
|
9
|
+
def test_register_sets_underline_font
|
|
10
|
+
assert_tk_app("register sets underlined font") do
|
|
11
|
+
require "gemba/tip_service"
|
|
12
|
+
tips = Gemba::TipService.new(app)
|
|
13
|
+
|
|
14
|
+
lbl = ".test_lbl"
|
|
15
|
+
app.command('ttk::label', lbl, text: "Test")
|
|
16
|
+
app.command(:pack, lbl)
|
|
17
|
+
app.update
|
|
18
|
+
|
|
19
|
+
tips.register(lbl, "Help text")
|
|
20
|
+
app.update
|
|
21
|
+
|
|
22
|
+
font = app.command(lbl, 'cget', '-font')
|
|
23
|
+
underline = app.tcl_eval("font actual #{font} -underline")
|
|
24
|
+
assert_equal '1', underline
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_show_creates_tooltip
|
|
29
|
+
assert_tk_app("show creates a tooltip toplevel") do
|
|
30
|
+
require "gemba/tip_service"
|
|
31
|
+
tips = Gemba::TipService.new(app)
|
|
32
|
+
|
|
33
|
+
lbl = ".test_lbl"
|
|
34
|
+
app.command('ttk::label', lbl, text: "Test")
|
|
35
|
+
app.command(:pack, lbl)
|
|
36
|
+
app.show
|
|
37
|
+
app.update
|
|
38
|
+
|
|
39
|
+
refute tips.showing?
|
|
40
|
+
tips.show(lbl, "Help text")
|
|
41
|
+
app.update
|
|
42
|
+
|
|
43
|
+
assert tips.showing?
|
|
44
|
+
assert_equal lbl, tips.target
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_hide_destroys_tooltip
|
|
49
|
+
assert_tk_app("hide destroys tooltip") do
|
|
50
|
+
require "gemba/tip_service"
|
|
51
|
+
tips = Gemba::TipService.new(app)
|
|
52
|
+
|
|
53
|
+
lbl = ".test_lbl"
|
|
54
|
+
app.command('ttk::label', lbl, text: "Test")
|
|
55
|
+
app.command(:pack, lbl)
|
|
56
|
+
app.show
|
|
57
|
+
app.update
|
|
58
|
+
|
|
59
|
+
tips.show(lbl, "Help text")
|
|
60
|
+
app.update
|
|
61
|
+
assert tips.showing?
|
|
62
|
+
|
|
63
|
+
tips.hide
|
|
64
|
+
app.update
|
|
65
|
+
refute tips.showing?
|
|
66
|
+
assert_nil tips.target
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_toggle_behavior
|
|
71
|
+
assert_tk_app("clicking same label toggles tooltip") do
|
|
72
|
+
require "gemba/tip_service"
|
|
73
|
+
tips = Gemba::TipService.new(app)
|
|
74
|
+
|
|
75
|
+
lbl = ".test_lbl"
|
|
76
|
+
app.command('ttk::label', lbl, text: "Test")
|
|
77
|
+
app.command(:pack, lbl)
|
|
78
|
+
app.show
|
|
79
|
+
app.update
|
|
80
|
+
|
|
81
|
+
tips.register(lbl, "Help text")
|
|
82
|
+
app.update
|
|
83
|
+
|
|
84
|
+
# First click — show
|
|
85
|
+
app.command(:event, 'generate', lbl, '<Button-1>')
|
|
86
|
+
app.update
|
|
87
|
+
assert tips.showing?
|
|
88
|
+
assert_equal lbl, tips.target
|
|
89
|
+
|
|
90
|
+
# Second click — hide
|
|
91
|
+
app.command(:event, 'generate', lbl, '<Button-1>')
|
|
92
|
+
app.update
|
|
93
|
+
refute tips.showing?
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_only_one_tooltip_at_a_time
|
|
98
|
+
assert_tk_app("showing a second tooltip hides the first") do
|
|
99
|
+
require "gemba/tip_service"
|
|
100
|
+
tips = Gemba::TipService.new(app)
|
|
101
|
+
|
|
102
|
+
lbl1 = ".test_lbl1"
|
|
103
|
+
lbl2 = ".test_lbl2"
|
|
104
|
+
app.command('ttk::label', lbl1, text: "Label 1")
|
|
105
|
+
app.command('ttk::label', lbl2, text: "Label 2")
|
|
106
|
+
app.command(:pack, lbl1)
|
|
107
|
+
app.command(:pack, lbl2)
|
|
108
|
+
app.show
|
|
109
|
+
app.update
|
|
110
|
+
|
|
111
|
+
tips.register(lbl1, "Help 1")
|
|
112
|
+
tips.register(lbl2, "Help 2")
|
|
113
|
+
app.update
|
|
114
|
+
|
|
115
|
+
# Show first tooltip
|
|
116
|
+
tips.show(lbl1, "Help 1")
|
|
117
|
+
app.update
|
|
118
|
+
assert_equal lbl1, tips.target
|
|
119
|
+
|
|
120
|
+
# Show second — first should be gone
|
|
121
|
+
tips.show(lbl2, "Help 2")
|
|
122
|
+
app.update
|
|
123
|
+
assert_equal lbl2, tips.target
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def test_dismiss_ms_is_configurable
|
|
128
|
+
assert_tk_app("dismiss_ms is configurable") do
|
|
129
|
+
require "gemba/tip_service"
|
|
130
|
+
tips = Gemba::TipService.new(app, dismiss_ms: 2000)
|
|
131
|
+
|
|
132
|
+
assert_equal 2000, tips.dismiss_ms
|
|
133
|
+
|
|
134
|
+
tips.dismiss_ms = 5000
|
|
135
|
+
assert_equal 5000, tips.dismiss_ms
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require_relative "../lib/gemba/toast_overlay"
|
|
5
|
+
|
|
6
|
+
class TestToastOverlay < Minitest::Test
|
|
7
|
+
# Minimal texture mock — records destroy calls.
|
|
8
|
+
class MockTexture
|
|
9
|
+
attr_reader :calls, :width, :height
|
|
10
|
+
attr_accessor :blend_mode
|
|
11
|
+
|
|
12
|
+
def initialize(w, h)
|
|
13
|
+
@width = w
|
|
14
|
+
@height = h
|
|
15
|
+
@blend_mode = nil
|
|
16
|
+
@calls = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def update(pixels)
|
|
20
|
+
@calls << [:update, pixels.bytesize]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def destroy
|
|
24
|
+
@calls << [:destroy]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Recording renderer mock.
|
|
29
|
+
class MockRenderer
|
|
30
|
+
attr_reader :calls, :textures
|
|
31
|
+
|
|
32
|
+
def initialize
|
|
33
|
+
@calls = []
|
|
34
|
+
@textures = []
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def create_texture(w, h, mode)
|
|
38
|
+
tex = MockTexture.new(w, h)
|
|
39
|
+
@textures << tex
|
|
40
|
+
@calls << [:create_texture, w, h, mode]
|
|
41
|
+
tex
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def output_size
|
|
45
|
+
[720, 480]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def copy(tex, src, dst)
|
|
49
|
+
@calls << [:copy, src, dst]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Recording font mock.
|
|
54
|
+
class MockFont
|
|
55
|
+
attr_reader :calls
|
|
56
|
+
|
|
57
|
+
def initialize
|
|
58
|
+
@calls = []
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def ascent
|
|
62
|
+
10
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def measure(_text)
|
|
66
|
+
[100, 14]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def render_text(text, r, g, b)
|
|
70
|
+
@calls << [:render_text, text, r, g, b]
|
|
71
|
+
MockTexture.new(text.length * 7, 14)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Stub for the C-implemented toast_background — returns dummy ARGB pixels.
|
|
76
|
+
STUB_BG_FN = ->(w, h, _radius) { "\x00".b * (w * h * 4) }
|
|
77
|
+
|
|
78
|
+
def setup
|
|
79
|
+
@renderer = MockRenderer.new
|
|
80
|
+
@font = MockFont.new
|
|
81
|
+
@toast = Gemba::ToastOverlay.new(
|
|
82
|
+
renderer: @renderer, font: @font, duration: 1.5, bg_fn: STUB_BG_FN
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# -- visibility ------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
def test_not_visible_initially
|
|
89
|
+
refute @toast.visible?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_visible_after_show
|
|
93
|
+
@toast.show("Hello")
|
|
94
|
+
assert @toast.visible?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_not_visible_after_destroy
|
|
98
|
+
@toast.show("Hello")
|
|
99
|
+
@toast.destroy
|
|
100
|
+
refute @toast.visible?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# -- show ------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
def test_show_renders_text
|
|
106
|
+
@toast.show("Saved")
|
|
107
|
+
render_calls = @font.calls.select { |c| c[0] == :render_text }
|
|
108
|
+
assert_equal 1, render_calls.size
|
|
109
|
+
assert_equal "Saved", render_calls[0][1]
|
|
110
|
+
# White text
|
|
111
|
+
assert_equal [255, 255, 255], render_calls[0][2..4]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def test_show_creates_background_texture
|
|
115
|
+
@toast.show("Saved")
|
|
116
|
+
create_calls = @renderer.calls.select { |c| c[0] == :create_texture }
|
|
117
|
+
assert_equal 1, create_calls.size
|
|
118
|
+
# Background should be wider than text (padded)
|
|
119
|
+
assert create_calls[0][1] > 0
|
|
120
|
+
assert create_calls[0][2] > 0
|
|
121
|
+
assert_equal :streaming, create_calls[0][3]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_show_replaces_previous_toast
|
|
125
|
+
@toast.show("First")
|
|
126
|
+
@toast.show("Second")
|
|
127
|
+
# Should have rendered text twice
|
|
128
|
+
render_calls = @font.calls.select { |c| c[0] == :render_text }
|
|
129
|
+
assert_equal 2, render_calls.size
|
|
130
|
+
assert_equal "Second", render_calls[1][1]
|
|
131
|
+
# First toast's textures should have been destroyed
|
|
132
|
+
first_bg = @renderer.textures[0]
|
|
133
|
+
assert first_bg.calls.any? { |c| c[0] == :destroy }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# -- draw ------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
def test_draw_does_nothing_when_no_toast
|
|
139
|
+
r = MockRenderer.new
|
|
140
|
+
@toast.draw(r, nil)
|
|
141
|
+
assert_empty r.calls
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def test_draw_copies_bg_and_text
|
|
145
|
+
@toast.show("Hello")
|
|
146
|
+
r = MockRenderer.new
|
|
147
|
+
@toast.draw(r, [0, 0, 720, 480])
|
|
148
|
+
copy_calls = r.calls.select { |c| c[0] == :copy }
|
|
149
|
+
assert_equal 2, copy_calls.size # background + text
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def test_draw_centers_in_dest_rect
|
|
153
|
+
@toast.show("Hi")
|
|
154
|
+
r = MockRenderer.new
|
|
155
|
+
@toast.draw(r, [100, 0, 500, 400])
|
|
156
|
+
copy_calls = r.calls.select { |c| c[0] == :copy }
|
|
157
|
+
# Background dest rect x should be centered around 100 + 250 = 350
|
|
158
|
+
bg_dst = copy_calls[0][2]
|
|
159
|
+
cx = 100 + 500 / 2
|
|
160
|
+
assert_equal cx - bg_dst[2] / 2, bg_dst[0]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def test_draw_uses_output_size_when_no_dest
|
|
164
|
+
@toast.show("Hi")
|
|
165
|
+
r = MockRenderer.new
|
|
166
|
+
@toast.draw(r, nil)
|
|
167
|
+
copy_calls = r.calls.select { |c| c[0] == :copy }
|
|
168
|
+
bg_dst = copy_calls[0][2]
|
|
169
|
+
# Should center around output_size width / 2 = 360
|
|
170
|
+
assert_equal 360 - bg_dst[2] / 2, bg_dst[0]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# -- expiration ------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
def test_draw_destroys_expired_toast
|
|
176
|
+
@toast.show("Bye", duration: 0.0)
|
|
177
|
+
sleep 0.01 # ensure expired
|
|
178
|
+
r = MockRenderer.new
|
|
179
|
+
@toast.draw(r, nil)
|
|
180
|
+
# Should have self-destroyed, no copy calls
|
|
181
|
+
assert_empty r.calls.select { |c| c[0] == :copy }
|
|
182
|
+
refute @toast.visible?
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def test_permanent_toast_does_not_expire
|
|
186
|
+
@toast.show("Wait", permanent: true)
|
|
187
|
+
sleep 0.01
|
|
188
|
+
r = MockRenderer.new
|
|
189
|
+
@toast.draw(r, [0, 0, 720, 480])
|
|
190
|
+
copy_calls = r.calls.select { |c| c[0] == :copy }
|
|
191
|
+
assert_equal 2, copy_calls.size
|
|
192
|
+
assert @toast.visible?
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# -- destroy ---------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
def test_destroy_frees_textures
|
|
198
|
+
@toast.show("Hello")
|
|
199
|
+
bg_tex = @renderer.textures.last
|
|
200
|
+
@toast.destroy
|
|
201
|
+
assert bg_tex.calls.any? { |c| c[0] == :destroy }
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def test_destroy_is_safe_when_empty
|
|
205
|
+
@toast.destroy # no-op, should not raise
|
|
206
|
+
refute @toast.visible?
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# -- duration accessor -----------------------------------------------------
|
|
210
|
+
|
|
211
|
+
def test_duration_accessor
|
|
212
|
+
assert_in_delta 1.5, @toast.duration
|
|
213
|
+
@toast.duration = 3.0
|
|
214
|
+
assert_in_delta 3.0, @toast.duration
|
|
215
|
+
end
|
|
216
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require_relative "../lib/gemba/input_mappings"
|
|
5
|
+
|
|
6
|
+
class TestVirtualKeyboard < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
@kb = Gemba::VirtualKeyboard.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_button_false_initially
|
|
12
|
+
refute @kb.button?('z')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_press_makes_button_true
|
|
16
|
+
@kb.press('z')
|
|
17
|
+
assert @kb.button?('z')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_release_makes_button_false
|
|
21
|
+
@kb.press('z')
|
|
22
|
+
@kb.release('z')
|
|
23
|
+
refute @kb.button?('z')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_multiple_keys
|
|
27
|
+
@kb.press('z')
|
|
28
|
+
@kb.press('x')
|
|
29
|
+
assert @kb.button?('z')
|
|
30
|
+
assert @kb.button?('x')
|
|
31
|
+
@kb.release('z')
|
|
32
|
+
refute @kb.button?('z')
|
|
33
|
+
assert @kb.button?('x')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_never_closed
|
|
37
|
+
refute @kb.closed?
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "gemba/headless"
|
|
5
|
+
|
|
6
|
+
class TestXorDelta < Minitest::Test
|
|
7
|
+
def test_xor_identical_strings_produces_zeros
|
|
8
|
+
a = "\xFF\x00\xAB".b
|
|
9
|
+
b = "\xFF\x00\xAB".b
|
|
10
|
+
result = Gemba.xor_delta(a, b)
|
|
11
|
+
assert_equal "\x00\x00\x00".b, result
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_xor_against_zeros_returns_original
|
|
15
|
+
a = "\xDE\xAD\xBE\xEF".b
|
|
16
|
+
b = "\x00\x00\x00\x00".b
|
|
17
|
+
result = Gemba.xor_delta(a, b)
|
|
18
|
+
assert_equal a, result
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_xor_is_reversible
|
|
22
|
+
a = "hello world!".b
|
|
23
|
+
b = "goodbye!!!12".b
|
|
24
|
+
delta = Gemba.xor_delta(a, b)
|
|
25
|
+
restored = Gemba.xor_delta(delta, b)
|
|
26
|
+
assert_equal a, restored
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_xor_mismatched_lengths_raises
|
|
30
|
+
assert_raises(ArgumentError) do
|
|
31
|
+
Gemba.xor_delta("abc", "ab")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_xor_empty_strings
|
|
36
|
+
result = Gemba.xor_delta("".b, "".b)
|
|
37
|
+
assert_equal "".b, result
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# -- count_changed_pixels --
|
|
41
|
+
|
|
42
|
+
def test_count_all_zeros
|
|
43
|
+
delta = "\x00\x00\x00\x00\x00\x00\x00\x00".b # 2 pixels, both zero
|
|
44
|
+
assert_equal 0, Gemba.count_changed_pixels(delta)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_count_all_changed
|
|
48
|
+
delta = "\xFF\x00\x00\x00\x00\xFF\x00\x00".b # 2 pixels, both non-zero
|
|
49
|
+
assert_equal 2, Gemba.count_changed_pixels(delta)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_count_mixed
|
|
53
|
+
# 3 pixels: changed, zero, changed
|
|
54
|
+
delta = "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01".b
|
|
55
|
+
assert_equal 2, Gemba.count_changed_pixels(delta)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_count_empty
|
|
59
|
+
assert_equal 0, Gemba.count_changed_pixels("".b)
|
|
60
|
+
end
|
|
61
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: gemba
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- James Cook
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: teek
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.1.2
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.1.2
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: teek-sdl2
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 0.1.3
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 0.1.3
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '13.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rake-compiler
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: minitest
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '6.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '6.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: method_source
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '1.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '1.0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: simplecov
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0.22'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0.22'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: listen
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '3.0'
|
|
117
|
+
type: :development
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '3.0'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: rubyzip
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - ">="
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '2.4'
|
|
131
|
+
type: :development
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - ">="
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '2.4'
|
|
138
|
+
description: Wraps libmgba's mCore C API and provides a full-featured GBA player with
|
|
139
|
+
SDL2 rendering, input, save states, and a Tk-based settings UI
|
|
140
|
+
email:
|
|
141
|
+
- jcook.rubyist@gmail.com
|
|
142
|
+
executables:
|
|
143
|
+
- gemba
|
|
144
|
+
extensions:
|
|
145
|
+
- ext/gemba/extconf.rb
|
|
146
|
+
extra_rdoc_files: []
|
|
147
|
+
files:
|
|
148
|
+
- THIRD_PARTY_NOTICES
|
|
149
|
+
- assets/JetBrainsMonoNL-Regular.ttf
|
|
150
|
+
- assets/ark-pixel-12px-monospaced-ja.ttf
|
|
151
|
+
- bin/gemba
|
|
152
|
+
- ext/gemba/extconf.rb
|
|
153
|
+
- ext/gemba/gemba_ext.c
|
|
154
|
+
- ext/gemba/gemba_ext.h
|
|
155
|
+
- gemba.gemspec
|
|
156
|
+
- lib/gemba.rb
|
|
157
|
+
- lib/gemba/child_window.rb
|
|
158
|
+
- lib/gemba/cli.rb
|
|
159
|
+
- lib/gemba/config.rb
|
|
160
|
+
- lib/gemba/core.rb
|
|
161
|
+
- lib/gemba/headless.rb
|
|
162
|
+
- lib/gemba/headless_player.rb
|
|
163
|
+
- lib/gemba/hotkey_map.rb
|
|
164
|
+
- lib/gemba/input_mappings.rb
|
|
165
|
+
- lib/gemba/locale.rb
|
|
166
|
+
- lib/gemba/locales/en.yml
|
|
167
|
+
- lib/gemba/locales/ja.yml
|
|
168
|
+
- lib/gemba/method_coverage_service.rb
|
|
169
|
+
- lib/gemba/overlay_renderer.rb
|
|
170
|
+
- lib/gemba/player.rb
|
|
171
|
+
- lib/gemba/recorder.rb
|
|
172
|
+
- lib/gemba/recorder_decoder.rb
|
|
173
|
+
- lib/gemba/rom_info_window.rb
|
|
174
|
+
- lib/gemba/rom_loader.rb
|
|
175
|
+
- lib/gemba/runtime.rb
|
|
176
|
+
- lib/gemba/save_state_manager.rb
|
|
177
|
+
- lib/gemba/save_state_picker.rb
|
|
178
|
+
- lib/gemba/settings_window.rb
|
|
179
|
+
- lib/gemba/tip_service.rb
|
|
180
|
+
- lib/gemba/toast_overlay.rb
|
|
181
|
+
- lib/gemba/version.rb
|
|
182
|
+
- test/fixtures/test.gba
|
|
183
|
+
- test/fixtures/test.sav
|
|
184
|
+
- test/shared/screenshot_helper.rb
|
|
185
|
+
- test/shared/simplecov_config.rb
|
|
186
|
+
- test/shared/teek_test_worker.rb
|
|
187
|
+
- test/shared/tk_test_helper.rb
|
|
188
|
+
- test/support/input_mocks.rb
|
|
189
|
+
- test/support/player_helpers.rb
|
|
190
|
+
- test/test_cli.rb
|
|
191
|
+
- test/test_config.rb
|
|
192
|
+
- test/test_core.rb
|
|
193
|
+
- test/test_gamepad_map.rb
|
|
194
|
+
- test/test_headless_player.rb
|
|
195
|
+
- test/test_helper.rb
|
|
196
|
+
- test/test_hotkey_map.rb
|
|
197
|
+
- test/test_keyboard_map.rb
|
|
198
|
+
- test/test_locale.rb
|
|
199
|
+
- test/test_mgba.rb
|
|
200
|
+
- test/test_overlay_renderer.rb
|
|
201
|
+
- test/test_player.rb
|
|
202
|
+
- test/test_recorder.rb
|
|
203
|
+
- test/test_rom_loader.rb
|
|
204
|
+
- test/test_save_state_manager.rb
|
|
205
|
+
- test/test_settings_hotkeys.rb
|
|
206
|
+
- test/test_settings_window.rb
|
|
207
|
+
- test/test_tip_service.rb
|
|
208
|
+
- test/test_toast_overlay.rb
|
|
209
|
+
- test/test_virtual_keyboard.rb
|
|
210
|
+
- test/test_xor_delta.rb
|
|
211
|
+
homepage: https://github.com/jamescook/gemba
|
|
212
|
+
licenses:
|
|
213
|
+
- MIT
|
|
214
|
+
metadata: {}
|
|
215
|
+
rdoc_options: []
|
|
216
|
+
require_paths:
|
|
217
|
+
- lib
|
|
218
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
219
|
+
requirements:
|
|
220
|
+
- - ">="
|
|
221
|
+
- !ruby/object:Gem::Version
|
|
222
|
+
version: '3.2'
|
|
223
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
224
|
+
requirements:
|
|
225
|
+
- - ">="
|
|
226
|
+
- !ruby/object:Gem::Version
|
|
227
|
+
version: '0'
|
|
228
|
+
requirements:
|
|
229
|
+
- libmgba development headers
|
|
230
|
+
- rubyzip gem >= 2.4 (optional, for loading ROMs from .zip files)
|
|
231
|
+
rubygems_version: 4.0.3
|
|
232
|
+
specification_version: 4
|
|
233
|
+
summary: GBA emulator frontend powered by teek and libmgba
|
|
234
|
+
test_files: []
|