motioninput-tui 0.1.0__tar.gz

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 (61) hide show
  1. motioninput_tui-0.1.0/LICENSE +21 -0
  2. motioninput_tui-0.1.0/PKG-INFO +106 -0
  3. motioninput_tui-0.1.0/README.md +85 -0
  4. motioninput_tui-0.1.0/pyproject.toml +134 -0
  5. motioninput_tui-0.1.0/pyproject.toml.orig +142 -0
  6. motioninput_tui-0.1.0/src/motioninput_tui/__init__.py +9 -0
  7. motioninput_tui-0.1.0/src/motioninput_tui/__main__.py +179 -0
  8. motioninput_tui-0.1.0/src/motioninput_tui/config.py +246 -0
  9. motioninput_tui-0.1.0/src/motioninput_tui/constants.py +41 -0
  10. motioninput_tui-0.1.0/src/motioninput_tui/controls/__init__.py +1 -0
  11. motioninput_tui-0.1.0/src/motioninput_tui/controls/buttons.py +131 -0
  12. motioninput_tui-0.1.0/src/motioninput_tui/controls/gamepad.py +256 -0
  13. motioninput_tui-0.1.0/src/motioninput_tui/controls/layouts.py +549 -0
  14. motioninput_tui-0.1.0/src/motioninput_tui/controls/source.py +125 -0
  15. motioninput_tui-0.1.0/src/motioninput_tui/engine/__init__.py +1 -0
  16. motioninput_tui-0.1.0/src/motioninput_tui/engine/buffer.py +114 -0
  17. motioninput_tui-0.1.0/src/motioninput_tui/engine/motions.py +801 -0
  18. motioninput_tui-0.1.0/src/motioninput_tui/engine/notation.py +207 -0
  19. motioninput_tui-0.1.0/src/motioninput_tui/engine/recognizer.py +377 -0
  20. motioninput_tui-0.1.0/src/motioninput_tui/engine/ruleset.py +159 -0
  21. motioninput_tui-0.1.0/src/motioninput_tui/engine/session.py +295 -0
  22. motioninput_tui-0.1.0/src/motioninput_tui/gamepad_probe.py +216 -0
  23. motioninput_tui-0.1.0/src/motioninput_tui/games/__init__.py +1 -0
  24. motioninput_tui-0.1.0/src/motioninput_tui/games/data/hsf2.json +1903 -0
  25. motioninput_tui-0.1.0/src/motioninput_tui/games/data/kof2001.json +9076 -0
  26. motioninput_tui-0.1.0/src/motioninput_tui/games/data/kof98.json +7700 -0
  27. motioninput_tui-0.1.0/src/motioninput_tui/games/data/lb2.json +2972 -0
  28. motioninput_tui-0.1.0/src/motioninput_tui/games/data/sfa3.json +6920 -0
  29. motioninput_tui-0.1.0/src/motioninput_tui/games/data/sfiii3.json +5456 -0
  30. motioninput_tui-0.1.0/src/motioninput_tui/games/data/ssii.json +2366 -0
  31. motioninput_tui-0.1.0/src/motioninput_tui/games/data/ssvsp.json +4306 -0
  32. motioninput_tui-0.1.0/src/motioninput_tui/games/data/usfiv.json +10064 -0
  33. motioninput_tui-0.1.0/src/motioninput_tui/games/loader.py +86 -0
  34. motioninput_tui-0.1.0/src/motioninput_tui/games/models.py +137 -0
  35. motioninput_tui-0.1.0/src/motioninput_tui/games/rulesets.py +406 -0
  36. motioninput_tui-0.1.0/src/motioninput_tui/notation_styles.py +379 -0
  37. motioninput_tui-0.1.0/src/motioninput_tui/py.typed +0 -0
  38. motioninput_tui-0.1.0/src/motioninput_tui/settings.py +73 -0
  39. motioninput_tui-0.1.0/src/motioninput_tui/terminal/__init__.py +6 -0
  40. motioninput_tui-0.1.0/src/motioninput_tui/terminal/detect.py +143 -0
  41. motioninput_tui-0.1.0/src/motioninput_tui/terminal/kitty.py +143 -0
  42. motioninput_tui-0.1.0/src/motioninput_tui/tui/__init__.py +5 -0
  43. motioninput_tui-0.1.0/src/motioninput_tui/tui/app.py +251 -0
  44. motioninput_tui-0.1.0/src/motioninput_tui/tui/keyboard_driver.py +105 -0
  45. motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/__init__.py +6 -0
  46. motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/gamepad_bind.py +155 -0
  47. motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/input_display.py +199 -0
  48. motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/input_picker.py +243 -0
  49. motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/keyboard_bind.py +154 -0
  50. motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/notation.py +152 -0
  51. motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/settings.py +87 -0
  52. motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/setup.py +228 -0
  53. motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/training.py +222 -0
  54. motioninput_tui-0.1.0/src/motioninput_tui/tui/widgets/__init__.py +7 -0
  55. motioninput_tui-0.1.0/src/motioninput_tui/tui/widgets/input_strip.py +57 -0
  56. motioninput_tui-0.1.0/src/motioninput_tui/tui/widgets/move_feed.py +75 -0
  57. motioninput_tui-0.1.0/src/motioninput_tui/tui/widgets/movelist.py +92 -0
  58. motioninput_tui-0.1.0/src/motioninput_tui/tui/widgets/panel.py +110 -0
  59. motioninput_tui-0.1.0/src/motioninput_tui/tui/widgets/settings_list.py +99 -0
  60. motioninput_tui-0.1.0/src/motioninput_tui/utils/__init__.py +1 -0
  61. motioninput_tui-0.1.0/src/motioninput_tui/utils/logger.py +78 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kieran Gee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: motioninput-tui
3
+ Version: 0.1.0
4
+ Summary: A terminal trainer for fighting game motion inputs, tuned per game.
5
+ Author: Kieran Gee
6
+ Author-email: Kieran Gee <kieran.lost.the.game@gmail.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Classifier: Topic :: Games/Entertainment
14
+ Requires-Dist: rich>=15.0.0
15
+ Requires-Dist: textual>=8.2.8
16
+ Requires-Dist: pygame-ce>=2.5.8
17
+ Requires-Python: >=3.14.0, <4
18
+ Project-URL: Repository, https://github.com/kism/motioninput-tui
19
+ Project-URL: Documentation, https://motioninput-tui.readthedocs.io/
20
+ Description-Content-Type: text/markdown
21
+
22
+ # motioninput-tui
23
+
24
+ [![Check](https://github.com/kism/motioninput-tui/actions/workflows/check.yml/badge.svg)](https://github.com/kism/motioninput-tui/actions/workflows/check.yml)
25
+ [![CheckType](https://github.com/kism/motioninput-tui/actions/workflows/check_types.yml/badge.svg)](https://github.com/kism/motioninput-tui/actions/workflows/check_types.yml)
26
+ [![Test](https://github.com/kism/motioninput-tui/actions/workflows/test.yml/badge.svg)](https://github.com/kism/motioninput-tui/actions/workflows/test.yml)
27
+ [![codecov](https://codecov.io/gh/kism/motioninput-tui/graph/badge.svg?token=FPGDA0ODT7)](https://codecov.io/gh/kism/motioninput-tui)
28
+ [![Docs](https://readthedocs.org/projects/motioninput-tui/badge/?version=latest)](https://motioninput-tui.readthedocs.io/en/latest/?badge=latest)
29
+
30
+ A terminal trainer for fighting game motion inputs. Pick a game and a
31
+ character, press inputs, and see which move the game would have given you.
32
+ The input handling has been tuned per game: hold down and
33
+ double tap forward in 3rd Strike and you get a dragon punch; do it in Super
34
+ Turbo or Alpha 3 and you get nothing.
35
+
36
+ Ships with Hyper Street Fighter II, Street Fighter Alpha 3, 3rd Strike, The
37
+ King of Fighters '98 and 2001, and Samurai Shodown V Special.
38
+
39
+ **Full documentation: <https://motioninput-tui.readthedocs.io/>**
40
+
41
+ ## Install and run
42
+
43
+ ```bash
44
+ uv sync --all-groups # omit --all-groups for a plain install
45
+ uv run motioninput-tui
46
+ ```
47
+
48
+ See the docs for the full command line reference, controls, settings and move
49
+ notation.
50
+
51
+ ## Technical Information and AI Disclaimr
52
+
53
+ ### How it's created
54
+
55
+ This is my first AI-heavy project, the workflow is
56
+
57
+ - Get claude to add a game based on a gamefaqs guide
58
+ - sha256 of original guide is verified to avoid possibility of claude editing the reference data
59
+ - skill to make a briefing of each guide, each brief gets made in the game-brief skill
60
+ - claude writes a parser for each guide
61
+ - The moves from the guide are parsed with hard logic, not interpereted by claude
62
+ - The game logic is interpreted by claude based on the guide
63
+
64
+ - The game engine has many control layouts and motion inputs that are applied per game
65
+
66
+ ### How I ensure code quality
67
+
68
+ For each numberd release I do the following
69
+
70
+ - Manually read all the changed code from the previous release
71
+
72
+ - In this register of games/characters I personally test and re-verify
73
+ - This is not frame perfect, I just open 3SX/Mame/whatever and see if it feels the same.
74
+ - The tests that claude writes will reflect these, but absolutely needs to be checked by a human
75
+
76
+ - Register (this will later be a separate file)
77
+ - SFA3
78
+ - Ken
79
+ - Shouryuu Ken feels too strict
80
+ - Sakura
81
+ - Shou'ou Ken feels too strict
82
+ - Sakura Otoshi punch timing is relaxed?
83
+ - SFIII
84
+ - Ken
85
+ - Elana
86
+ - Hugo
87
+ - I can't do Gigas Breaker
88
+ - USFIV
89
+ - Ken
90
+ - Sakura
91
+ - Sakura Otoshi timing is relaxed
92
+ - KOF2001
93
+ - Yuri Sakazaki
94
+
95
+ ## Contributing
96
+
97
+ - [Adding a game](https://motioninput-tui.readthedocs.io/en/latest/adding-a-game.html)
98
+ - [Development setup](https://motioninput-tui.readthedocs.io/en/latest/development.html)
99
+
100
+ ## Credit
101
+
102
+ Move list guides by:
103
+
104
+ - [Kao Megura / Chris MacDonald](https://gamefaqs.gamespot.com/community/Kao_Megura/contributions/faqs) [Rest In Peace](https://web.archive.org/web/20040520095719/http://cgfm2.emuviews.com/).
105
+ - x_MJ_x (Hyper Street Fighter II)
106
+ - THEMCD / Damon M. McDaniel (Ultimate Street Fighter IV)
@@ -0,0 +1,85 @@
1
+ # motioninput-tui
2
+
3
+ [![Check](https://github.com/kism/motioninput-tui/actions/workflows/check.yml/badge.svg)](https://github.com/kism/motioninput-tui/actions/workflows/check.yml)
4
+ [![CheckType](https://github.com/kism/motioninput-tui/actions/workflows/check_types.yml/badge.svg)](https://github.com/kism/motioninput-tui/actions/workflows/check_types.yml)
5
+ [![Test](https://github.com/kism/motioninput-tui/actions/workflows/test.yml/badge.svg)](https://github.com/kism/motioninput-tui/actions/workflows/test.yml)
6
+ [![codecov](https://codecov.io/gh/kism/motioninput-tui/graph/badge.svg?token=FPGDA0ODT7)](https://codecov.io/gh/kism/motioninput-tui)
7
+ [![Docs](https://readthedocs.org/projects/motioninput-tui/badge/?version=latest)](https://motioninput-tui.readthedocs.io/en/latest/?badge=latest)
8
+
9
+ A terminal trainer for fighting game motion inputs. Pick a game and a
10
+ character, press inputs, and see which move the game would have given you.
11
+ The input handling has been tuned per game: hold down and
12
+ double tap forward in 3rd Strike and you get a dragon punch; do it in Super
13
+ Turbo or Alpha 3 and you get nothing.
14
+
15
+ Ships with Hyper Street Fighter II, Street Fighter Alpha 3, 3rd Strike, The
16
+ King of Fighters '98 and 2001, and Samurai Shodown V Special.
17
+
18
+ **Full documentation: <https://motioninput-tui.readthedocs.io/>**
19
+
20
+ ## Install and run
21
+
22
+ ```bash
23
+ uv sync --all-groups # omit --all-groups for a plain install
24
+ uv run motioninput-tui
25
+ ```
26
+
27
+ See the docs for the full command line reference, controls, settings and move
28
+ notation.
29
+
30
+ ## Technical Information and AI Disclaimr
31
+
32
+ ### How it's created
33
+
34
+ This is my first AI-heavy project, the workflow is
35
+
36
+ - Get claude to add a game based on a gamefaqs guide
37
+ - sha256 of original guide is verified to avoid possibility of claude editing the reference data
38
+ - skill to make a briefing of each guide, each brief gets made in the game-brief skill
39
+ - claude writes a parser for each guide
40
+ - The moves from the guide are parsed with hard logic, not interpereted by claude
41
+ - The game logic is interpreted by claude based on the guide
42
+
43
+ - The game engine has many control layouts and motion inputs that are applied per game
44
+
45
+ ### How I ensure code quality
46
+
47
+ For each numberd release I do the following
48
+
49
+ - Manually read all the changed code from the previous release
50
+
51
+ - In this register of games/characters I personally test and re-verify
52
+ - This is not frame perfect, I just open 3SX/Mame/whatever and see if it feels the same.
53
+ - The tests that claude writes will reflect these, but absolutely needs to be checked by a human
54
+
55
+ - Register (this will later be a separate file)
56
+ - SFA3
57
+ - Ken
58
+ - Shouryuu Ken feels too strict
59
+ - Sakura
60
+ - Shou'ou Ken feels too strict
61
+ - Sakura Otoshi punch timing is relaxed?
62
+ - SFIII
63
+ - Ken
64
+ - Elana
65
+ - Hugo
66
+ - I can't do Gigas Breaker
67
+ - USFIV
68
+ - Ken
69
+ - Sakura
70
+ - Sakura Otoshi timing is relaxed
71
+ - KOF2001
72
+ - Yuri Sakazaki
73
+
74
+ ## Contributing
75
+
76
+ - [Adding a game](https://motioninput-tui.readthedocs.io/en/latest/adding-a-game.html)
77
+ - [Development setup](https://motioninput-tui.readthedocs.io/en/latest/development.html)
78
+
79
+ ## Credit
80
+
81
+ Move list guides by:
82
+
83
+ - [Kao Megura / Chris MacDonald](https://gamefaqs.gamespot.com/community/Kao_Megura/contributions/faqs) [Rest In Peace](https://web.archive.org/web/20040520095719/http://cgfm2.emuviews.com/).
84
+ - x_MJ_x (Hyper Street Fighter II)
85
+ - THEMCD / Damon M. McDaniel (Ultimate Street Fighter IV)
@@ -0,0 +1,134 @@
1
+ [project]
2
+ name = "motioninput-tui"
3
+ version = "0.1.0"
4
+ description = "A terminal trainer for fighting game motion inputs, tuned per game."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.14.0,<4"
9
+ dependencies = [
10
+ "rich>=15.0.0",
11
+ "textual>=8.2.8",
12
+ "pygame-ce>=2.5.8",
13
+ ]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Environment :: Console",
17
+ "Intended Audience :: End Users/Desktop",
18
+ "Programming Language :: Python :: 3.14",
19
+ "Topic :: Games/Entertainment",
20
+ ]
21
+
22
+ [[project.authors]]
23
+ name = "Kieran Gee"
24
+ email = "kieran.lost.the.game@gmail.com"
25
+
26
+ [project.urls]
27
+ Repository = "https://github.com/kism/motioninput-tui"
28
+ Documentation = "https://motioninput-tui.readthedocs.io/"
29
+
30
+ [project.scripts]
31
+ motioninput-tui = "motioninput_tui.__main__:main"
32
+ motioninput-tui-probe = "motioninput_tui.gamepad_probe:main"
33
+
34
+ [dependency-groups]
35
+ type = ["ty>=0.0.69"]
36
+ lint = ["ruff>=0.16.2"]
37
+ test = [
38
+ "pytest>=9.1.1",
39
+ "coverage>=7.15.4",
40
+ ]
41
+ docs = [
42
+ "sphinx>=8.1.0",
43
+ "myst-parser>=4.0.0",
44
+ "sphinx-rtd-theme>=3.0.0",
45
+ ]
46
+ guides = [
47
+ "curl-cffi>=0.7.0",
48
+ "beautifulsoup4>=4.12.0",
49
+ ]
50
+
51
+ [build-system]
52
+ requires = ["uv_build>=0.12.3,<0.13.0"]
53
+ build-backend = "uv_build"
54
+
55
+ [tool.ruff]
56
+ cache-dir = "~/.cache/ruff"
57
+ line-length = 120
58
+
59
+ [tool.ruff.lint]
60
+ preview = true
61
+ select = ["ALL"]
62
+ ignore = [
63
+ "DJ",
64
+ "AIR",
65
+ "FAST",
66
+ "DTZ",
67
+ "missing-todo-author",
68
+ "missing-todo-link",
69
+ "single-line-implicit-string-concatenation",
70
+ "missing-trailing-comma",
71
+ "D",
72
+ "DOC",
73
+ "missing-copyright-notice",
74
+ ]
75
+
76
+ [tool.ruff.lint.per-file-ignores]
77
+ "tests/*.py" = [
78
+ "ARG",
79
+ "FBT",
80
+ "ANN",
81
+ "D",
82
+ "DOC",
83
+ "TC",
84
+ "no-self-use",
85
+ "assert",
86
+ "private-member-access",
87
+ "implicit-namespace-package",
88
+ "pytest-missing-fixture-name-underscore",
89
+ "suspicious-non-cryptographic-random-usage",
90
+ "hardcoded-password-string",
91
+ "too-many-arguments",
92
+ "blind-except",
93
+ "suspicious-non-cryptographic-random-usage",
94
+ "import-private-name",
95
+ ]
96
+ "tests/test_notation_styles.py" = ["ambiguous-unicode-character-string"]
97
+ "src/motioninput_tui/notation_styles.py" = ["ambiguous-unicode-character-string"]
98
+ "scripts/*.py" = [
99
+ "print",
100
+ "implicit-namespace-package",
101
+ ]
102
+ ".claude/skills/**/*.py" = [
103
+ "print",
104
+ "implicit-namespace-package",
105
+ ]
106
+ "docs/conf.py" = ["implicit-namespace-package"]
107
+
108
+ [tool.ruff.lint.flake8-pytest-style]
109
+ fixture-parentheses = false
110
+
111
+ [tool.ruff.lint.pydocstyle]
112
+ convention = "google"
113
+
114
+ [tool.ruff.format]
115
+ quote-style = "double"
116
+ indent-style = "space"
117
+ docstring-code-format = true
118
+ docstring-code-line-length = 40
119
+
120
+ [tool.pytest.ini_options]
121
+ testpaths = ["tests"]
122
+
123
+ [tool.coverage.run]
124
+ command_line = ".venv/bin/pytest -q"
125
+ source = ["motioninput_tui"]
126
+ context = "test"
127
+
128
+ [tool.coverage.report]
129
+ show_missing = false
130
+ format = "text"
131
+ sort = "-Miss"
132
+
133
+ [tool.coverage.html]
134
+ show_contexts = true
@@ -0,0 +1,142 @@
1
+ [project]
2
+ name = "motioninput-tui"
3
+ version = "0.1.0"
4
+ description = "A terminal trainer for fighting game motion inputs, tuned per game."
5
+ authors = [{ name = "Kieran Gee", email = "kieran.lost.the.game@gmail.com" }]
6
+ readme = "README.md"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ requires-python = ">=3.14.0,<4"
10
+ dependencies = [
11
+ "rich>=15.0.0",
12
+ "textual>=8.2.8",
13
+ "pygame-ce>=2.5.8", # Gamepad support, imported only when the gamepad layout is selected.
14
+ ]
15
+
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Environment :: Console",
19
+ "Intended Audience :: End Users/Desktop",
20
+ "Programming Language :: Python :: 3.14",
21
+ "Topic :: Games/Entertainment",
22
+ ]
23
+
24
+ [dependency-groups]
25
+ # Development only; none of these are published as PyPI extras.
26
+ type = ["ty>=0.0.69"]
27
+ lint = ["ruff>=0.16.2"]
28
+ test = ["pytest>=9.1.1", "coverage>=7.15.4"]
29
+
30
+ # Building the docs/ site. Never imported by the trainer itself.
31
+ docs = ["sphinx>=8.1.0", "myst-parser>=4.0.0", "sphinx-rtd-theme>=3.0.0"]
32
+
33
+ # Only for fetching reference guides, never imported by the trainer itself.
34
+ guides = ["curl-cffi>=0.7.0", "beautifulsoup4>=4.12.0"]
35
+
36
+ [project.urls]
37
+ Repository = "https://github.com/kism/motioninput-tui"
38
+ Documentation = "https://motioninput-tui.readthedocs.io/"
39
+
40
+ [build-system]
41
+ requires = ["uv_build>=0.12.3,<0.13.0"]
42
+ build-backend = "uv_build"
43
+
44
+ [project.scripts]
45
+ motioninput-tui = "motioninput_tui.__main__:main"
46
+ motioninput-tui-probe = "motioninput_tui.gamepad_probe:main"
47
+
48
+ [tool.ruff]
49
+ cache-dir = "~/.cache/ruff"
50
+ line-length = 120
51
+
52
+ [tool.ruff.lint]
53
+ preview = true # Optional, these can be unstable, many of these rules exist.
54
+ select = ["ALL"] # Include absolutely all of Ruff's rules.
55
+ ignore = [
56
+ # Modules
57
+ "DJ", # Django not required
58
+ "AIR", # Airflow not required
59
+ "FAST", # FastAPI not required
60
+ "DTZ", # no need to specify timezone for time objects
61
+
62
+ # Specific rules
63
+ "missing-todo-author", # Verbose TODO, only I work on this project
64
+ "missing-todo-link", # Verbose TODO, only I work on this project
65
+ "single-line-implicit-string-concatenation", # conflict in the formatter
66
+ "missing-trailing-comma", # conflict in the formatter
67
+ "D", # Don't care about docstrings
68
+ "DOC", # Don't care about docstrings
69
+ "missing-copyright-notice", # Copyright lives in pyproject.toml
70
+ ]
71
+
72
+ [tool.ruff.lint.per-file-ignores]
73
+ "tests/*.py" = [
74
+ # Modules
75
+ "ARG", # Unused function args -> fixtures nevertheless are functionally relevant
76
+ "FBT", # Don't care about booleans as positional arguments in tests, e.g. via @pytest.mark.parametrize()
77
+ "ANN", # Don't care about annotations in tests
78
+ "D", # Don't care about docstrings in tests
79
+ "DOC", # Don't care about docstrings in tests
80
+ "TC", # Test doubles are used at runtime; no need to guard their imports
81
+
82
+ # Specific rules
83
+ "no-self-use", # Test doubles mirror a real API, even when a method ignores self
84
+ "assert", # asserts allowed in tests.
85
+ "private-member-access", # Accessing private members is needed for testing.
86
+ "implicit-namespace-package", # __init__.py not needed for PyTest.
87
+ "pytest-missing-fixture-name-underscore", # monkeypatch doesn't return.
88
+ "suspicious-non-cryptographic-random-usage", # I'll assume no real crypto will be done in PyTest.
89
+ "hardcoded-password-string", # Hardcoded fake passwords are fine in tests.
90
+ "too-many-arguments", # Tests can have as many arguments as they want.
91
+ "blind-except", # Tests can use BaseException.
92
+ "suspicious-non-cryptographic-random-usage", # Regular RNG is fine in tests.
93
+ "import-private-name", # Tests can import private members.
94
+ ]
95
+ "tests/test_notation_styles.py" = [
96
+ "ambiguous-unicode-character-string", # The glyphs are what it is testing.
97
+ ]
98
+ "src/motioninput_tui/notation_styles.py" = [
99
+ # A module of glyphs to pick between: ⋃ really is meant to be a cup, not a U,
100
+ # and ×2 is the multiplication sign the guides use.
101
+ "ambiguous-unicode-character-string",
102
+ ]
103
+ "scripts/*.py" = [
104
+ "print", # Command line tools; printing is how they report.
105
+ "implicit-namespace-package", # Standalone scripts, not a package.
106
+ ]
107
+ ".claude/skills/**/*.py" = [
108
+ "print", # Skill scripts are command line tools; printing is how they report.
109
+ "implicit-namespace-package", # Standalone scripts, not a package.
110
+ ]
111
+ "docs/conf.py" = [
112
+ "implicit-namespace-package", # Sphinx config, not part of the motioninput_tui package.
113
+ ]
114
+
115
+
116
+ [tool.ruff.lint.flake8-pytest-style]
117
+ fixture-parentheses = false
118
+
119
+ [tool.ruff.lint.pydocstyle]
120
+ convention = "google"
121
+
122
+ [tool.ruff.format]
123
+ quote-style = "double"
124
+ indent-style = "space"
125
+ docstring-code-format = true
126
+ docstring-code-line-length = 40
127
+
128
+ [tool.pytest.ini_options]
129
+ testpaths = ["tests"]
130
+
131
+ [tool.coverage.run]
132
+ command_line = ".venv/bin/pytest -q"
133
+ source = ["motioninput_tui"]
134
+ context = "test"
135
+
136
+ [tool.coverage.report]
137
+ show_missing = false
138
+ format = "text"
139
+ sort = "-Miss"
140
+
141
+ [tool.coverage.html]
142
+ show_contexts = true
@@ -0,0 +1,9 @@
1
+ """Python App motioninput-tui."""
2
+
3
+ from .constants import PROGRAM_NAME, PROGRAM_REPO_URL, PROGRAM_VERSION
4
+
5
+ __all__ = [
6
+ "PROGRAM_NAME",
7
+ "PROGRAM_REPO_URL",
8
+ "PROGRAM_VERSION",
9
+ ]
@@ -0,0 +1,179 @@
1
+ """Main entrypoint."""
2
+
3
+ import argparse
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ from rich import traceback
8
+
9
+ from .config import Config, config_path
10
+ from .constants import PROGRAM_NAME, PROGRAM_NAME_WITH_FULL_VERSION, PROGRAM_NAME_WITH_VERSION
11
+ from .controls.layouts import DEFAULT_LAYOUT, available_layouts
12
+ from .engine.recognizer import BufferPolicy
13
+ from .games.loader import GameDataMissingError, available_games, load_game
14
+ from .games.rulesets import GAME_SPECS
15
+ from .terminal import detect, query_support
16
+ from .utils.logger import get_logger, setup_logger_cli
17
+
18
+ traceback.install(extra_lines=2)
19
+ logger = get_logger(__name__)
20
+
21
+
22
+ def _get_args() -> argparse.Namespace:
23
+ parser = argparse.ArgumentParser(prog=PROGRAM_NAME, description="Fighting game motion input trainer.")
24
+ parser.add_argument("--game", choices=sorted(GAME_SPECS), help="Skip the game picker.")
25
+ parser.add_argument("--character", help="Skip the character picker. Needs --game.")
26
+ parser.add_argument(
27
+ "--layout",
28
+ choices=sorted(layout.key for layout in available_layouts()),
29
+ default=None,
30
+ help=f"Control layout (default: last used, or {DEFAULT_LAYOUT}).",
31
+ )
32
+ parser.add_argument(
33
+ "--no-key-release",
34
+ action="store_true",
35
+ help="Do not ask the terminal for key release reporting; infer holds from auto-repeat instead.",
36
+ )
37
+ parser.add_argument(
38
+ "--loose-buffer",
39
+ action=argparse.BooleanOptionalAction,
40
+ default=None,
41
+ help="Do not spend inputs when a move comes out, so one motion can feed several moves. "
42
+ "Not how the games behave; it is also in the trainer's settings, ctrl+b. Default: last used.",
43
+ )
44
+ parser.add_argument(
45
+ "--config",
46
+ type=Path,
47
+ default=None,
48
+ help=f"Config file holding the last used selection (default: {config_path()}).",
49
+ )
50
+ parser.add_argument("--list", action="store_true", help="List games and characters, then exit.")
51
+ parser.add_argument("--check-terminal", action="store_true", help="Report terminal suitability, then exit.")
52
+ parser.add_argument("--version", action="version", version=PROGRAM_NAME_WITH_FULL_VERSION)
53
+ parser.add_argument("-v", action="count", default=0, help="Increase verbosity (can be used multiple times).")
54
+ return parser.parse_args()
55
+
56
+
57
+ def _print_terminal() -> int:
58
+ info = detect()
59
+ logger.info("Terminal: %s (%s, %s)", info.name, info.speed, info.detail)
60
+ if info.warning():
61
+ logger.warning("%s", info.warning())
62
+ else:
63
+ logger.info("Should be fast enough for accurate input timing.")
64
+
65
+ releases = query_support()
66
+ if releases is None:
67
+ logger.info("Key releases: could not ask, no terminal attached.")
68
+ elif releases:
69
+ logger.info("Key releases: supported. Holds will be tracked exactly.")
70
+ else:
71
+ logger.warning(
72
+ "Key releases: not supported. Holds will be inferred from auto-repeat, "
73
+ "so charge moves depend on your keyboard repeat delay. "
74
+ "Terminals that do support this: Ghostty, Alacritty, WezTerm, kitty, foot, Contour, Rio."
75
+ )
76
+ return 0
77
+
78
+
79
+ def _print_roster() -> int:
80
+ for game in available_games():
81
+ logger.info("%s - %s", game.key, game.name)
82
+ for character in game.characters:
83
+ # The input display's "characters" are button sets, with no moves
84
+ # to count, so they say what they are instead.
85
+ if character.moves:
86
+ logger.info(" %-22s %d trainable moves", character.key, len(character.trainable_moves))
87
+ else:
88
+ logger.info(" %-22s %s", character.key, character.name)
89
+ return 0
90
+
91
+
92
+ def main() -> int:
93
+ """Main entrypoint."""
94
+ args = _get_args()
95
+ setup_logger_cli(args.v)
96
+ logger.debug("%s", PROGRAM_NAME_WITH_VERSION)
97
+
98
+ if args.check_terminal:
99
+ return _print_terminal()
100
+ if args.list:
101
+ return _print_roster()
102
+
103
+ if args.character and not args.game:
104
+ logger.error("--character needs --game as well")
105
+ return 2
106
+
107
+ config = Config.load(args.config)
108
+ _apply_overrides(config, args)
109
+
110
+ if not _resolve_selection(config, from_cli=bool(args.character)):
111
+ return 1
112
+
113
+ info = detect()
114
+ if info.should_warn:
115
+ logger.warning("%s", info.warning())
116
+
117
+ # Ask before the interface takes over the terminal, so holds are tracked
118
+ # exactly from the very first keystroke rather than from the first release.
119
+ key_release = False if args.no_key_release else bool(query_support())
120
+ if key_release:
121
+ logger.debug("Terminal reports key releases; holds will be tracked exactly")
122
+ else:
123
+ logger.info("Terminal does not report key releases; holds will be inferred from auto-repeat")
124
+
125
+ from .tui import MotionInputApp # ruff: ignore[import-outside-top-level] - importing textual is slow, only do it when running the app
126
+
127
+ MotionInputApp(
128
+ config,
129
+ key_release=key_release,
130
+ skip_setup=bool(args.game and args.character),
131
+ ).run()
132
+ return 0
133
+
134
+
135
+ def _resolve_selection(config: Config, *, from_cli: bool) -> bool:
136
+ """Check the selection against the rosters. False means do not start.
137
+
138
+ A remembered character can simply be gone: rosters are regenerated, and a
139
+ name override in ``motioninput_tui_datagen/names.py`` renames the key with the character.
140
+ That is no reason to refuse to start, so the selection is dropped and the
141
+ picker opens on it instead. A character named on the command line is
142
+ different, and still gets an error.
143
+ """
144
+ if not config.game:
145
+ return True
146
+ try:
147
+ game = load_game(config.game)
148
+ except GameDataMissingError as exc:
149
+ logger.error("%s", exc) # ruff: ignore[error-instead-of-exception] - a traceback helps nobody here
150
+ return False
151
+ if not config.character:
152
+ return True
153
+ try:
154
+ config.character = game.character(config.character).key
155
+ except KeyError as exc:
156
+ if from_cli:
157
+ logger.error("%s", exc) # ruff: ignore[error-instead-of-exception] - a traceback helps nobody here
158
+ return False
159
+ logger.info("Forgetting the saved character, it is not in the roster any more: %s", exc)
160
+ config.character = None
161
+ return True
162
+
163
+
164
+ def _apply_overrides(config: Config, args: argparse.Namespace) -> None:
165
+ """Let command line arguments win over what was remembered."""
166
+ if args.game:
167
+ config.game = args.game
168
+ # Not the character from whatever game was being played last: whoever
169
+ # was last trained on this one. --character always wins, and cannot be
170
+ # given without --game.
171
+ config.character = args.character or config.characters.get(args.game)
172
+ if args.layout:
173
+ config.layout = args.layout
174
+ if args.loose_buffer is not None:
175
+ config.buffer_policy = BufferPolicy.LOOSE if args.loose_buffer else BufferPolicy.CONSUME
176
+
177
+
178
+ if __name__ == "__main__":
179
+ sys.exit(main()) # pragma: no cover