ff9mapkit 1.0.0b3__py3-none-any.whl

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 (193) hide show
  1. ff9mapkit/__init__.py +18 -0
  2. ff9mapkit/__main__.py +36 -0
  3. ff9mapkit/_animdb.py +2994 -0
  4. ff9mapkit/_animdb_all.py +14125 -0
  5. ff9mapkit/_fieldtable.py +1516 -0
  6. ff9mapkit/_fieldtext.py +845 -0
  7. ff9mapkit/_held_poses.py +44 -0
  8. ff9mapkit/_itemdb.py +65 -0
  9. ff9mapkit/_modeldb.py +725 -0
  10. ff9mapkit/_narrowmap_data.py +10 -0
  11. ff9mapkit/_npcparams.py +634 -0
  12. ff9mapkit/_regen_animdb.py +72 -0
  13. ff9mapkit/_regen_animdb_all.py +66 -0
  14. ff9mapkit/_regen_fieldtable.py +95 -0
  15. ff9mapkit/_regen_fieldtext.py +66 -0
  16. ff9mapkit/_regen_modeldb.py +67 -0
  17. ff9mapkit/_regen_npcparams.py +123 -0
  18. ff9mapkit/_regen_scenedb.py +57 -0
  19. ff9mapkit/_scenedb.py +869 -0
  20. ff9mapkit/abilities.py +225 -0
  21. ff9mapkit/animations.py +120 -0
  22. ff9mapkit/archetypes.py +218 -0
  23. ff9mapkit/areatitle.py +76 -0
  24. ff9mapkit/battle/__init__.py +21 -0
  25. ff9mapkit/battle/abilityfeatures.py +294 -0
  26. ff9mapkit/battle/actiondelta.py +441 -0
  27. ff9mapkit/battle/aiauthor.py +305 -0
  28. ff9mapkit/battle/ailint.py +140 -0
  29. ff9mapkit/battle/aipatch.py +175 -0
  30. ff9mapkit/battle/battleai.py +148 -0
  31. ff9mapkit/battle/battlecsv.py +390 -0
  32. ff9mapkit/battle/battlepatch.py +395 -0
  33. ff9mapkit/battle/build.py +558 -0
  34. ff9mapkit/battle/camera_codec.py +332 -0
  35. ff9mapkit/battle/camera_data.py +128 -0
  36. ff9mapkit/battle/characterdelta.py +789 -0
  37. ff9mapkit/battle/event_data.py +72 -0
  38. ff9mapkit/battle/extract.py +540 -0
  39. ff9mapkit/battle/fbx.py +223 -0
  40. ff9mapkit/battle/reskin.py +149 -0
  41. ff9mapkit/battle/scene_codec.py +314 -0
  42. ff9mapkit/battle/scene_data.py +369 -0
  43. ff9mapkit/battle/scenelint.py +125 -0
  44. ff9mapkit/battle/seqasm.py +131 -0
  45. ff9mapkit/battle/seqauthor.py +220 -0
  46. ff9mapkit/battle/seqcodec.py +300 -0
  47. ff9mapkit/battle/seqdis.py +106 -0
  48. ff9mapkit/battle/seqpatch.py +137 -0
  49. ff9mapkit/battle_bgm.py +133 -0
  50. ff9mapkit/binutils.py +60 -0
  51. ff9mapkit/build.py +5445 -0
  52. ff9mapkit/campaign.py +1276 -0
  53. ff9mapkit/catalog.py +316 -0
  54. ff9mapkit/chain.py +358 -0
  55. ff9mapkit/cli.py +3114 -0
  56. ff9mapkit/config.py +360 -0
  57. ff9mapkit/content/__init__.py +13 -0
  58. ff9mapkit/content/areatitle.py +36 -0
  59. ff9mapkit/content/ate.py +118 -0
  60. ff9mapkit/content/camera.py +123 -0
  61. ff9mapkit/content/chest.py +186 -0
  62. ff9mapkit/content/choice.py +163 -0
  63. ff9mapkit/content/conductor.py +217 -0
  64. ff9mapkit/content/cutscene.py +290 -0
  65. ff9mapkit/content/encounter.py +41 -0
  66. ff9mapkit/content/entry_settle.py +50 -0
  67. ff9mapkit/content/equipment.py +93 -0
  68. ff9mapkit/content/event.py +191 -0
  69. ff9mapkit/content/gateway.py +101 -0
  70. ff9mapkit/content/inventory.py +59 -0
  71. ff9mapkit/content/itemdata.py +644 -0
  72. ff9mapkit/content/itemtext.py +168 -0
  73. ff9mapkit/content/jump.py +114 -0
  74. ff9mapkit/content/ladder.py +633 -0
  75. ff9mapkit/content/movement.py +53 -0
  76. ff9mapkit/content/music.py +97 -0
  77. ff9mapkit/content/npc.py +348 -0
  78. ff9mapkit/content/object.py +340 -0
  79. ff9mapkit/content/onentry.py +135 -0
  80. ff9mapkit/content/party.py +111 -0
  81. ff9mapkit/content/pathfind.py +138 -0
  82. ff9mapkit/content/platform.py +314 -0
  83. ff9mapkit/content/player.py +168 -0
  84. ff9mapkit/content/prop.py +75 -0
  85. ff9mapkit/content/region.py +340 -0
  86. ff9mapkit/content/reinit.py +59 -0
  87. ff9mapkit/content/savepoint.py +90 -0
  88. ff9mapkit/content/shop.py +178 -0
  89. ff9mapkit/content/sps_trigger.py +66 -0
  90. ff9mapkit/content/startup.py +71 -0
  91. ff9mapkit/content/synthesis.py +106 -0
  92. ff9mapkit/content/text.py +183 -0
  93. ff9mapkit/content/textcarry.py +290 -0
  94. ff9mapkit/content/verbatim.py +86 -0
  95. ff9mapkit/content/walkmesh_hotfix.py +38 -0
  96. ff9mapkit/data/__init__.py +48 -0
  97. ff9mapkit/data/_regen_provenance.py +142 -0
  98. ff9mapkit/data/provenance/blank.es.patch +1 -0
  99. ff9mapkit/data/provenance/blank.fr.patch +1 -0
  100. ff9mapkit/data/provenance/blank.gr.patch +1 -0
  101. ff9mapkit/data/provenance/blank.it.patch +1 -0
  102. ff9mapkit/data/provenance/blank.jp.patch +1 -0
  103. ff9mapkit/data/provenance/blank.uk.patch +1 -0
  104. ff9mapkit/data/provenance/blank.us.patch +1 -0
  105. ff9mapkit/data/provenance/manifest.json +65 -0
  106. ff9mapkit/data/provenance/region_template.patch +1 -0
  107. ff9mapkit/data/reference_arcs.toml +89 -0
  108. ff9mapkit/data/region_catalog.toml +593 -0
  109. ff9mapkit/deploystack.py +358 -0
  110. ff9mapkit/dialogue.py +803 -0
  111. ff9mapkit/eb/__init__.py +12 -0
  112. ff9mapkit/eb/_exprtable.py +59 -0
  113. ff9mapkit/eb/_membertable.py +38 -0
  114. ff9mapkit/eb/_optables.py +537 -0
  115. ff9mapkit/eb/_regen_optables.py +76 -0
  116. ff9mapkit/eb/cmdasm.py +323 -0
  117. ff9mapkit/eb/disasm.py +332 -0
  118. ff9mapkit/eb/edit.py +439 -0
  119. ff9mapkit/eb/exprasm.py +158 -0
  120. ff9mapkit/eb/model.py +178 -0
  121. ff9mapkit/eb/opcodes.py +463 -0
  122. ff9mapkit/eblint.py +177 -0
  123. ff9mapkit/editor/__init__.py +20 -0
  124. ff9mapkit/editor/app.py +950 -0
  125. ff9mapkit/editor/battle_forms.py +240 -0
  126. ff9mapkit/editor/breadcrumb.py +89 -0
  127. ff9mapkit/editor/dialogs.py +116 -0
  128. ff9mapkit/editor/feedback.py +208 -0
  129. ff9mapkit/editor/forms.py +632 -0
  130. ff9mapkit/editor/graphview.py +350 -0
  131. ff9mapkit/editor/jobs.py +342 -0
  132. ff9mapkit/editor/model.py +243 -0
  133. ff9mapkit/editor/picker.py +120 -0
  134. ff9mapkit/editor/theme.py +212 -0
  135. ff9mapkit/eventscan.py +1441 -0
  136. ff9mapkit/extract.py +2279 -0
  137. ff9mapkit/flags.py +693 -0
  138. ff9mapkit/forkreport.py +1383 -0
  139. ff9mapkit/hub.py +477 -0
  140. ff9mapkit/idgated.py +101 -0
  141. ff9mapkit/infohub.py +580 -0
  142. ff9mapkit/items.py +63 -0
  143. ff9mapkit/itemstats.py +346 -0
  144. ff9mapkit/journey.py +1902 -0
  145. ff9mapkit/keyitems.py +93 -0
  146. ff9mapkit/logic_add.py +632 -0
  147. ff9mapkit/logic_edit.py +728 -0
  148. ff9mapkit/logic_map.py +526 -0
  149. ff9mapkit/pack.py +175 -0
  150. ff9mapkit/playerswap.py +231 -0
  151. ff9mapkit/prop_archetypes.py +228 -0
  152. ff9mapkit/provision.py +282 -0
  153. ff9mapkit/refarc.py +825 -0
  154. ff9mapkit/save.py +337 -0
  155. ff9mapkit/save_items.py +1673 -0
  156. ff9mapkit/scene/__init__.py +11 -0
  157. ff9mapkit/scene/arena.py +63 -0
  158. ff9mapkit/scene/bgart.py +140 -0
  159. ff9mapkit/scene/bgi.py +732 -0
  160. ff9mapkit/scene/bgs.py +174 -0
  161. ff9mapkit/scene/bgx.py +185 -0
  162. ff9mapkit/scene/cam.py +345 -0
  163. ff9mapkit/scene/guide.py +311 -0
  164. ff9mapkit/scene/paint.py +506 -0
  165. ff9mapkit/scene/placeholder.py +107 -0
  166. ff9mapkit/sjbinary.py +285 -0
  167. ff9mapkit/sps/__init__.py +17 -0
  168. ff9mapkit/sps/author.py +294 -0
  169. ff9mapkit/sps/catalog.py +88 -0
  170. ff9mapkit/sps/codec.py +264 -0
  171. ff9mapkit/sps/edit.py +184 -0
  172. ff9mapkit/sps/lint.py +58 -0
  173. ff9mapkit/sps/render.py +116 -0
  174. ff9mapkit/sps/templates.py +47 -0
  175. ff9mapkit/sps/texture.py +131 -0
  176. ff9mapkit/walkmesh_hotfixes.py +163 -0
  177. ff9mapkit/workspace/__init__.py +18 -0
  178. ff9mapkit/workspace/battledoc.py +985 -0
  179. ff9mapkit/workspace/builddoc.py +607 -0
  180. ff9mapkit/workspace/forms_qt.py +586 -0
  181. ff9mapkit/workspace/importdoc.py +665 -0
  182. ff9mapkit/workspace/mapview.py +131 -0
  183. ff9mapkit/workspace/palette.py +85 -0
  184. ff9mapkit/workspace/savedoc.py +664 -0
  185. ff9mapkit/workspace/shell.py +6907 -0
  186. ff9mapkit/workspace/style.py +105 -0
  187. ff9mapkit/workspace/tuningdialog.py +223 -0
  188. ff9mapkit-1.0.0b3.dist-info/METADATA +155 -0
  189. ff9mapkit-1.0.0b3.dist-info/RECORD +193 -0
  190. ff9mapkit-1.0.0b3.dist-info/WHEEL +5 -0
  191. ff9mapkit-1.0.0b3.dist-info/entry_points.txt +5 -0
  192. ff9mapkit-1.0.0b3.dist-info/licenses/LICENSE +31 -0
  193. ff9mapkit-1.0.0b3.dist-info/top_level.txt +1 -0
ff9mapkit/eblint.py ADDED
@@ -0,0 +1,177 @@
1
+ """Phase-3: the field event-script (``.eb``) structural LINTER -- the ``ailint`` analogue for FIELD scripts.
2
+
3
+ Validates a field's ``.eb`` control flow OFFLINE (the "I can't see the game" superpower applied to the field
4
+ stack), so an in-place edit (Phases 2/4) can be re-checked before deploy and a verbatim fork can be vetted up
5
+ front. The ERROR checks are SOUND -- every shipping field lints with ZERO errors (proven by a sweep over all
6
+ 676 real fields; 29382 functions, 0 decode/jump/switch/reachability faults), so an error only ever flags a
7
+ genuine structural break.
8
+
9
+ Checks (per entry/function):
10
+ * decode (error) -- the body decodes cleanly to its declared boundary (a truncated/desynced eb).
11
+ * jump bounds (error) -- every relative jump (JMP/JMP_IFNOT/JMP_IF) lands ON an instruction boundary inside
12
+ its own function (engine-correct signedness; reuses :func:`eb.disasm.jump_target`).
13
+ * switch bounds (error) -- every switch (0x06/0x0B/0x0D) case + default target lands on an instruction
14
+ boundary (:func:`eb.disasm.decode_switch`, Phase 1) -- the field stack's primary dispatch, which the battle
15
+ linter treats as opaque. A switch whose operands are computed is a WARNING (its targets can't be checked).
16
+ * reachable terminator (error) -- a forward walk (follow jumps + SWITCH ARMS + fall-through, bounded by
17
+ visited offsets) flags a path that falls off the function end without a terminator (the engine would run
18
+ the IP into adjacent bytecode at runtime).
19
+ * call resolution (warning) -- a ``RunScript[Async|Sync](uid, tag)`` whose uid resolves statically (self /
20
+ Main_Init / a sibling object entry) should call a tag that entry defines; a dangling call is the #5
21
+ softlock class. WARNING not error: 25 shipping fields have a statically-dangling call (engine no-ops it or
22
+ it's an unreachable beat), so it can't be a soundness error -- but it's exactly what an editor wants flagged.
23
+ Player/party/computed targets are skipped (not resolvable offline).
24
+
25
+ Read-only + offline. ``clean`` == zero ERRORS (warnings are advisory).
26
+ """
27
+ from __future__ import annotations
28
+
29
+ from dataclasses import dataclass
30
+
31
+ from .eb import disasm
32
+ from .eb.disasm import JUMP_OPS, SWITCH_OPS, TERMINATOR_OPS, decode_switch, jump_target
33
+ from .eb.model import EbScript
34
+
35
+ _RUNSCRIPT_OPS = frozenset({0x10, 0x12, 0x14}) # RunScript[Async|Sync](level, uid, tag)
36
+
37
+
38
+ @dataclass
39
+ class EbIssue:
40
+ severity: str # "error" | "warning"
41
+ where: str # e.g. "entry7/tag3 @4210"
42
+ message: str
43
+
44
+ def __str__(self) -> str:
45
+ return f"[{self.severity}] {self.where}: {self.message}"
46
+
47
+
48
+ def _lint_function(eb, e, f, *, player_entries) -> list:
49
+ data, start, end, where = eb.data, f.abs_start, f.abs_end, f"entry{e.index}/tag{f.tag}"
50
+ issues: list = []
51
+ instrs: dict = {}
52
+ try:
53
+ for ins in disasm.iter_code(data, start, end):
54
+ instrs[ins.off] = ins
55
+ except (IndexError, KeyError):
56
+ return [EbIssue("error", where, "bytecode does not decode cleanly (truncated/corrupt)")]
57
+ if not instrs:
58
+ return [EbIssue("error", where, "empty function body")]
59
+ last = instrs[max(instrs)]
60
+ if last.end != end:
61
+ return [EbIssue("error", where, f"bytecode does not decode to the function boundary "
62
+ f"(last instr ends at {last.end}, boundary {end})")]
63
+
64
+ def _bad(t): # a target that isn't an in-function instr boundary
65
+ return t < start or t >= end or t not in instrs
66
+
67
+ for off, ins in instrs.items():
68
+ if ins.op in JUMP_OPS:
69
+ tgt = jump_target(ins)
70
+ if tgt is not None and _bad(tgt):
71
+ issues.append(EbIssue("error", f"{where} @{off}",
72
+ f"{disasm.op_name(ins.op)} target {tgt} is outside the function / not an "
73
+ f"instruction boundary [{start}..{end})"))
74
+ elif ins.op in SWITCH_OPS:
75
+ sw = decode_switch(ins)
76
+ if sw is None:
77
+ issues.append(EbIssue("warning", f"{where} @{off}",
78
+ "switch operands are computed -- its targets can't be validated"))
79
+ else:
80
+ for ed in sw.edges:
81
+ if _bad(ed.target):
82
+ arm = "default" if ed.is_default else f"case {ed.value}"
83
+ issues.append(EbIssue("error", f"{where} @{off}",
84
+ f"switch {arm} target {ed.target} is outside the function / not an "
85
+ f"instruction boundary [{start}..{end})"))
86
+ elif ins.op in _RUNSCRIPT_OPS:
87
+ issues += _check_call(eb, e.index, off, ins, where, player_entries)
88
+
89
+ # reachability -- forward walk, follow jumps + switch arms + fall-through; flag a path off the end.
90
+ seen: set = set()
91
+ stack = [start]
92
+ ran_off = False
93
+ while stack:
94
+ o = stack.pop()
95
+ if o >= end:
96
+ ran_off = True
97
+ continue
98
+ if o in seen or o not in instrs: # explored, or a bad target (already flagged)
99
+ continue
100
+ seen.add(o)
101
+ ins = instrs[o]
102
+ op = ins.op
103
+ if op in TERMINATOR_OPS:
104
+ continue
105
+ if op in SWITCH_OPS: # dispatches to its arms; never falls through
106
+ sw = decode_switch(ins)
107
+ if sw is None: # computed switch -> can't follow; treat as a stop
108
+ continue
109
+ for ed in sw.edges:
110
+ stack.append(ed.target)
111
+ continue
112
+ if op == 0x01: # unconditional JMP -> its target only
113
+ tgt = jump_target(ins)
114
+ stack.append(tgt if tgt is not None else ins.end)
115
+ elif op in (0x02, 0x03): # conditional -> target AND fall-through
116
+ tgt = jump_target(ins)
117
+ if tgt is not None:
118
+ stack.append(tgt)
119
+ stack.append(ins.end)
120
+ else:
121
+ stack.append(ins.end)
122
+ if ran_off:
123
+ issues.append(EbIssue("error", where, "a control-flow path runs off the end of the function without a "
124
+ "terminator (RET/TerminateEntry) -- the engine would execute "
125
+ "adjacent bytecode at runtime"))
126
+ return issues
127
+
128
+
129
+ def _check_call(eb, entry_index, off, ins, where, player_entries) -> list:
130
+ """A RunScript(uid, tag) whose uid resolves to a concrete entry (self/Main_Init/sibling object) must call a
131
+ tag that entry defines -- else a dangling dispatch (warning; 25 shipping fields trip it, so not an error)."""
132
+ from . import eventscan
133
+ uid, tag = ins.imm(1), ins.imm(2)
134
+ if uid is None or tag is None:
135
+ return []
136
+ kind, targets = eventscan.resolve_uid(uid, entry_index, player_entries, eb.entry_count)
137
+ if kind == "player": # any player entry defining the tag is fine
138
+ if targets and not any(0 <= t < eb.entry_count and not eb.entry(t).empty
139
+ and eb.entry(t).func_by_tag(tag) is not None for t in targets):
140
+ return [EbIssue("warning", f"{where} @{off}",
141
+ f"RunScript directs the player at tag {tag}, which no player entry defines")]
142
+ return []
143
+ if kind not in ("self", "main", "object") or not targets:
144
+ return [] # party / computed / unknown -> not checkable
145
+ t = targets[0]
146
+ te = eb.entry(t) if 0 <= t < eb.entry_count else None
147
+ if te is not None and not te.empty and te.func_by_tag(tag) is None:
148
+ return [EbIssue("warning", f"{where} @{off}",
149
+ f"RunScript to entry {t} tag {tag}, which that entry doesn't define (dangling call)")]
150
+ return []
151
+
152
+
153
+ def lint_eb(eb_bytes: bytes) -> list:
154
+ """Lint a field's ``.eb`` -> a list of :class:`EbIssue` (no ERRORS == structurally clean; warnings are
155
+ advisory). Read-only + offline."""
156
+ if not eb_bytes:
157
+ return [EbIssue("error", "eb", "empty or missing .eb data")]
158
+ try:
159
+ eb = EbScript.from_bytes(eb_bytes)
160
+ except (ValueError, IndexError, TypeError) as ex:
161
+ return [EbIssue("error", "eb", f"malformed field .eb: {type(ex).__name__}: {ex}")]
162
+ from . import eventscan
163
+ try: # the player-entry pre-pass DECODES every function;
164
+ player_entries = eventscan.resolve_player_entries(eb) # malformed bytecode here would crash before the
165
+ except (IndexError, KeyError): # per-function decode guard can flag it -> degrade so
166
+ player_entries = [] # _lint_function emits the clean "doesn't decode" error
167
+ issues: list = []
168
+ for e in eb.entries:
169
+ if e.empty:
170
+ continue
171
+ for f in e.funcs:
172
+ issues += _lint_function(eb, e, f, player_entries=player_entries)
173
+ return issues
174
+
175
+
176
+ def errors(issues) -> list:
177
+ return [i for i in issues if i.severity == "error"]
@@ -0,0 +1,20 @@
1
+ """The FF9 Map Kit field-logic editor.
2
+
3
+ A friendly front-end for the *logic* half of a field (dialogue, events, flags, encounters, music,
4
+ cutscenes) so authors never hand-write TOML. Spatial placement (camera / walkmesh / positions /
5
+ zones) stays in Blender; this editor owns the ``<field>.field.toml`` logic file, leaving any sibling
6
+ ``<field>.scene.toml`` (Blender-owned) untouched.
7
+
8
+ - ``model`` - bpy/tk-FREE: load/edit/serialize a field.toml (round-trip-safe TOML writer +
9
+ a ``FieldDoc`` that preserves the scene/field split). Fully unit-testable.
10
+ - ``app`` - the Tkinter UI over the model (imported lazily so the package stays importable
11
+ on a headless machine without a display).
12
+
13
+ Launch with ``ff9mapkit edit [field.toml]``.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from . import model # noqa: F401 (re-export the bpy/tk-free core)
19
+
20
+ __all__ = ["model"]