tuipet 0.2.415__tar.gz → 0.2.417__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 (239) hide show
  1. {tuipet-0.2.415/src/tuipet.egg-info → tuipet-0.2.417}/PKG-INFO +1 -1
  2. {tuipet-0.2.415 → tuipet-0.2.417}/pyproject.toml +1 -1
  3. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/app.py +4 -4
  4. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/training.py +180 -173
  5. {tuipet-0.2.415 → tuipet-0.2.417/src/tuipet.egg-info}/PKG-INFO +1 -1
  6. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_panel_smoke.py +2 -3
  7. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_status_icons.py +2 -0
  8. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_training_facing.py +80 -42
  9. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_training_strike.py +70 -146
  10. {tuipet-0.2.415 → tuipet-0.2.417}/LICENSE +0 -0
  11. {tuipet-0.2.415 → tuipet-0.2.417}/README.md +0 -0
  12. {tuipet-0.2.415 → tuipet-0.2.417}/setup.cfg +0 -0
  13. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/__init__.py +0 -0
  14. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/adventure.py +0 -0
  15. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/adventurescreen.py +0 -0
  16. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/anim.py +0 -0
  17. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/arena.py +0 -0
  18. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/assistscreen.py +0 -0
  19. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/battle.py +0 -0
  20. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/battlefx.py +0 -0
  21. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/battlescreen.py +0 -0
  22. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/bugscreen.py +0 -0
  23. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/cloudsync.py +0 -0
  24. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/attr_icons.json +0 -0
  25. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/backgrounds.json.gz +0 -0
  26. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/battle_overlays.json +0 -0
  27. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/careEffect.csv +0 -0
  28. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/digicoreMenuConfig.csv +0 -0
  29. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/digimon.csv +0 -0
  30. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/dropRate.csv +0 -0
  31. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/effects.json.gz +0 -0
  32. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/eggUnlock.csv +0 -0
  33. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/eggs.json.gz +0 -0
  34. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/elementAffinity.csv +0 -0
  35. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/enemies.csv +0 -0
  36. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/evolutions.csv +0 -0
  37. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/fieldAffinity.csv +0 -0
  38. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/foods.csv +0 -0
  39. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/habitats.csv +0 -0
  40. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/hp_dummies.json +0 -0
  41. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/icons.json.gz +0 -0
  42. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/items.csv +0 -0
  43. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/lines.csv +0 -0
  44. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/lootTable.csv +0 -0
  45. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/maps.csv +0 -0
  46. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/orbs.json.gz +0 -0
  47. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/shopConsumable.csv +0 -0
  48. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/alarm.wav +0 -0
  49. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/angry.wav +0 -0
  50. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/attack.wav +0 -0
  51. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/attackHit.wav +0 -0
  52. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/battle.wav +0 -0
  53. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/cancel.wav +0 -0
  54. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/champion.wav +0 -0
  55. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/click.wav +0 -0
  56. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/compatible.wav +0 -0
  57. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/confirm.wav +0 -0
  58. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/death.wav +0 -0
  59. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/eat.wav +0 -0
  60. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/error.wav +0 -0
  61. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/evolve.wav +0 -0
  62. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/happy.wav +0 -0
  63. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/hatch.wav +0 -0
  64. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/jogress.wav +0 -0
  65. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/largePoop.wav +0 -0
  66. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/lastBite.wav +0 -0
  67. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/lose.wav +0 -0
  68. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/menu.wav +0 -0
  69. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/mischief.wav +0 -0
  70. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/poop.wav +0 -0
  71. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/refuse.wav +0 -0
  72. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/reward.wav +0 -0
  73. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/scroll.wav +0 -0
  74. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/select.wav +0 -0
  75. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/smallPoop.wav +0 -0
  76. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/startBattle.wav +0 -0
  77. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/strongAttack.wav +0 -0
  78. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/strongHit.wav +0 -0
  79. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/trainhit.wav +0 -0
  80. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/wash.wav +0 -0
  81. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/win.wav +0 -0
  82. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sounds/wind.wav +0 -0
  83. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/sprites.json.gz +0 -0
  84. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/tournies.csv +0 -0
  85. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/towns.csv +0 -0
  86. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data/zones.csv +0 -0
  87. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/data.py +0 -0
  88. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/deathscreen.py +0 -0
  89. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/digicorescreen.py +0 -0
  90. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/dnascreen.py +0 -0
  91. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/egg.py +0 -0
  92. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/eggselectscreen.py +0 -0
  93. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/evolution.py +0 -0
  94. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/feedscreen.py +0 -0
  95. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/grid.py +0 -0
  96. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/habitatscreen.py +0 -0
  97. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/helpscreen.py +0 -0
  98. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/itemfx.py +0 -0
  99. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/jogress.py +0 -0
  100. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/jogressscreen.py +0 -0
  101. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/lines.py +0 -0
  102. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/lobbyscreen.py +0 -0
  103. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/loot.py +0 -0
  104. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/menu.py +0 -0
  105. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/net.py +0 -0
  106. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/optionsscreen.py +0 -0
  107. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/persistence.py +0 -0
  108. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/pet.py +0 -0
  109. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/placeholder.py +0 -0
  110. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/render.py +0 -0
  111. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/shop.py +0 -0
  112. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/shopscreen.py +0 -0
  113. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/sound.py +0 -0
  114. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/strikefx.py +0 -0
  115. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/theme.py +0 -0
  116. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/themescreen.py +0 -0
  117. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/titlescreen.py +0 -0
  118. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/tournament.py +0 -0
  119. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/tournamentscreen.py +0 -0
  120. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/townscreen.py +0 -0
  121. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/transportscreen.py +0 -0
  122. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/update.py +0 -0
  123. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/weather.py +0 -0
  124. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet/world.py +0 -0
  125. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet.egg-info/SOURCES.txt +0 -0
  126. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet.egg-info/dependency_links.txt +0 -0
  127. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet.egg-info/entry_points.txt +0 -0
  128. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet.egg-info/requires.txt +0 -0
  129. {tuipet-0.2.415 → tuipet-0.2.417}/src/tuipet.egg-info/top_level.txt +0 -0
  130. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_adventure.py +0 -0
  131. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_adventure_math.py +0 -0
  132. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_adventure_scenery.py +0 -0
  133. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_album_dex.py +0 -0
  134. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_anim_canon.py +0 -0
  135. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_anim_timing.py +0 -0
  136. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_announce.py +0 -0
  137. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_audit_fixes.py +0 -0
  138. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_auto_care.py +0 -0
  139. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_battle.py +0 -0
  140. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_battle_math.py +0 -0
  141. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_bedtime.py +0 -0
  142. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_birthday.py +0 -0
  143. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_body_math.py +0 -0
  144. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_bugreport.py +0 -0
  145. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_care_asleep.py +0 -0
  146. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_care_bonus.py +0 -0
  147. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_completeness_sweep.py +0 -0
  148. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_data.py +0 -0
  149. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_dead_gate.py +0 -0
  150. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_death.py +0 -0
  151. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_device_lines.py +0 -0
  152. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_digicore.py +0 -0
  153. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_digimemory.py +0 -0
  154. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_discipline.py +0 -0
  155. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_divergence.py +0 -0
  156. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_dna_charge.py +0 -0
  157. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_effects_integrity.py +0 -0
  158. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_egg_economy.py +0 -0
  159. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_egg_unlock.py +0 -0
  160. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_egg_unlock_math.py +0 -0
  161. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_energy_system.py +0 -0
  162. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_evolution.py +0 -0
  163. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_evolution_math.py +0 -0
  164. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_evolution_system.py +0 -0
  165. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_feed.py +0 -0
  166. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_feeding_math.py +0 -0
  167. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_filth_system.py +0 -0
  168. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_fx.py +0 -0
  169. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_gift_birthday_math.py +0 -0
  170. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_habitat.py +0 -0
  171. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_habitat_math.py +0 -0
  172. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_help.py +0 -0
  173. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_hint_strips.py +0 -0
  174. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_hud.py +0 -0
  175. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_humulos_eggs.py +0 -0
  176. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_hunger.py +0 -0
  177. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_item_effects.py +0 -0
  178. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_item_heal.py +0 -0
  179. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_itemfx.py +0 -0
  180. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_jogress.py +0 -0
  181. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_jogress_math.py +0 -0
  182. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_line_curation.py +0 -0
  183. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_lines.py +0 -0
  184. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_lobby.py +0 -0
  185. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_lobby_admin.py +0 -0
  186. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_lobby_screens.py +0 -0
  187. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_medicine.py +0 -0
  188. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_menu_bounds.py +0 -0
  189. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_messages.py +0 -0
  190. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_mood_system.py +0 -0
  191. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_mortality_math.py +0 -0
  192. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_msg_strip.py +0 -0
  193. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_nav_quit.py +0 -0
  194. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_night_palette.py +0 -0
  195. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_obedience_system.py +0 -0
  196. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_options.py +0 -0
  197. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_performance.py +0 -0
  198. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_persistence.py +0 -0
  199. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_persistence_audit.py +0 -0
  200. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_pet_effect.py +0 -0
  201. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_poop_model.py +0 -0
  202. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_poop_placement.py +0 -0
  203. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_protocol_math.py +0 -0
  204. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_rank_system.py +0 -0
  205. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_refusal.py +0 -0
  206. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_refusal_math.py +0 -0
  207. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_robustness.py +0 -0
  208. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_save_integrity.py +0 -0
  209. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_scene_actors.py +0 -0
  210. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_screen_sweep.py +0 -0
  211. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_sfx_scroll.py +0 -0
  212. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_shop.py +0 -0
  213. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_shop_economy.py +0 -0
  214. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_shop_math.py +0 -0
  215. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_sickness_system.py +0 -0
  216. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_sleep.py +0 -0
  217. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_sleep_system.py +0 -0
  218. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_sleep_weather_math.py +0 -0
  219. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_sound.py +0 -0
  220. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_special_idles.py +0 -0
  221. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_stage_arrival.py +0 -0
  222. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_status_box.py +0 -0
  223. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_sync.py +0 -0
  224. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_theme.py +0 -0
  225. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_toilet.py +0 -0
  226. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_tournament.py +0 -0
  227. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_town.py +0 -0
  228. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_town_eggs.py +0 -0
  229. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_training.py +0 -0
  230. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_training_hp.py +0 -0
  231. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_training_math.py +0 -0
  232. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_training_system.py +0 -0
  233. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_ui_stats.py +0 -0
  234. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_update.py +0 -0
  235. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_verx.py +0 -0
  236. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_weight_system.py +0 -0
  237. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_whats_new.py +0 -0
  238. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_win_eggs.py +0 -0
  239. {tuipet-0.2.415 → tuipet-0.2.417}/tests/test_world.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tuipet
3
- Version: 0.2.415
3
+ Version: 0.2.417
4
4
  Summary: A terminal virtual pet (Digimon V-Pet style) rendered with halfblock sprites, built on data extracted from DVPet.
5
5
  Author-email: Joel Taylor <joeltaylor734@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tuipet"
3
- version = "0.2.415"
3
+ version = "0.2.417"
4
4
  description = "A terminal virtual pet (Digimon V-Pet style) rendered with halfblock sprites, built on data extracted from DVPet."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -250,10 +250,10 @@ class TuiPetApp(App):
250
250
  """
251
251
  # the release-news line (title-screen msg box, first launch per build) --
252
252
  # UPDATE THIS WITH EVERY RELEASE that ships something player-visible
253
- WHATS_NEW = ("Training impacts hit BIG again: the orb-strike explosion is "
254
- "back to its full 32x16 flash, and the punch drill's Hit!! "
255
- "banner now takes over the whole window like the battle "
256
- "banner.")
253
+ WHATS_NEW = ("The Data drill is now the REAL DM20 versus training: YOU "
254
+ "fire high or low past the sparring partner's shield, 3 of 5 "
255
+ "rounds to pass -- the fan-made turret is gone, and the "
256
+ "manual's secret pattern chart works here too.")
257
257
 
258
258
  BINDINGS = [
259
259
  # battle + jogress are LOBBY-ONLY (Joel 2026-07-07: "battles and
@@ -3,7 +3,7 @@
3
3
  Attack_Contact -> Attack_Aftermath).
4
4
 
5
5
  Every drill is fought against an on-screen OPPONENT that is present the whole time
6
- -- the punching bag (HP / Vaccine / Virus) or the green pop-up target (Data) -- and
6
+ -- the punching bag (HP / Vaccine / Virus) or the sparring partner (Data) -- and
7
7
  the skill phase flows straight into the strike -> impact -> aftermath, exactly like
8
8
  the hardware (no abstract gauge with the opponent conjured up only at the end):
9
9
 
@@ -12,9 +12,11 @@ the hardware (no abstract gauge with the opponent conjured up only at the end):
12
12
  (drawHPTraining, first to 2 wins); builds Effort.
13
13
  Vaccine — pet HIDDEN, bag on screen: mash the hit button (drawVaccinePre);
14
14
  builds Vaccine power. Pet appears for the strike.
15
- Data — pet LEFT, green target RIGHT (drawDataPre): the attack feints, then
16
- COMMITS high or low; raise your shield to the matching side to BLOCK it
17
- (controller checkSuccess: success = shieldTop == isUp). Fires RIGHT.
15
+ Data — the REAL DM20's versus training (canon rebuild 2026-07-13; DVPet's
16
+ turret duel here was pure fan invention -- Joel: "i dont think this
17
+ system is canon at all", and it isn't). Manual: "you choose to fire
18
+ a high shot or a low shot. If your shot gets past your partner's
19
+ shield, you succeed. You need to succeed 3 out of the 5 rounds."
18
20
  Virus — pet HIDDEN, bag on screen: stop the sweeping power bar high
19
21
  (drawVirusPre); builds Virus power.
20
22
 
@@ -35,7 +37,7 @@ from . import menu
35
37
  # to the TARGET as the orb arrives -> fullscreen explosion -> break). Reuse the battle's
36
38
  # pose ids + beat timings + column-bounds helper so the two stay in lockstep.
37
39
  from .battlescreen import (IDLE, TURN, ATTACK, CHARGE, COLLAPSE, # noqa: F401
38
- EXPLODE_FRAMES, EXPLODE_HOLD, FLINCH_T)
40
+ EXPLODE_FRAMES, EXPLODE_HOLD, FLINCH_T, FIRE_T)
39
41
 
40
42
  # the full-screen spiky hit burst (attackHit/attackHitFlash), shared with the battle
41
43
  # screen -- strobes outline<->filled to flash the LCD on impact (DVPet hitAnim).
@@ -53,14 +55,20 @@ VACCINE_WINDOW = 41 # drawVaccinePre = _interval*41 = 246 frames @60fp
53
55
  VIRUS_BAR_MIN = 86 # VirusGameBarMin
54
56
  VIRUS_MAX = 94 # DVPet maxBar (bar pixel width) -> the zone 86..94 is the top ~8%
55
57
  VIRUS_SPEEDS = (4.8, 6.0, 8.0) # per-tick fill = DVPet +4 per VirusGameBarSpeed{5,4,3} frames (48/60/80 px/s / 10Hz)
56
- # Data is a SHIELD BLOCK (checkSuccess: success = shieldTop == isUp): the green target feints,
57
- # then COMMITS its attack high or low; raise your shield to the matching side before the window closes.
58
- DATA_BOB = 3 # ticks per feint up/down toggle during the telegraph
59
- DATA_TELEGRAPH = (21, 17, 15) # feint window before commit (DVPet frame*8+pad*8 frames, /6 -> ticks)
60
- DATA_WINDOW = (10, 7, 5) # reaction window after commit (DataTrainShootFrame{10,7,5}; 6*frame/6 -> ticks)
61
- DATA_FLY = 4 # shot flight ticks (DVPet attackGreen used 3 for its 12px nudge;
62
- # our longer 10px lane gets 4 -- ~2.5px/tick)
63
- # collision = the battle's exact beats: EXPLODE_FRAMES strobe (hitAnim) + FLINCH_T pose (aftermathGreen)
58
+ # Data is the REAL DM20's VERSUS training (humulos.com/digimon/dm20/manual): "you choose
59
+ # to fire a high shot or a low shot. If your shot gets past your partner's shield, you
60
+ # succeed. You need to succeed 3 out of the 5 rounds for training to be successful."
61
+ # The partner's shield follows the manual's REPEATING PATTERN -- "pressing the buttons
62
+ # according to the chart below will let you win tag training every time" -- the six
63
+ # winning-button rows ported VERBATIM (5 rounds each, sessions cycle 1..6 and repeat,
64
+ # indexed by the pet's stage_trainings so the cheat chart works like the hardware).
65
+ # The manual never says which of A/B fires high; tuipet reads A=HIGH (adaptation, the
66
+ # one unspecified bit) -- the shield therefore stands OPPOSITE each winning shot.
67
+ DATA_ROUNDS = 5 # rounds per session (manual: "3 out of the 5 rounds")
68
+ DATA_PASS = 3 # shots that must get past for a successful training
69
+ DATA_WIN_CHART = ("ABABB", "BBAAB", "BAABB", "ABBAA", "BABAB", "ABABA") # manual, verbatim
70
+ DATA_BLOCK_HOLD = 4 # beats the blocked tableau holds before the next round
71
+ # collision = the battle's exact beats: EXPLODE_FRAMES strobe (hitAnim) on a shot that gets past
64
72
  HP_ROUNDS = 3 # DVPet _hpTrainingRounds
65
73
  HP_ROUNDS_WON = 2 # DVPet _hpTrainingRoundsWon (first to 2 wins the drill)
66
74
  HP_TRAIN_DIFFICULTY = 11 # DVPet _hpTrainDifficultyChange: rank = fullHealthPoints // 11
@@ -100,6 +108,17 @@ def _fit_cell(sprite):
100
108
  from .render import blit as _blit # one blit for app/training/strikefx (refactor 2026-07-05)
101
109
 
102
110
 
111
+ def _pop(overlay, ink):
112
+ """Layer `ink` on TOP of the staged overlay: punch a 1px clearance halo
113
+ out of everything already under it, then add the ink. On a 1-bit LCD
114
+ adjacency IS fusion -- the halo is how a foreground actor (a held shield,
115
+ a flying pellet) reads as its own object over a busy stage. Occlusion,
116
+ not art: no sprite's pixels change, the back actor is just behind."""
117
+ guard = {(x + dx, y + dy) for x, y in ink
118
+ for dx in (-1, 0, 1) for dy in (-1, 0, 1)}
119
+ return [pt for pt in overlay if pt not in guard] + list(ink)
120
+
121
+
103
122
  def _crop(pf):
104
123
  """Crop a creature frame to its real body (the sheets are mostly padding) so
105
124
  the drills can place it on exact columns. Shared by vaccine/data/hp."""
@@ -126,7 +145,7 @@ _HP_ICON_KEYS = ("atk_vaccine", "atk_data", "atk_virus") # index == hp_target
126
145
  GAMES = [
127
146
  ("hp", "HP Drill", "Effort", "stop the reel on the match — best of 3"),
128
147
  ("vaccine", "Vaccine", "Vaccine", "mash the bag — fast!"),
129
- ("data", "Data", "Data", "block the attack — high or low"),
148
+ ("data", "Data", "Data", "fire past the shield — 3 of 5"),
130
149
  ("virus", "Virus", "Virus", f"stop the bar over {VIRUS_BAR_MIN}"),
131
150
  ]
132
151
 
@@ -151,25 +170,22 @@ class TrainingPanel:
151
170
  self.timer = VACCINE_WINDOW
152
171
  self.vaccine_target = VACCINE_HITS[1] # rank-based, set per drill start (below)
153
172
  self.virus_speed = VIRUS_SPEEDS[1] # rank-based bar fill speed
154
- self.data_telegraph = DATA_TELEGRAPH[1] # rank-based feint window
155
- self.data_window = DATA_WINDOW[1] # rank-based reaction window
156
173
  self.round_len = HP_ROUND_LEN[1]
157
174
  self.round_t = self.round_len
158
175
  self.rounds_won = 0
159
176
  self.hp_target = 0 # hidden attribute the bag "is" (0/1/2)
160
177
  self.hp_pick = 0 # the player's cursor over the 3 guess buttons
161
- # data shield-block state
162
- self.data_t = 0
163
- self.feint_up = False # green's current feint position during the telegraph
164
- self.locked = False # has the attack committed (revealed high/low)?
165
- self.tgt_up = False # the committed attack direction (only once locked)
166
- self.shield_up = True # the player's shield: up (True) or down (False)
167
- self.blocked = False
168
- self.fired = False # DVPet onPreFinish: success is LOCKED; the shot now fires (cosmetic)
169
- self.fly_t = 0 # DVPet attackGreen countdown: the committed shot flies to the target
170
- self.strobe_t = 0 # DVPet hitAnim: the centred fullscreen collision flash strobes
171
- self.flinch_t = 0 # DVPet aftermathGreen: the pet's pose is held after the impact
172
- self._impact_args = None # the staged _finish() call, fired when the flinch ends
178
+ # data versus-training state (real DM20 tag training)
179
+ self.tt_round = 0 # current round, 0..DATA_ROUNDS-1
180
+ self.tt_past = 0 # shots that got past the shield so far
181
+ self.tt_log = [] # per-round outcomes (True = past), for the strip pips
182
+ self.tt_shield = () # the partner's 5-lane chart row (True = shield HIGH)
183
+ self.tt_shield_up = False # this round's revealed shield lane (set on fire)
184
+ self.shot_up = False # the lane the player fired
185
+ self.blocked = False # this round's outcome
186
+ self.fired = False # a shot is playing out (the round's volley timeline runs)
187
+ self.tt_tl = [] # the round's beat timeline (fire_out/fire_in/hit|block)
188
+ self.tt_i = 0 # index into tt_tl
173
189
  self._strike_pose = None # transient hit(6)/miss(9) pose during a drill
174
190
  self._strike_t = 0
175
191
  self.strike_tl = [] # the post-drill strike timeline (battle-style volley)
@@ -217,9 +233,13 @@ class TrainingPanel:
217
233
  self.vaccine_target = self._vaccine_threshold()
218
234
  self.flash = "MASH the bag!"
219
235
  elif gk == "data":
220
- r = self._attr_rank(self.pet.data_power)
221
- self.data_telegraph, self.data_window = DATA_TELEGRAPH[r], DATA_WINDOW[r]
222
- self.flash = "watch the feint — block high or low!"
236
+ # the partner's shield lanes for THIS session: the manual's repeating
237
+ # chart row, cycled by lifetime drills this stage (the printed cheat
238
+ # sequences work on tuipet exactly as on the hardware). The row lists
239
+ # WINNING shots (A=HIGH); the shield stands in the opposite lane.
240
+ row = DATA_WIN_CHART[self.pet.stage_trainings % len(DATA_WIN_CHART)]
241
+ self.tt_shield = tuple(c == "B" for c in row) # win LOW -> shield was HIGH
242
+ self.flash = f"round 1/{DATA_ROUNDS} — UP high · DOWN low"
223
243
  else:
224
244
  self.virus_speed = VIRUS_SPEEDS[self._attr_rank(self.pet.virus)]
225
245
  self.flash = "stop the bar high!"
@@ -267,8 +287,8 @@ class TrainingPanel:
267
287
  self._strong = hits >= 3
268
288
  self.result = self.pet.apply_training(hits, power, attribute, game=game)
269
289
  if game == "data":
270
- # DATA is purely DEFENSIVE (DVPet attackGreen -> the CANNON shoots the pet, you
271
- # just block -- the shot + impact already played out in the drill). Reveal directly.
290
+ # DATA already played its five volleys IN the drill (every round is a
291
+ # full fire -> reveal -> impact beat) -- a sixth would be noise. Reveal.
272
292
  self.phase = "done"
273
293
  self.flash = self.result + " (SPACE)"
274
294
  else:
@@ -330,35 +350,19 @@ class TrainingPanel:
330
350
  1 if self.taps >= thr * 0.5 else 0) # a clear overshoot = strong, near-miss = partial
331
351
  self._finish(hits, int(self.taps), "Vaccine", "vaccine")
332
352
  elif gk == "data":
333
- if self.fired: # success is locked (DVPet onPreFinish); the
334
- if self.fly_t > 0: # finale is cosmetic. Same beats as the battle
335
- self.fly_t -= 1 # collision. DVPet attackGreen: the shot flies...
336
- if self.fly_t == 0:
337
- self.sfx = "attackHit" # ...DVPet hitAnim: it LANDS -> the flash strobes
338
- self.strobe_t = EXPLODE_FRAMES
339
- return
340
- if self.strobe_t > 0: # DVPet hitAnim: the fullscreen flash strobes,
341
- self.strobe_t -= 1 # then aftermathGreen holds the pet's pose
342
- if self.strobe_t == 0:
343
- self.flinch_t = FLINCH_T
344
- return
345
- if self.flinch_t > 0:
346
- self.flinch_t -= 1
347
- if self.flinch_t == 0:
348
- self._finish(*self._impact_args)
349
- return
350
- return
351
- self.data_t += 1
352
- if not self.locked: # telegraph: the cannon's barrel feints up/down
353
- if self.data_t % DATA_BOB == 0:
354
- self.feint_up = not self.feint_up
355
- if self.data_t >= self.data_telegraph: # ...then it COMMITS high or low: the barrel
356
- self.locked = True # locks and the orb appears at the muzzle to
357
- self.tgt_up = random.choice((True, False)) # charge (you can still toggle the shield)
358
- self.data_t = 0
359
- self.flash = "block it — toggle the shield!"
360
- elif self.data_t >= self.data_window: # charge window closes -> EVAL (DVPet onPreFinish)
361
- self._data_resolve()
353
+ if not self.fired: # the PICK act waits on the player --
354
+ return # turn-based, like the HP reel's stop
355
+ if self.tt_i < len(self.tt_tl) - 1: # the round's volley marches, beat by beat
356
+ self.tt_i += 1
357
+ m = self.tt_tl[self.tt_i]["m"]
358
+ if m != self._last_sm: # one-shot sting at each beat edge
359
+ if m == "hit":
360
+ self.sfx = "attackHit" # DVPet hitAnim: it got PAST
361
+ elif m == "block":
362
+ self.sfx = "cancel" # dead on the shield
363
+ self._last_sm = m
364
+ else:
365
+ self._data_next_round()
362
366
  else: # virus -- DVPet drawVirusPre: the bar FILLS then snaps back to 0 and loops;
363
367
  self.pos += self.virus_speed # hit captures the level at that instant
364
368
  if self.pos >= VIRUS_MAX:
@@ -400,28 +404,57 @@ class TrainingPanel:
400
404
  if gk == "hp": # the reel spins itself: SPACE stops it on the match
401
405
  if k in ("space", "enter"):
402
406
  self._hp_resolve(self.hp_pick == self.hp_target)
403
- elif gk == "data": # DVPet onShield: ONE button toggles the shield top<->bot.
404
- if not self.fired and k in ("space", "enter", "up", "down", "k", "j"):
405
- self.shield_up = not self.shield_up # toggleable until the shot commits (onPreFinish)
407
+ elif gk == "data": # DM20 versus training: A/B = fire high or low
408
+ if not self.fired and k in ("up", "k"):
409
+ self._data_fire(True)
410
+ elif not self.fired and k in ("down", "j"):
411
+ self._data_fire(False)
406
412
  elif k == "space": # vaccine mash / virus stop
407
413
  self._strike()
408
414
  return None
409
415
 
410
- def _data_resolve(self):
411
- """The shot commits (DVPet checkSuccess: success = shieldActiveTop == isUp). A block
412
- (shield matches the attack side) succeeds, else it gets through. The shot then fires
413
- and lands -- attackGreen (fly) -> hitAnim (strobe) -> aftermathGreen (flinch) -- and
414
- the staged _finish (_impact_args) reveals the result once the flinch ends."""
415
- self.blocked = self.shield_up == self.tgt_up
416
+ def _data_fire(self, up):
417
+ """The player's shot commits (DM20 manual: past = the partner's shield stood
418
+ in the OTHER lane). The round plays out in the battle's alternating-view
419
+ volley grammar: fire_out (the mon looses its real orb) -> fire_in (cut to
420
+ the partner -- the shield lane REVEALS, the orb arrives) -> the fullscreen
421
+ hit strobe (past) or the blocked tableau."""
422
+ self.shot_up = up
423
+ self.tt_shield_up = self.tt_shield[self.tt_round]
424
+ self.blocked = (up == self.tt_shield_up)
425
+ ft = max(3, FIRE_T // 2) # the battle's flight halved: five rounds
426
+ # per session need a brisker volley
427
+ tl = [{"m": "fire_out", "prog": (s + 1) / ft} for s in range(ft)]
428
+ tl += [{"m": "fire_in", "prog": (s + 1) / ft} for s in range(ft)]
416
429
  if self.blocked:
417
- self.flash = "BLOCKED!"
418
- self._impact_args = (3, 60, "Data", "data")
430
+ tl += [{"m": "block"}] * DATA_BLOCK_HOLD
431
+ else:
432
+ tl += [{"m": "hit", "f": (s // EXPLODE_HOLD) % 2} for s in range(EXPLODE_FRAMES)]
433
+ self.tt_tl, self.tt_i = tl, 0
434
+ self.fired = True
435
+ self._last_sm = "fire_out"
436
+ self.sfx = "attack"
437
+ self.flash = "HIGH!" if up else "LOW!"
438
+
439
+ def _data_next_round(self):
440
+ """Score the round, then the next pick -- or the session verdict (manual:
441
+ "You need to succeed 3 out of the 5 rounds")."""
442
+ past = not self.blocked
443
+ self.tt_log.append(past)
444
+ if past:
445
+ self.tt_past += 1
446
+ # the family's hit/miss tell rides the next act (pose only -- the round's
447
+ # own sting already played at the hit/block beat, no double beep)
448
+ self._strike_pose, self._strike_t = (6 if past else 9), 4
449
+ self.tt_round += 1
450
+ self.fired = False
451
+ self._last_sm = None
452
+ if self.tt_round >= DATA_ROUNDS:
453
+ p = self.tt_past # canon pass = 3 of 5; graded around that line
454
+ hits = 3 if p >= DATA_ROUNDS else 2 if p >= DATA_PASS else 1 if p >= 1 else 0
455
+ self._finish(hits, p * 20, "Data", "data")
419
456
  else:
420
- self.flash = "hit you!"
421
- self._impact_args = (0, 0, "Data", "data")
422
- self.fired = True # success LOCKED -> the shot fires (cosmetic)
423
- self.fly_t = DATA_FLY
424
- self.sfx = "attack" # DVPet attackGreen turretShoot (cannon fires)
457
+ self.flash = f"round {self.tt_round + 1}/{DATA_ROUNDS} — UP high · DOWN low"
425
458
 
426
459
  def _flash(self, pose):
427
460
  """Briefly show a strike (6) or recoil (9) pose, like DVPet AttackSuccess/Fail."""
@@ -500,8 +533,8 @@ class TrainingPanel:
500
533
  """The DVPet drills, rebuilt to match the real game with the real sprites:
501
534
  Vaccine: MASH the bag (bag LEFT, 'Hit!' label flashes, pet HIDDEN).
502
535
  Virus: the power bar FILLS L->R; stop it in the zone (bar centred, pet HIDDEN).
503
- Data: a FIXED cannon (left) fires a shot HIGH/LOW at the pet; raise the matching
504
- shield (two slots in front of the pet RIGHT) to block it. Defensive.
536
+ Data: the REAL DM20 versus training -- fire HIGH/LOW past the sparring
537
+ partner's shield, 3 of 5 rounds (the fan turret is gone).
505
538
  HP: read the target the training dummy wants, pick the matching attribute (dummy LEFT,
506
539
  pet RIGHT face-off; the picker glyphs live in the gauge) -- on a flat LCD."""
507
540
  gk = self.gkey
@@ -574,86 +607,57 @@ class TrainingPanel:
574
607
  zx = fx + 1 + min(track_w - 1, int(track_w * VIRUS_BAR_MIN / VIRUS_MAX))
575
608
  overlay += [(zx, fy + y) for y in range(fh)]
576
609
  elif gk == "data":
577
- # Built like the VIRUS drill: the whole stage is CENTRED in the LCD (band-centred,
578
- # NOT jammed on the floor) over the same habitat bg. Cannon LEFT, pet RIGHT, the
579
- # shield standing in front of the pet (high OR low), the shot flying between them.
580
- if self.strobe_t > 0: # COLLISION: clone DVPet hitAnim exactly --
581
- # resetScreen() then a CENTRED fullscreen flash strobing attackHit<->attackHitFlash.
582
- # Same render as the battle's hit (centred _EXPLODE), NOT a burst on the busy scene.
583
- ex = _EXPLODE[(self.strobe_t // EXPLODE_HOLD) % len(_EXPLODE)]
610
+ # The REAL DM20's VERSUS TRAINING (canon rebuild 2026-07-13 -- DVPet's
611
+ # turret duel here was fan invention; manual: "you choose to fire a
612
+ # high shot or a low shot. If your shot gets past your partner's
613
+ # shield, you succeed"). Each round plays in the battle's
614
+ # alternating-view volley grammar -- ONE combatant on screen at a
615
+ # time, the same look every other drill's strike already wears:
616
+ # PICK -- faceoff: the sparring partner (the square-marked Data
617
+ # dummy) LEFT, the mon RIGHT, both idle; UP/DOWN commits
618
+ # OUT -- cut to the mon: it looses its REAL orb along the lane
619
+ # IN -- cut to the partner: the shield SNAPS to the chart's
620
+ # lane (the reveal) as the orb arrives -- pressed dead on
621
+ # the shield (blocked) or through into the partner (past)
622
+ # HIT -- past: the battle's fullscreen strobe owns the window
623
+ fr = self.tt_tl[min(self.tt_i, len(self.tt_tl) - 1)] if self.fired else None
624
+ m = fr["m"] if fr else "pick"
625
+ orb = data.attack_orb(self.pet.num, "Data", self.pet.data_power)
626
+ oh = len(orb) if orb else 8
627
+ ow = max(len(r) for r in orb) if orb else 8
628
+ lane_y = BAND_TOP if self.shot_up else BASE_Y - oh # HIGH rides the band top, LOW the floor
629
+ shield = E.get("train_shield", [None])[0]
630
+ sh_h = len(shield) if shield else 6
631
+ sw = len(shield[0]) if shield else 5
632
+ if m == "hit": # PAST: DVPet hitAnim, the window is the flash
633
+ ex = _EXPLODE[fr["f"]]
584
634
  overlay += _blit(ex, GRID_X0 + max(0, (GRID_W - len(ex[0])) // 2),
585
635
  BAND_TOP + max(0, (BASE_Y - BAND_TOP - len(ex)) // 2))
586
- else:
587
- aim_up = self.tgt_up if self.locked else self.feint_up
588
- cannon = E.get("train_green_up" if aim_up else "train_green", [None])[0] # barrel aim only
589
- shield = E.get("train_shield", [None])[0]
590
- sh_h = len(shield) if shield else 6
591
- sw = len(shield[0]) if shield else 5
592
- ch = len(cannon) if cannon else 9
593
- # One reaction language across the drills (consistency polish,
594
- # Joel 2026-07-06 "similar and consistent like the hp drill"):
595
- # a BLOCK flashes the same success pose as the HP drill's right
596
- # pick (6); canon aftermathGreen stood the pet plain, but the
597
- # family's layout language outranks canon here (the standing rule)
598
- if self.flinch_t > 0:
599
- pose = ATTACK if self.blocked else COLLAPSE # success tell / the hurt pose
600
- elif self.fired: # DVPet attackGreen: the pet braces for the shot
601
- pose = IDLE
602
- else: # the mon bobs through AIM *and* the LOCK window
603
- # (both are live decision windows, like the HP
604
- # reel) -- it goes still only for the shot
605
- pose = self._pose_now(1 if (self.frame_i // 2) % 2 else 0)
606
- # crop to the real body so the mon can be placed precisely
607
- # (centred, hugged by the shields) instead of floating in padding
608
- pf = _crop(self._frame(rec, pose))
609
- phh = len(pf)
610
- px = GRID_X0 + GRID_W - max(len(r) for r in pf) # the mon flush on the window's right
611
- # edge: even a 16px mon fits (gate ends
612
- # x19, the widest mon starts x20)
613
- # Data layout INSIDE the 32x16 window (LAW 2026-07-11; the
614
- # 07-06 measured stage still leaked into the bezel on both
615
- # sides): turret x4..13 · air x14 · gate x15..19 · the mon
616
- # flush right, x20..35 at its widest.
617
- # Three staged acts, the MON on stage for all of them (canon
618
- # drawDataPre bobs the pet through the aim; HP-drill consistency,
619
- # Joel 2026-07-06 -- the old staging hid the mon until the LOCK):
620
- # AIM -- barrel feinting, the mon bobbing behind its gate
621
- # LOCK -- the barrel commits and the mon BRACES; the toggle
622
- # window runs ("block it!")
623
- # SHOOT -- the turret recoils, the pellet bursts out of the barrel and
624
- # flies the lane into the gate; then hitAnim's strobe.
625
- floor = BASE_Y # the shared grid floor (2px above bottom)
626
- cannon_x = GRID_X0 # turret on the window's left edge; the
627
- # recoil beat kicks 1px past it -- a
628
- # momentary LEFT-edge exit, the lawful kind
629
- sx = 15 # the gate, mid-stage
630
- py = floor - phh
631
- cy = floor - ch # turret grounded
632
- recoil = -1 if (self.fired and self.fly_t >= DATA_FLY - 1) else 0
633
- overlay.extend(_blit(cannon, cannon_x + recoil, cy))
634
- overlay.extend(_blit(pf, px, py)) # on stage every act
635
- hi_y = STRIKE_BAND_TOP + 1 # HIGH lane gate: up near the band top
636
- lo_y = floor - sh_h # LOW lane gate: grounded on the floor
637
- # ONE solid shield, drawn only in the lane it guards -- the open lane
638
- # is empty. (DVPet's transparent shieldTransp ghost was a mouse
639
- # click-target; on a 1-bit LCD with a keyboard toggle it's just
640
- # noise -- the old 1-in-4 dither read as scattered dots.)
641
- on_y = hi_y if self.shield_up else lo_y
642
- if shield:
643
- overlay.extend(_blit(shield, sx, on_y))
644
- if self.fired and self.fly_t > 0: # the shoot animation (DVPet attackGreen):
645
- # the PELLET (the Data orb, /2 through the standard downsample pipeline --
646
- # the full 8px orb is wider than the old gap) bursts out of the barrel
647
- # and flies the committed lane into the gate: 10px over DATA_FLY ticks.
648
- from .render import downsample
649
- orb = downsample(data.load_orbs()["generic"]["Data"][0], 2)
650
- ow, oh = max(len(r) for r in orb), len(orb)
651
- lane_top = hi_y if self.tgt_up else lo_y # the lane follows the ATTACK (tgt_up)
652
- lane_y = lane_top + sh_h // 2 - oh // 2 # pellet centred on that gate's row
653
- mx = cannon_x + 4 # in the barrel's mouth...
654
- end_x = sx - ow + 2 # ...to pressing the gate by 2px
655
- prog = (DATA_FLY - self.fly_t) / (DATA_FLY - 1)
656
- overlay += _blit(orb, int(mx + (end_x - mx) * prog), lane_y)
636
+ elif m == "pick": # the faceoff: partner vs mon, like the done screen
637
+ dummy = _fit_cell(_HP_DUMMIES["data"])
638
+ pf = self._frame(rec, self._pose_now(1 if (self.frame_i // 2) % 2 else 0))
639
+ placements = grid.faceoff(dummy, pf) # partner mirrored to face the mon
640
+ elif m == "fire_out": # the mon's view: it looses the shot
641
+ pose = ATTACK
642
+ xshift = -2 if fr["prog"] < 0.35 else 0 # the battle's release lunge
643
+ placements, mouth = strikefx.place_combatant(True, self._frame(rec, pose), xshift)
644
+ if orb: # from the mouth, off the near edge, in-lane
645
+ ox = int((mouth - ow) + ((GRID_X0 - ow) - (mouth - ow)) * fr["prog"])
646
+ overlay = _pop(overlay, _blit(orb, ox, lane_y))
647
+ else: # fire_in / block: the partner's view
648
+ dummy = _fit_cell(_HP_DUMMIES["data"])
649
+ dm = [r[::-1] for r in dummy] # creature stand-in faces the mon (canon mirror)
650
+ overlay.extend(_blit(dm, GRID_X0, BASE_Y - len(dm)))
651
+ dw = max(len(r) for r in dummy)
652
+ sx = GRID_X0 + dw - sw + 2 # the shield WORN on the partner's front edge,
653
+ on_y = BAND_TOP + 1 if self.tt_shield_up else BASE_Y - sh_h # snapped to the chart's lane
654
+ if shield: # _pop: it reads as a held object, not a growth
655
+ overlay = _pop(overlay, _blit(shield, sx, on_y))
656
+ if orb:
657
+ stop_x = (sx + sw - 1) if self.blocked else (GRID_X0 + 2) # dead on the shield / through
658
+ prog = 1.0 if m == "block" else fr["prog"]
659
+ ox = int(grid.X1 + (stop_x - grid.X1) * prog) # arrives from the far (right) edge
660
+ overlay = _pop(overlay, _blit(orb, ox, lane_y))
657
661
  else: # hp: pick the shape the dummy wants
658
662
  # The marked training dummy (battle bag) LEFT, full height, grounded,
659
663
  # and -- canon drawHPTraining (restaged 2026-07-04, the vaccine
@@ -718,9 +722,11 @@ class TrainingPanel:
718
722
  return (f"{cue} {self.taps}/{self.vaccine_target} "
719
723
  f"{'▓' * filled}{'░' * (8 - filled)}")
720
724
  if gk == "data":
721
- cue = (("[b]CANNON HIGH![/]" if self.tgt_up else "[b]CANNON LOW![/]")
722
- + " — block!") if self.locked else "[b]SPACE[/] flips the shield"
723
- return cue # the shield itself shows in the scene
725
+ # round pips: ▓ past / ░ blocked / · to play (3 of 5 passes) -- the
726
+ # pips ARE the score; raw numbers and game glyphs stay off the strip
727
+ pips = "".join("▓" if p else "░" for p in self.tt_log).ljust(DATA_ROUNDS, "·")
728
+ cue = "[b]...[/]" if self.fired else "[b]UP[/] high · [b]DOWN[/] low"
729
+ return f"{cue} {pips}"
724
730
  cue = ("[b]IN THE ZONE — SPACE![/]" if int(self.pos) >= VIRUS_BAR_MIN
725
731
  else "[b]SPACE[/] stops it in the zone")
726
732
  return cue # the bar itself shows in the scene
@@ -739,7 +745,7 @@ class TrainingPanel:
739
745
  # keep every hint <= menu.W (38 cols) or footer() clips it mid-word
740
746
  return {"hp": "SPACE stops the reel ESC out",
741
747
  "vaccine": "SPACE punch the bag! ESC out",
742
- "data": "SPACE flip the shield ESC out",
748
+ "data": "UP/DOWN fires past ESC out",
743
749
  "virus": "watch the marker SPACE stops it"}[self.gkey]
744
750
 
745
751
  def _attr_pow(self):
@@ -748,20 +754,21 @@ class TrainingPanel:
748
754
  "Virus": self.pet.virus}.get(self.strike_attr, 0)
749
755
 
750
756
  def _target_mirror(self):
751
- """Canon facing for the drill's target. The HP dummy is a CREATURE stand-in:
752
- DVPet drawHPTraining places it left with drawNumMirror(.., true) -- mirrored
753
- (drawNum keeps _isMirror, so the taunt flips with it). The punching bag and
754
- the cannon are PROPS drawn via setAltIcon -- never flipped (the cannon's
755
- barrel faces right off the sheet, toward the pet)."""
756
- return self.gkey == "hp"
757
+ """Canon facing for the drill's target. The HP dummy and the Data drill's
758
+ sparring partner are CREATURE stand-ins: DVPet drawHPTraining places its
759
+ dummy left with drawNumMirror(.., true) -- mirrored (drawNum keeps
760
+ _isMirror, so the taunt flips with it). The punching bag is a PROP drawn
761
+ via setAltIcon -- never flipped."""
762
+ return self.gkey in ("hp", "data")
757
763
 
758
764
  def _target_sprite(self, broken):
759
- """The thing being struck: the green target (Data drill) or the punching bag, which
760
- shows its BROKEN frame on a successful break (DVPet aftermathDefault). Fit to the
761
- 16px band so the strike animation sits in the same grid as the drill sprites."""
765
+ """The thing being struck: the sparring partner (Data), the marked dummy (HP),
766
+ or the punching bag, which shows its BROKEN frame on a successful break
767
+ (DVPet aftermathDefault). Fit to the 16px band so the strike animation sits
768
+ in the same grid as the drill sprites."""
762
769
  E = data.load_effects()
763
- if self._is_data:
764
- return _fit_cell(E.get("train_green", [None])[0]) # no broken variant for the target
770
+ if self._is_data: # the versus partner, intact either way
771
+ return _HP_DUMMIES["data"]
765
772
  if self.gkey == "hp": # HP fires at the training dummy (full height)
766
773
  return _HP_DUMMIES[("vaccine", "data", "virus")[self.hp_target]]
767
774
  return _fit_cell(E.get("punching_bag_broken" if broken else "punching_bag", [None])[0])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tuipet
3
- Version: 0.2.415
3
+ Version: 0.2.417
4
4
  Summary: A terminal virtual pet (Digimon V-Pet style) rendered with halfblock sprites, built on data extracted from DVPet.
5
5
  Author-email: Joel Taylor <joeltaylor734@gmail.com>
6
6
  License-Expression: MIT
@@ -354,9 +354,8 @@ def test_every_drill_completes_through_its_strike_within_budget():
354
354
 
355
355
  plays = {
356
356
  "vaccine": lambda pan: pan.key("space"),
357
- "data": lambda pan: (pan.key("space")
358
- if getattr(pan, "locked", False) and not pan.fired
359
- and pan.shield_up != pan.tgt_up else None),
357
+ "data": lambda pan: (pan.key("down" if pan.tt_shield[pan.tt_round] else "up")
358
+ if not pan.fired else None),
360
359
  "virus": lambda pan: pan.key("space") if getattr(pan, "pos", 0) > 80 else None,
361
360
  "hp": lambda pan: (setattr(pan, "hp_pick", pan.hp_target), pan.key("enter")),
362
361
  }
@@ -41,6 +41,8 @@ ALLOWED_SILENT = {
41
41
  "train_button": "the mouse mash button; tuipet is keyboard-driven",
42
42
  "train_cannon": "duplicate alias of train_green in the generated json",
43
43
  "train_cannon_up": "duplicate alias of train_green_up",
44
+ "train_green": "DVPet's fan turret, retired by the canon DM20 versus training (2026-07-13)",
45
+ "train_green_up": "DVPet's fan turret (up-aim frame), retired with train_green",
44
46
  }
45
47
 
46
48