ff9mapkit 1.0.0b4__tar.gz → 1.0.0b5__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 (315) hide show
  1. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/PKG-INFO +1 -1
  2. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/__init__.py +1 -1
  3. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/build.py +14 -3
  4. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/config.py +120 -16
  5. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/extract.py +15 -5
  6. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit.egg-info/PKG-INFO +1 -1
  7. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/pyproject.toml +1 -1
  8. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_ate.py +17 -0
  9. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_setup.py +41 -0
  10. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_verbatim.py +59 -0
  11. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/LICENSE +0 -0
  12. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/README.md +0 -0
  13. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/__main__.py +0 -0
  14. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_animdb.py +0 -0
  15. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_animdb_all.py +0 -0
  16. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_fieldtable.py +0 -0
  17. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_fieldtext.py +0 -0
  18. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_held_poses.py +0 -0
  19. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_itemdb.py +0 -0
  20. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_modeldb.py +0 -0
  21. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_narrowmap_data.py +0 -0
  22. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_npcparams.py +0 -0
  23. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_regen_animdb.py +0 -0
  24. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_regen_animdb_all.py +0 -0
  25. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_regen_fieldtable.py +0 -0
  26. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_regen_fieldtext.py +0 -0
  27. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_regen_modeldb.py +0 -0
  28. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_regen_npcparams.py +0 -0
  29. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_regen_scenedb.py +0 -0
  30. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/_scenedb.py +0 -0
  31. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/abilities.py +0 -0
  32. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/animations.py +0 -0
  33. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/archetypes.py +0 -0
  34. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/areatitle.py +0 -0
  35. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/__init__.py +0 -0
  36. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/abilityfeatures.py +0 -0
  37. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/actiondelta.py +0 -0
  38. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/aiauthor.py +0 -0
  39. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/ailint.py +0 -0
  40. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/aipatch.py +0 -0
  41. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/battleai.py +0 -0
  42. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/battlecsv.py +0 -0
  43. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/battlepatch.py +0 -0
  44. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/build.py +0 -0
  45. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/camera_codec.py +0 -0
  46. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/camera_data.py +0 -0
  47. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/characterdelta.py +0 -0
  48. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/event_data.py +0 -0
  49. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/extract.py +0 -0
  50. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/fbx.py +0 -0
  51. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/reskin.py +0 -0
  52. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/scene_codec.py +0 -0
  53. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/scene_data.py +0 -0
  54. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/scenelint.py +0 -0
  55. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/seqasm.py +0 -0
  56. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/seqauthor.py +0 -0
  57. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/seqcodec.py +0 -0
  58. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/seqdis.py +0 -0
  59. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle/seqpatch.py +0 -0
  60. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/battle_bgm.py +0 -0
  61. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/binutils.py +0 -0
  62. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/campaign.py +0 -0
  63. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/catalog.py +0 -0
  64. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/chain.py +0 -0
  65. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/cli.py +0 -0
  66. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/__init__.py +0 -0
  67. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/areatitle.py +0 -0
  68. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/ate.py +0 -0
  69. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/camera.py +0 -0
  70. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/chest.py +0 -0
  71. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/choice.py +0 -0
  72. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/conductor.py +0 -0
  73. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/cutscene.py +0 -0
  74. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/encounter.py +0 -0
  75. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/entry_settle.py +0 -0
  76. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/equipment.py +0 -0
  77. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/event.py +0 -0
  78. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/gateway.py +0 -0
  79. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/inventory.py +0 -0
  80. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/itemdata.py +0 -0
  81. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/itemtext.py +0 -0
  82. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/jump.py +0 -0
  83. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/ladder.py +0 -0
  84. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/movement.py +0 -0
  85. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/music.py +0 -0
  86. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/npc.py +0 -0
  87. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/object.py +0 -0
  88. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/onentry.py +0 -0
  89. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/party.py +0 -0
  90. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/pathfind.py +0 -0
  91. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/platform.py +0 -0
  92. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/player.py +0 -0
  93. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/prop.py +0 -0
  94. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/region.py +0 -0
  95. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/reinit.py +0 -0
  96. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/savepoint.py +0 -0
  97. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/shop.py +0 -0
  98. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/sps_trigger.py +0 -0
  99. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/startup.py +0 -0
  100. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/synthesis.py +0 -0
  101. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/text.py +0 -0
  102. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/textcarry.py +0 -0
  103. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/verbatim.py +0 -0
  104. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/content/walkmesh_hotfix.py +0 -0
  105. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/data/__init__.py +0 -0
  106. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/data/_regen_provenance.py +0 -0
  107. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/data/provenance/blank.es.patch +0 -0
  108. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/data/provenance/blank.fr.patch +0 -0
  109. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/data/provenance/blank.gr.patch +0 -0
  110. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/data/provenance/blank.it.patch +0 -0
  111. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/data/provenance/blank.jp.patch +0 -0
  112. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/data/provenance/blank.uk.patch +0 -0
  113. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/data/provenance/blank.us.patch +0 -0
  114. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/data/provenance/manifest.json +0 -0
  115. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/data/provenance/region_template.patch +0 -0
  116. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/data/reference_arcs.toml +0 -0
  117. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/data/region_catalog.toml +0 -0
  118. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/deploystack.py +0 -0
  119. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/dialogue.py +0 -0
  120. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/eb/__init__.py +0 -0
  121. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/eb/_exprtable.py +0 -0
  122. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/eb/_membertable.py +0 -0
  123. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/eb/_optables.py +0 -0
  124. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/eb/_regen_optables.py +0 -0
  125. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/eb/cmdasm.py +0 -0
  126. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/eb/disasm.py +0 -0
  127. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/eb/edit.py +0 -0
  128. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/eb/exprasm.py +0 -0
  129. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/eb/model.py +0 -0
  130. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/eb/opcodes.py +0 -0
  131. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/eblint.py +0 -0
  132. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/editor/__init__.py +0 -0
  133. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/editor/app.py +0 -0
  134. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/editor/battle_forms.py +0 -0
  135. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/editor/breadcrumb.py +0 -0
  136. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/editor/dialogs.py +0 -0
  137. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/editor/feedback.py +0 -0
  138. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/editor/forms.py +0 -0
  139. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/editor/graphview.py +0 -0
  140. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/editor/jobs.py +0 -0
  141. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/editor/model.py +0 -0
  142. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/editor/picker.py +0 -0
  143. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/editor/theme.py +0 -0
  144. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/eventscan.py +0 -0
  145. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/flags.py +0 -0
  146. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/forkreport.py +0 -0
  147. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/hub.py +0 -0
  148. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/idgated.py +0 -0
  149. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/infohub.py +0 -0
  150. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/items.py +0 -0
  151. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/itemstats.py +0 -0
  152. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/journey.py +0 -0
  153. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/keyitems.py +0 -0
  154. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/logic_add.py +0 -0
  155. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/logic_edit.py +0 -0
  156. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/logic_map.py +0 -0
  157. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/memoria.py +0 -0
  158. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/pack.py +0 -0
  159. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/playerswap.py +0 -0
  160. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/prop_archetypes.py +0 -0
  161. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/provision.py +0 -0
  162. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/refarc.py +0 -0
  163. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/save.py +0 -0
  164. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/save_items.py +0 -0
  165. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/scene/__init__.py +0 -0
  166. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/scene/arena.py +0 -0
  167. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/scene/bgart.py +0 -0
  168. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/scene/bgi.py +0 -0
  169. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/scene/bgs.py +0 -0
  170. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/scene/bgx.py +0 -0
  171. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/scene/cam.py +0 -0
  172. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/scene/guide.py +0 -0
  173. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/scene/paint.py +0 -0
  174. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/scene/placeholder.py +0 -0
  175. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/sjbinary.py +0 -0
  176. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/sps/__init__.py +0 -0
  177. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/sps/author.py +0 -0
  178. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/sps/catalog.py +0 -0
  179. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/sps/codec.py +0 -0
  180. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/sps/edit.py +0 -0
  181. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/sps/lint.py +0 -0
  182. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/sps/render.py +0 -0
  183. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/sps/templates.py +0 -0
  184. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/sps/texture.py +0 -0
  185. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/walkmesh_hotfixes.py +0 -0
  186. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/workspace/__init__.py +0 -0
  187. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/workspace/battledoc.py +0 -0
  188. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/workspace/builddoc.py +0 -0
  189. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/workspace/forms_qt.py +0 -0
  190. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/workspace/importdoc.py +0 -0
  191. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/workspace/mapview.py +0 -0
  192. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/workspace/palette.py +0 -0
  193. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/workspace/savedoc.py +0 -0
  194. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/workspace/shell.py +0 -0
  195. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/workspace/style.py +0 -0
  196. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit/workspace/tuningdialog.py +0 -0
  197. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit.egg-info/SOURCES.txt +0 -0
  198. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit.egg-info/dependency_links.txt +0 -0
  199. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit.egg-info/entry_points.txt +0 -0
  200. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit.egg-info/requires.txt +0 -0
  201. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/ff9mapkit.egg-info/top_level.txt +0 -0
  202. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/setup.cfg +0 -0
  203. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_abilities.py +0 -0
  204. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_abilityfeatures.py +0 -0
  205. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_actiondelta.py +0 -0
  206. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_ai_phase_insert.py +0 -0
  207. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_ai_phase_insert_adversary.py +0 -0
  208. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_aiauthor.py +0 -0
  209. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_ailint.py +0 -0
  210. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_aipatch.py +0 -0
  211. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_animations.py +0 -0
  212. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_archetypes.py +0 -0
  213. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_areatitle.py +0 -0
  214. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_arming.py +0 -0
  215. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_battle.py +0 -0
  216. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_battle_bgm.py +0 -0
  217. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_battle_forms.py +0 -0
  218. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_battle_scene_codec.py +0 -0
  219. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_battle_seq.py +0 -0
  220. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_battleai.py +0 -0
  221. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_battlecsv.py +0 -0
  222. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_battlepatch.py +0 -0
  223. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_bgart.py +0 -0
  224. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_bgs.py +0 -0
  225. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_build.py +0 -0
  226. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_cameras.py +0 -0
  227. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_campaign.py +0 -0
  228. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_capstone.py +0 -0
  229. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_carry_text_lint.py +0 -0
  230. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_catalog.py +0 -0
  231. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_chain.py +0 -0
  232. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_characterdelta.py +0 -0
  233. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_choice.py +0 -0
  234. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_cli_entry.py +0 -0
  235. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_cmdasm.py +0 -0
  236. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_cmdasm_relocate.py +0 -0
  237. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_content.py +0 -0
  238. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_deploy_campaign.py +0 -0
  239. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_deploystack.py +0 -0
  240. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_dialogue.py +0 -0
  241. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_eb.py +0 -0
  242. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_eblint.py +0 -0
  243. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_editor_app.py +0 -0
  244. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_editor_breadcrumb.py +0 -0
  245. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_editor_feedback.py +0 -0
  246. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_editor_forms.py +0 -0
  247. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_editor_integration.py +0 -0
  248. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_editor_jobs.py +0 -0
  249. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_editor_model.py +0 -0
  250. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_editor_theme.py +0 -0
  251. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_entry_settle.py +0 -0
  252. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_eventscan.py +0 -0
  253. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_export.py +0 -0
  254. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_exprasm.py +0 -0
  255. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_extract_area.py +0 -0
  256. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_find_field.py +0 -0
  257. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_flags.py +0 -0
  258. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_forkreport.py +0 -0
  259. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_gateway_advance.py +0 -0
  260. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_graphview.py +0 -0
  261. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_hub_gen.py +0 -0
  262. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_idgated.py +0 -0
  263. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_import_borrow.py +0 -0
  264. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_infohub.py +0 -0
  265. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_itemdata.py +0 -0
  266. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_items.py +0 -0
  267. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_itemstats.py +0 -0
  268. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_itemtext.py +0 -0
  269. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_journey.py +0 -0
  270. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_journey_merge.py +0 -0
  271. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_jump.py +0 -0
  272. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_ladder.py +0 -0
  273. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_lint.py +0 -0
  274. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_logic_add.py +0 -0
  275. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_logic_edit.py +0 -0
  276. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_logic_map.py +0 -0
  277. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_movement.py +0 -0
  278. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_npc_model.py +0 -0
  279. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_npc_verbatim.py +0 -0
  280. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_npcparams.py +0 -0
  281. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_object_graft.py +0 -0
  282. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_occlusion.py +0 -0
  283. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_on_entry.py +0 -0
  284. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_pack.py +0 -0
  285. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_paint.py +0 -0
  286. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_party.py +0 -0
  287. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_platform.py +0 -0
  288. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_player_graft.py +0 -0
  289. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_playerswap.py +0 -0
  290. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_prop_archetypes.py +0 -0
  291. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_provision.py +0 -0
  292. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_refarc.py +0 -0
  293. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_repaint_native.py +0 -0
  294. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_reskin.py +0 -0
  295. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_save.py +0 -0
  296. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_save_items.py +0 -0
  297. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_savepoint.py +0 -0
  298. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_scene.py +0 -0
  299. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_scenelint.py +0 -0
  300. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_scroll.py +0 -0
  301. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_shared_text_block.py +0 -0
  302. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_shop.py +0 -0
  303. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_showcase.py +0 -0
  304. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_sjbinary.py +0 -0
  305. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_spawn.py +0 -0
  306. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_sps.py +0 -0
  307. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_startstate.py +0 -0
  308. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_startup.py +0 -0
  309. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_synthesis.py +0 -0
  310. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_text.py +0 -0
  311. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_textcarry.py +0 -0
  312. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_walkmesh_hotfix.py +0 -0
  313. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_workspace_style.py +0 -0
  314. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_world_hub.py +0 -0
  315. {ff9mapkit-1.0.0b4 → ff9mapkit-1.0.0b5}/tests/test_yaw_movement.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ff9mapkit
3
- Version: 1.0.0b4
3
+ Version: 1.0.0b5
4
4
  Summary: Author novel custom field maps for Final Fantasy IX (Memoria engine) from a declarative TOML project file.
5
5
  Author: GameJawnsInc
6
6
  License-Expression: MIT
@@ -15,4 +15,4 @@ Public surface is organized as:
15
15
  ff9mapkit.battle — the battle.toml -> custom battle-background (BBG) builder (fork/edit/build a 3D battle map)
16
16
  """
17
17
 
18
- __version__ = "1.0.0b4" # keep in lockstep with [project] version in pyproject.toml
18
+ __version__ = "1.0.0b5" # keep in lockstep with [project] version in pyproject.toml
@@ -1839,6 +1839,16 @@ def lint_logic(project: FieldProject) -> list[str]:
1839
1839
  for ln in _text.overflow_lines(t, wrap):
1840
1840
  out.append(f"{who} has a word too wide to fit one line ({ln!r}) -- it will overflow; "
1841
1841
  f"shorten it or raise [dialogue] wrap.")
1842
+ # DEPRECATION: `[cutscene] ate = true` is the OLD, unfaithful forced-ATE model -- a grey banner held over
1843
+ # THIS in-place cutscene. A real grey ATE WARPS you to a dedicated scene and back (verified vs 6 real grey
1844
+ # ATEs; see project-ff9-ate-system). Steer to the faithful trigger; the held-banner still builds (not an error).
1845
+ _cs = raw.get("cutscene")
1846
+ if isinstance(_cs, dict) and _cs.get("ate"):
1847
+ out.append("[cutscene] ate = true is the OLD held-banner ATE styling (a grey banner over this in-place "
1848
+ "cutscene) -- NOT how a real grey ATE works (it WARPS you to a scene, plays it, and warps you "
1849
+ "back). For a faithful forced ATE use `[[gateway]] ate = true` (the warp-in trigger: banner "
1850
+ "warning + centered title window, then the warp) + a plain [cutscene] + exit_warp on the "
1851
+ "destination field. This held-banner flavor still builds, but it isn't faithful.")
1842
1852
 
1843
1853
  # reference-data sanity (Info Hub): an [[npc]] model id / animation id the engine won't recognise.
1844
1854
  # A model NAME is handled by validate() (fatal); here we WARN on a raw id outside the known tables
@@ -3183,9 +3193,10 @@ _UID_HOTFIX_DONORS = frozenset((900, 2803))
3183
3193
 
3184
3194
 
3185
3195
  def _verbatim_donor_id(project: FieldProject):
3186
- """Best-effort donor field id of a verbatim fork (for engine-hotfix warnings). ``import --verbatim``
3187
- records it as ``[verbatim_eb] donor``; ``None`` when an older fork's toml lacks it (the warn is then
3188
- skipped -- only fields 900/2803 are affected, so a missing hint just means no warn)."""
3196
+ """Best-effort donor field id of ANY fork (verbatim OR native/synth), for engine-hotfix warnings AND the
3197
+ deploy-time ForkDonorPatch `<forkId> <donorId>` mapping. The import records it as ``[verbatim_eb] donor``
3198
+ (verbatim) or ``[field] source_field`` (native/synth); ``borrow_field`` is the BG-borrow form. ``None``
3199
+ when an older fork's toml lacks it (the warn/emit is then skipped -- pre-record forks need a hand-added line)."""
3189
3200
  for blk, key in (("verbatim_eb", "donor"), ("field", "source_field"), ("field", "borrow_field")):
3190
3201
  v = (project.raw.get(blk) or {}).get(key)
3191
3202
  if isinstance(v, bool):
@@ -46,6 +46,15 @@ _COMMON_STEAM_PATHS = (
46
46
  r"D:\SteamLibrary\steamapps\common\FINAL FANTASY IX",
47
47
  )
48
48
 
49
+ # FF9 store identifiers -- used to read the per-game install path from the Windows registry (the SAME keys
50
+ # Memoria's patcher reads, so we resolve exactly the folder it patches -- correct even on a secondary drive
51
+ # / custom Steam library). Steam + GOG are the same Unity port with an identical on-disk layout; the
52
+ # Microsoft Store / Xbox Game Pass build is a different, non-Unity, DRM-locked package Memoria can't patch,
53
+ # so the detector never targets it. Refs: Albeoris/Memoria Memoria.Patcher/GameInfo/.
54
+ _STEAM_APPID = "377840"
55
+ _GOG_GAMEID = "1375008492"
56
+ _FF9_DIRNAME = "FINAL FANTASY IX"
57
+
49
58
 
50
59
  class ConfigError(RuntimeError):
51
60
  """Raised when a required path cannot be resolved or does not exist."""
@@ -80,34 +89,129 @@ def save_game_path(game_path: str | os.PathLike) -> Path:
80
89
  return USER_CONFIG
81
90
 
82
91
 
92
+ def _is_ff9_root(p: Path) -> bool:
93
+ """True if ``p`` is a real, MODDABLE FF9 install (Steam or GOG -- same layout). Keys off Memoria's own
94
+ sentinel (``FF9_Launcher.exe``) plus ``StreamingAssets`` and the managed-DLL tree -- which also rejects
95
+ the un-moddable Microsoft Store build (no launcher / no Managed dir) and stale empty folders."""
96
+ try:
97
+ return ((p / "FF9_Launcher.exe").is_file()
98
+ and (p / "StreamingAssets").is_dir()
99
+ and ((p / "x64" / "FF9_Data" / "Managed").is_dir()
100
+ or (p / "x86" / "FF9_Data" / "Managed").is_dir()))
101
+ except OSError:
102
+ return False
103
+
104
+
105
+ def _reg_str(hive, subkey: str, value: str) -> str | None:
106
+ """Read one registry string value, trying the 64-bit THEN 32-bit view (FF9's keys live under
107
+ ``WOW6432Node`` on 64-bit Windows). Windows-only; returns None elsewhere or if the key/value is absent."""
108
+ if os.name != "nt":
109
+ return None
110
+ import winreg # noqa: PLC0415 - Windows-only, lazy
111
+ for view in (winreg.KEY_WOW64_64KEY, winreg.KEY_WOW64_32KEY):
112
+ try:
113
+ with winreg.OpenKeyEx(hive, subkey, 0, winreg.KEY_READ | view) as key:
114
+ data, _ = winreg.QueryValueEx(key, value)
115
+ if data:
116
+ return str(data)
117
+ except OSError:
118
+ continue
119
+ return None
120
+
121
+
122
+ def _registry_candidates() -> list[Path]:
123
+ """FF9 roots from the per-game Steam + GOG registry keys (the exact keys Memoria reads). These point at
124
+ the ACTUAL install dir, so they find FF9 even on a secondary drive or a custom-named Steam library."""
125
+ if os.name != "nt":
126
+ return []
127
+ import winreg # noqa: PLC0415
128
+ out: list[Path] = []
129
+ steam = _reg_str(winreg.HKEY_LOCAL_MACHINE,
130
+ rf"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App {_STEAM_APPID}",
131
+ "InstallLocation")
132
+ if steam:
133
+ out.append(Path(steam))
134
+ gog = _reg_str(winreg.HKEY_LOCAL_MACHINE, rf"SOFTWARE\GOG.com\Games\{_GOG_GAMEID}", "path")
135
+ if gog:
136
+ out.append(Path(gog))
137
+ return out
138
+
139
+
140
+ def _parse_vdf_library_paths(text: str) -> list[str]:
141
+ """Library-folder paths from a Steam ``libraryfolders.vdf`` -- handles both the old ``"1" "<path>"`` and
142
+ the new ``"path" "<path>"`` schemas (and ignores the new schema's ``"apps"`` appid->buildid map). The
143
+ file doubles its backslashes, so they're unescaped here."""
144
+ keyed = re.findall(r'"path"\s+"([^"]+)"', text) if '"path"' in text \
145
+ else re.findall(r'"\d+"\s+"([^"]+)"', text)
146
+ return [p.replace("\\\\", "\\") for p in keyed]
147
+
148
+
149
+ def _steam_library_candidates() -> list[Path]:
150
+ """FF9 under every Steam library (a fallback when the per-game Uninstall key is missing): locate Steam
151
+ via the registry, parse ``libraryfolders.vdf``, and join ``steamapps/common/FINAL FANTASY IX``."""
152
+ if os.name != "nt":
153
+ return []
154
+ import winreg # noqa: PLC0415
155
+ steam = (_reg_str(winreg.HKEY_CURRENT_USER, r"Software\Valve\Steam", "SteamPath")
156
+ or _reg_str(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WOW6432Node\Valve\Steam", "InstallPath"))
157
+ if not steam:
158
+ return []
159
+ steam_dir = Path(steam)
160
+ out: list[Path] = [steam_dir / "steamapps" / "common" / _FF9_DIRNAME]
161
+ for vdf in (steam_dir / "steamapps" / "libraryfolders.vdf", steam_dir / "config" / "libraryfolders.vdf"):
162
+ try:
163
+ text = vdf.read_text(encoding="utf-8", errors="ignore")
164
+ except OSError:
165
+ continue
166
+ for lib in _parse_vdf_library_paths(text):
167
+ out.append(Path(lib) / "steamapps" / "common" / _FF9_DIRNAME)
168
+ return out
169
+
170
+
171
+ def _fallback_candidates() -> list[Path]:
172
+ """Last-resort default install folders (Steam + GOG) if registry/library lookups don't resolve."""
173
+ gog = [Path(b) / _FF9_DIRNAME for b in
174
+ (r"C:\GOG Games", r"D:\GOG Games", r"C:\Program Files (x86)\GOG Galaxy\Games")]
175
+ return [Path(p) for p in _COMMON_STEAM_PATHS] + gog
176
+
177
+
83
178
  def find_game_path(explicit: str | os.PathLike | None = None) -> Path:
84
179
  """Resolve the Final Fantasy IX install folder.
85
180
 
86
- Order: explicit arg > $FF9_GAME_PATH > ~/.ff9mapkit.toml(game_path) > common Steam dirs.
87
- Raises ConfigError with actionable guidance if none of them point at a real folder.
181
+ Order: explicit arg > ``$FF9_GAME_PATH`` > ``~/.ff9mapkit.toml`` (``game_path``) > auto-detect. The
182
+ auto-detector mirrors Memoria's: the per-game Steam + GOG registry keys, then a Steam-library scan, then
183
+ the common default folders. Explicit/env/config paths are trusted if they exist; auto-detected ones are
184
+ validated as a real, moddable FF9 install (which also skips the un-moddable Microsoft Store build).
185
+ Raises ConfigError with actionable guidance if nothing resolves.
88
186
  """
89
- candidates: list[Path] = []
90
- if explicit:
91
- candidates.append(Path(explicit))
92
- env = os.environ.get("FF9_GAME_PATH")
93
- if env:
94
- candidates.append(Path(env))
95
- cfg = _read_user_config().get("game_path")
96
- if cfg:
97
- candidates.append(Path(cfg))
98
- candidates.extend(Path(p) for p in _COMMON_STEAM_PATHS)
99
-
100
- for c in candidates:
187
+ # 1) user-specified -- trusted on existence
188
+ for c in (Path(p) for p in (explicit, os.environ.get("FF9_GAME_PATH"),
189
+ _read_user_config().get("game_path")) if p):
101
190
  if c.is_dir():
102
191
  return c.resolve()
103
192
 
193
+ # 2) auto-detect Steam + GOG, validated as a real install; de-dupe by resolved path
194
+ seen: set[Path] = set()
195
+ for c in _registry_candidates() + _steam_library_candidates() + _fallback_candidates():
196
+ try:
197
+ rc = c.resolve()
198
+ except OSError:
199
+ continue
200
+ if rc in seen:
201
+ continue
202
+ seen.add(rc)
203
+ if _is_ff9_root(c):
204
+ return rc
205
+
104
206
  raise ConfigError(
105
- "Could not locate the Final Fantasy IX install folder.\n"
207
+ "Could not locate the Final Fantasy IX install folder (checked Steam + GOG via the registry,\n"
208
+ "the Steam libraries, and the common install paths).\n"
106
209
  "Set it one of these ways:\n"
107
210
  " - pass --game \"<path>\" on the command line\n"
108
211
  " - export FF9_GAME_PATH=\"<path>\"\n"
109
212
  f" - add game_path = \"<path>\" to {USER_CONFIG}\n"
110
- "The folder should contain FF9_Launcher.exe and a StreamingAssets directory."
213
+ "The folder should contain FF9_Launcher.exe and a StreamingAssets directory.\n"
214
+ "(The Microsoft Store / Xbox Game Pass version is not moddable -- use the Steam or GOG release.)"
111
215
  )
112
216
 
113
217
 
@@ -1829,6 +1829,16 @@ def write_native_project(field: str, out_dir, *, name: str | None = None, field_
1829
1829
  wb = meta["walkmesh_bounds"]
1830
1830
  x, z = meta["player_start"]
1831
1831
  scroll = "[camera.scroll]\nenabled = true\n" if meta["scrolling"] else ""
1832
+ # The donor's REAL field id (FBG -> id): a fork MIRRORS this field's geometry, so deploy auto-emits the
1833
+ # ForkDonorPatch `<forkId> <donorId>` -> the engine's name-keyed fidelity (s31 occlusion z-offset,
1834
+ # FieldLocationName) resolves for the custom id. Recorded as `[verbatim_eb] donor` (verbatim) OR
1835
+ # `[field] source_field` (native/synth) -- both read by build._verbatim_donor_id + emitted by deploy_field.
1836
+ from .dialogue import _resolve_field_id as _rfi
1837
+ try:
1838
+ _src_fid = _rfi(field)
1839
+ except (FileNotFoundError, ValueError):
1840
+ _src_fid = None
1841
+ source_field_line = ""
1832
1842
  if verbatim:
1833
1843
  # VERBATIM .eb fork (docs/FORK_FIDELITY.md, the entry-0 carry): ship the donor's WHOLE event script;
1834
1844
  # the build runs the real logic instead of synthesizing. No declarative content (it's all in the .eb).
@@ -1845,11 +1855,7 @@ def write_native_project(field: str, out_dir, *, name: str | None = None, field_
1845
1855
  # Donor battle BGM: a verbatim fork carries the real Battle()/BattleEx() ops, but its custom id misses
1846
1856
  # the engine's (fldMapNo, scene) song lookup -> the boss/special theme is lost. Auto-emit [[battle_bgm]]
1847
1857
  # for the donor's scripted battle scenes whose song is non-zero (build -> a scene-keyed Music: line).
1848
- from .dialogue import _resolve_field_id
1849
- try:
1850
- _donor_fid = _resolve_field_id(field)
1851
- except (FileNotFoundError, ValueError):
1852
- _donor_fid = None
1858
+ _donor_fid = _src_fid
1853
1859
  bgm_pairs = _donor_battle_bgm_pairs(donor_eb, _donor_fid, game)
1854
1860
  bgm_blocks = _render_battle_bgm_blocks(bgm_pairs)
1855
1861
  # retarget the Field() exits: import-chain pre-fills a LIVE table (doors warp into the chain's own
@@ -1894,6 +1900,9 @@ def write_native_project(field: str, out_dir, *, name: str | None = None, field_
1894
1900
  field, game, out_dir=out, name=name, id_remap=id_remap, live_seams=live_seams,
1895
1901
  graft_player_funcs=graft_player_funcs, carry_text=carry_text, graft_savepoint=graft_savepoint)
1896
1902
  meta["imported_content"] = content_summary
1903
+ if _src_fid is not None and _src_fid != field_id: # record the donor -> deploy auto-emits ForkDonorPatch
1904
+ source_field_line = (f"source_field = {_src_fid} # the real field this NATIVE fork mirrors; deploy "
1905
+ f"emits ForkDonorPatch so name-keyed fidelity (occlusion/location) resolves\n")
1897
1906
  control_line = (f"control_direction = {control_dir} # imported WASD-vs-camera tuning\n"
1898
1907
  if control_dir is not None else "")
1899
1908
  content_tail = (
@@ -1913,6 +1922,7 @@ def write_native_project(field: str, out_dir, *, name: str | None = None, field_
1913
1922
  f'name = "{name}"\n'
1914
1923
  f"area = {safe_area}\n"
1915
1924
  f"text_block = {text_block}\n"
1925
+ f"{source_field_line}"
1916
1926
  f"{_walkmesh_hotfix_line(field)}"
1917
1927
  f"{_area_title_hide_lines(meta, verbatim=verbatim)}"
1918
1928
  f'bgs = "scene.bgs.bytes" # NATIVE scene (per-tile depth) -> seamless render, NO .bgx / no tile seams\n'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ff9mapkit
3
- Version: 1.0.0b4
3
+ Version: 1.0.0b5
4
4
  Summary: Author novel custom field maps for Final Fantasy IX (Memoria engine) from a declarative TOML project file.
5
5
  Author: GameJawnsInc
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ff9mapkit"
7
- version = "1.0.0b4"
7
+ version = "1.0.0b5"
8
8
  description = "Author novel custom field maps for Final Fantasy IX (Memoria engine) from a declarative TOML project file."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11" # tomllib is stdlib from 3.11
@@ -369,3 +369,20 @@ def _w(tmp_path, toml):
369
369
  p = tmp_path / "ok.field.toml"
370
370
  p.write_text(toml, encoding="utf-8")
371
371
  return p
372
+
373
+
374
+ def test_legacy_cutscene_ate_is_deprecated(tmp_path):
375
+ """`[cutscene] ate = true` (the OLD held-banner model) now lint-warns toward the faithful `[[gateway]] ate`
376
+ warp-in trigger -- it still builds (a warning, not an error)."""
377
+ from ff9mapkit.build import FieldProject, lint_logic, validate
378
+ base = ('[field]\nid = 4003\nname = "D"\narea = 11\ntext_block = 1073\n\n'
379
+ '[camera]\npitch = 45\nfov = 42.2\n\n'
380
+ '[walkmesh]\nquad = [[-200,-200],[200,-200],[200,200],[-200,200]]\n\n'
381
+ '[player]\nspawn = [0, 0]\n\n')
382
+ p = tmp_path / "d.field.toml"
383
+ p.write_text(base + '[cutscene]\nate = true\nsteps = [ { say = "An ATE." } ]\n', encoding="utf-8")
384
+ proj = FieldProject.load(p)
385
+ assert any("held-banner" in w and "[[gateway]] ate" in w for w in lint_logic(proj))
386
+ assert validate(proj) == [] # deprecated, but still valid (warning, not error)
387
+ p.write_text(base + '[cutscene]\nsteps = [ { say = "Hi." } ]\n', encoding="utf-8") # no ate -> no warning
388
+ assert not any("held-banner" in w for w in lint_logic(FieldProject.load(p)))
@@ -132,3 +132,44 @@ def test_install_engine_refuses_without_memoria(tmp_path):
132
132
  def test_bundle_missing_dll_raises(tmp_path):
133
133
  with pytest.raises(ValueError):
134
134
  memoria.bundle_dll_members(_make_bundle(tmp_path, complete=False))
135
+
136
+
137
+ # ---- game-install detection (Steam + GOG; rejects MS Store) ----------------------------------------
138
+ def _make_ff9_root(tmp_path, *, launcher=True, streaming=True, managed=True):
139
+ root = tmp_path / "FINAL FANTASY IX"
140
+ root.mkdir(parents=True, exist_ok=True)
141
+ if launcher:
142
+ (root / "FF9_Launcher.exe").write_bytes(b"")
143
+ if streaming:
144
+ (root / "StreamingAssets").mkdir(exist_ok=True)
145
+ if managed:
146
+ (root / "x64" / "FF9_Data" / "Managed").mkdir(parents=True, exist_ok=True)
147
+ return root
148
+
149
+
150
+ def test_is_ff9_root_accepts_real_layout(tmp_path):
151
+ assert config._is_ff9_root(_make_ff9_root(tmp_path)) is True
152
+
153
+
154
+ def test_is_ff9_root_rejects_incomplete(tmp_path):
155
+ assert config._is_ff9_root(_make_ff9_root(tmp_path / "a", launcher=False)) is False # MS Store-like
156
+ assert config._is_ff9_root(_make_ff9_root(tmp_path / "b", streaming=False)) is False
157
+ assert config._is_ff9_root(_make_ff9_root(tmp_path / "c", managed=False)) is False
158
+ assert config._is_ff9_root(tmp_path / "nope") is False
159
+
160
+
161
+ def test_parse_vdf_new_schema():
162
+ text = (
163
+ '"libraryfolders"\n{\n'
164
+ ' "0"\n {\n'
165
+ ' "path" "C:\\\\Program Files (x86)\\\\Steam"\n'
166
+ ' "apps" { "377840" "12345678" }\n' # appid->buildid must NOT be read as a library
167
+ ' }\n'
168
+ ' "1"\n {\n "path" "E:\\\\SteamLibrary"\n }\n}\n'
169
+ )
170
+ assert config._parse_vdf_library_paths(text) == [r"C:\Program Files (x86)\Steam", r"E:\SteamLibrary"]
171
+
172
+
173
+ def test_parse_vdf_old_schema():
174
+ text = '"LibraryFolders"\n{\n "1" "E:\\\\Games\\\\Steam"\n "2" "F:\\\\Steam"\n}\n'
175
+ assert config._parse_vdf_library_paths(text) == [r"E:\Games\Steam", r"F:\Steam"]
@@ -90,6 +90,21 @@ def test_remap_fields_patches_destinations():
90
90
  assert _vb.remap_fields(eb, {}) == eb
91
91
 
92
92
 
93
+ def test_fork_donor_id_reads_native_source_field():
94
+ """deploy_field auto-emits ForkDonorPatch for NATIVE/SYNTH forks too: build._verbatim_donor_id resolves the
95
+ donor from `[field] source_field` (the native import's record), not only `[verbatim_eb] donor`."""
96
+ from ff9mapkit.build import _verbatim_donor_id
97
+
98
+ class _P:
99
+ def __init__(self, raw):
100
+ self.raw = raw
101
+
102
+ assert _verbatim_donor_id(_P({"field": {"source_field": 351}})) == 351 # native fork
103
+ assert _verbatim_donor_id(_P({"verbatim_eb": {"donor": 1860}})) == 1860 # verbatim fork (unchanged)
104
+ assert _verbatim_donor_id(_P({"field": {"borrow_field": 100}})) == 100 # BG-borrow form
105
+ assert _verbatim_donor_id(_P({"field": {}})) is None # a non-fork synth field -> no emit
106
+
107
+
93
108
  def _game_ready():
94
109
  try:
95
110
  import UnityPy # noqa: F401,PLC0415
@@ -136,6 +151,22 @@ def test_import_verbatim_ships_the_whole_donor_eb(tmp_path):
136
151
  assert 4100 in _fields(shipped) and exits[0] not in _fields(shipped)
137
152
 
138
153
 
154
+ @pytest.mark.skipif(not _game_ready(), reason="needs the FF9 install + UnityPy")
155
+ def test_native_import_records_donor_for_forkdonorpatch(tmp_path):
156
+ # A NATIVE (non-verbatim) import now records `[field] source_field = <donor real id>`, so deploy_field can
157
+ # auto-emit ForkDonorPatch (name-keyed occlusion/location fidelity) -- no more hand-written `<fork> <donor>`.
158
+ from ff9mapkit import extract
159
+ from ff9mapkit.build import FieldProject, _verbatim_donor_id
160
+ from ff9mapkit.dialogue import _resolve_field_id
161
+ fbg = "fbg_n06_vgdl_map101_dl_inn_0"
162
+ donor = _resolve_field_id(fbg)
163
+ _m, toml = extract.write_native_project(fbg, tmp_path, name="DV") # native scene, NOT verbatim
164
+ proj = FieldProject.load(toml)
165
+ assert "verbatim_eb" not in proj.raw # it's a native fork
166
+ assert proj.raw["field"].get("source_field") == donor # donor recorded in [field]
167
+ assert _verbatim_donor_id(proj) == donor # -> deploy_field emits ForkDonorPatch
168
+
169
+
139
170
  @pytest.mark.skipif(not _game_ready(), reason="needs the FF9 install + UnityPy")
140
171
  def test_build_field_verbatim_with_logic_edit_end_to_end(tmp_path):
141
172
  # REGRESSION: a FULL build of a verbatim fork must run build_field's per-language loop (which reads
@@ -374,6 +405,34 @@ def test_build_field_verbatim_player_walk_end_to_end(tmp_path):
374
405
  assert (2, 250, 20) in calls # conductor RunScriptSync(2, player=250, 20)
375
406
 
376
407
 
408
+ @pytest.mark.skipif(not _game_ready(), reason="needs the FF9 install + UnityPy")
409
+ def test_build_field_verbatim_conductor_exit_warp_end_to_end(tmp_path):
410
+ # A conductor on a verbatim fork with `exit_warp`: the below-band director ENDS with a fade + Field(target)
411
+ # (the warp-back) instead of EnableMove -- the player is warped out after the scene (the same lever the
412
+ # forced-ATE scene uses to return the player). exit_warp sits OUTSIDE the once-gate so it always fires.
413
+ from ff9mapkit import build, extract
414
+ from ff9mapkit.eb import EbScript
415
+ from ff9mapkit.content import object as _object
416
+ _meta, toml = extract.write_native_project("fbg_n06_vgdl_map101_dl_inn_0", tmp_path, name="DV", verbatim=True)
417
+ donor = EbScript.from_bytes(extract.extract_event_script("fbg_n06_vgdl_map101_dl_inn_0"))
418
+ project = build.FieldProject.load(toml)
419
+ project.raw["npc"] = [{"name": "lefty", "preset": "vivi", "pos": [100, 200], "dialogue": "."}]
420
+ project.raw["cutscene"] = {"once": True, "actor": ["lefty"], "exit_warp": 1153,
421
+ "steps": [{"actor": "lefty", "say": "The scene ends -- and out you go."}]}
422
+ assert build.validate(project) == []
423
+ out = tmp_path / "mod"
424
+ build.build_mod([project], out, mod_name="FF9CustomMap") # must not raise
425
+ band_lo = donor.entry_count - _object.PARTY_BAND_SIZE # lefty=band_lo, conductor=band_lo+1
426
+ ebs = [p for p in out.rglob("*.eb.bytes")]
427
+ assert ebs
428
+ for p in ebs:
429
+ s = EbScript.from_bytes(p.read_bytes())
430
+ cond = s.entry(band_lo + 1).func_by_tag(0)
431
+ ops = [i.op for i in s.instrs(cond)]
432
+ assert 1153 in [i.imm(0) for i in s.instrs(cond) if i.op == 0x2B] # ends with Field(exit_warp) -- the warp-back
433
+ assert 0x2E not in ops # NO EnableMove (the destination restores control)
434
+
435
+
377
436
  @pytest.mark.skipif(not _game_ready(), reason="needs the FF9 install + UnityPy")
378
437
  def test_build_field_verbatim_with_readable_prop_end_to_end(tmp_path):
379
438
  # A readable [[prop]] (dialogue=) ADDED to a verbatim fork: the below-band prop object is NON-bare -- it
File without changes
File without changes