Updated TAassets and HPIView for MacOS 26 and ARM#1
Open
csilvertooth wants to merge 55 commits into
Open
Conversation
Bumps MACOSX_DEPLOYMENT_TARGET from 10.12 to 10.13 (the new floor for Xcode 26) on both HPIView and TAassets. Disambiguates five Data.withUnsafeBytes call sites in TdfParser now that Swift requires an explicit buffer-pointer type. Implements the empty extractAll action in HPIView so the archive root is enumerated and handed to the existing extractItems recursion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces PieceHierarchyView (outline of piece names, primitive and vertex counts, and per-piece script references) in both HPIView and TAassets. Piece references are decoded from each unit's COB via a new UnitScript.pieceReferences walk. Selecting a piece tints it in the Metal renderer: a new highlightedPieceIndex uniform and a flat piece index interpolant drive a yellow mix in the fragment shader in both targets. Adds mod support. FileSystem.init gains a modDirectory argument that overlays mod archives on top of the base. TaassetsDocument scans <base>/mods for mod subdirectories and exposes them through a dynamic top-level Mods menu that rebuilds the filesystem and reloads the current browser on selection. Adds camera controls. Scroll-wheel and trackpad pinch adjust zoom; = / - / 0 act as keyboard zoom and reset. Reset also restores rotation. Shift-drag pitches the camera via a new rotateX step in the Metal view matrix. The HPIView preview container no longer wastes its lower third on a centered title/size block: the filename and byte count now sit as a compact footer so the 3D view and piece outline get the rest of the pane. Adds auto-fit. UnitModel.maxWorldExtent walks the piece tree once on load and the view sizes the scene around it, so large buildings like armasy no longer open zoomed past the viewport. Window resizes and piece changes re-run the fit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces PlaybackControlsView below the 3D preview in the unit detail pane. A speed slider scales the script's deltaTime from 0x to 2x, a Pause toggle freezes the timeline without touching the model state, Step advances one thirtieth of a second of script execution while paused, and a pull-down menu launches any COB module by name so building internals like the Activate sequence can be observed piece by piece. UnitViewController gains the matching API (setPlaybackSpeed, stepOnce, startScript, availableScriptFunctions) and updateAnimatingState scales deltaTime accordingly, short- circuiting when speed is zero so Metal still renders the frozen frame. Fixes a long-standing layout bug where the detail controller's view was given autoresizingMask = [.width, .width] instead of [.width, .height], so the right pane didn't grow vertically with the window; that kept the 3D viewport small and pushed the piece outline into a cramped strip at the bottom. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mods like TAESC and UH nest additional unit definitions under units/<category>/*.fbi, but FileSystem.Directory.files(withExtension:) only walked the top level, so those units never reached the unit browser. Adds an allFiles(withExtension:) walker that recurses into every subdirectory and switches both UnitBrowser and UnitInfo.collectUnits to use it. Deduplicates by base name so a mod override of a vanilla unit still appears exactly once, and logs the resolved unit count so mod troubleshooting from the console is obvious. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously File → Open required the selected directory to supply gamedata/sidedata.tdf, so a standalone folder of ufo/gp3/ccx files (e.g. ~/tafiles/mods/taesc on its own) failed to load the document. Now TaassetsDocument treats the sidedata as optional: if the file is missing or malformed, it logs a note and proceeds with an empty side list. Adds a palette fallback in the unit browser that tries Palette.texturePalette first, then Palette.standardTaPalette, and finally a neutral Palette() so the 3D preview still renders when the mod folder does not carry matching palette data. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Palette.init() built a 255-entry color array, but palette lookups index from a UInt8 so anything with byte 255 crashed the moment the new mod-folder fallback was exercised (Adv. Aircraft Plant's texture atlas hit it on selection). Bumps the default to a full 256 entries. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mods regularly ship unit thumbnails outside the vanilla unitpics/<NAME>.PCX convention: TAESC builds keep JPEGs in anims/buildpic/, other packs use PNG or BMP. Broadens the lookup to try PCX, BMP, PNG, JPG, JPEG, and TGA under unitpics/ and then JPG/JPEG/PNG/BMP under anims/buildpic/ before giving up, so mod units get thumbnails in the list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Some mod archives stash their unit FBIs outside a units/ directory. When nothing shows up under units/, UnitBrowser now falls back to walking the entire filesystem for *.fbi so mod-supplied units still surface in the list. Adds startup logs listing the root directory entries and the resolved unit count, which is the quickest way to eyeball whether the selected mod actually overlaid. Relabels the first Mods-menu entry from "Vanilla (no mod)" to "Base only: <folder>". When the user opens a mod folder directly the label now matches what is actually loaded instead of implying a vanilla install. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TaassetsDocument.read now inspects the opened folder's location. If the folder sits directly under a "mods" directory whose grandparent contains TA archives, the grandparent loads as the base and the opened folder is treated as the active mod. Opening ~/tafiles/mods/taesc directly therefore behaves the same as opening ~/tafiles and choosing taesc from the Mods menu, so the mod can resolve textures and palettes that live in the vanilla archives. Hardens the COB VM against division by zero. Some mod-supplied scripts (confirmed in TAESC) exercise the divide opcode against a zero right-hand side, which trapped Swift's / operator and crashed the app the moment the unit was selected. The operator is now guarded to return 0 on zero divisor so the animation VM keeps running. Expands unit and buildpic discovery so mod-only folders populate their lists. UnitBrowser walks the entire merged filesystem for *.fbi instead of just units/, catching TAESC's unitsE/ tree. The buildpic search iterates every root directory whose name starts with unitpic (unitpics, unitpicE, unitpicsE) with PCX plus common image formats, falling back to anims/buildpic/. AppDelegate routes the Mods-menu action through its own IBAction so dispatch no longer depends on NSDocument's validator chain, and the first menu entry now reads "Base only: <folder>" instead of a generic "Vanilla" label. Extends notes/SwiftTA_Apple_Silicon_Bootstrap.md with a feature-work section documenting the piece inspector, camera controls, playback controls, mod support, and outstanding follow-ups. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings SwiftTA up on current Xcode/macOS on Apple silicon plus a batch of feature work targeting TA archive inspection and mod browsing: piece hierarchy and script-reference outline in both HPIView and TAassets, COB playback controls with pause/step/speed/run, Metal piece highlight, camera zoom and pitch, mod-aware filesystem with a dynamic Mods menu and mod-folder auto-detect, tolerant standalone mod-folder loading, and COB divide-by-zero hardening. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaces the new TAassets and HPIView features at the top of the README so the fork's intent is obvious from the repo landing page. Points at notes/SwiftTA_Apple_Silicon_Bootstrap.md for the full write-up, summarizes the piece inspector, COB playback, camera controls, and mod-aware filesystem, and gives copy-paste build and usage instructions for both apps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reworks the map detail pane so the selected map fills the available viewport. The map name moves from a giant centered label sitting below a 62% content box to a compact header strip that also shows planet, player count, wind, tidal, and gravity from the OTA. The detail container pins content to the full remaining height and the sidebar view's autoresizing mask is corrected ([.width, .width] was a typo mirroring the one already fixed on the unit browser). Swaps the sidebar icons from stock AppKit images to SF Symbols where available: cube.fill for Units, scope for Weapons, map.fill for Maps, folder.fill for Files. Guarded by #available(macOS 11) so the 10.13 deployment target still builds on older systems. Adds a filter search field above the unit and map lists. The unit search matches name, title, description, and object; the map search matches the file base name. Typing filters live. MapView now auto-fits the loaded map to the viewport instead of always opening at 1:1 magnification (some maps are 16k pixels wide, and a 500px pane at 1:1 was useless). Wraps the scroll view's document view in a new MapOverlayView that draws numbered, numbered start-position markers from the OTA schema. Markers scroll and zoom with the map and expose a showMarkers toggle for future UI wiring. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Applies 28pt of top padding to the sidebar stack so the Units icon no longer overlaps the red/yellow/green window controls. Bottom inset is also bumped so spacing stays balanced. Reworks UnitDetailViewController's title strip to match the map detail pane: the object name sits as a small header alongside a secondary detail line listing unit.title, description, side, tedclass, footprint, and maxVelocity when present. The 3D preview now pins directly under the header instead of floating below an oversized title. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Raises DynamicTileMetalTntViewRenderer's maximumDisplaySize from 4096 to 8192 so maps like [ESC] Dark Prime render cleanly on 4K-class Retina displays. Bigger maps (any resolution) still work fine; they simply scroll through the 8192-pixel working window. The grid clamping in computeTileGrid also caps the requested tile area at maximumGridSize so a viewport larger than the pool no longer overflows the pre-sized index or slice buffers. Replaces the empty Weapons sidebar pane with a working browser that walks every weapons/*.tdf recursively, parses each top-level block with TdfParser, and lists the discovered weapons in a searchable table. Selecting a weapon shows its key, source file, weapon type, range, damage table, and full property set. Matches the UX of the unit and map browsers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Metal map view only refreshed its viewport when the scrollView posted a bounds-changed notification, which would occasionally get dropped around a reload so the second map stopped redrawing even as the overlay markers scrolled. Each draw() now pulls the current clip view bounds directly so scrolling and magnification stay consistent across map swaps. Surfaces a console warning when feature loading throws (commonly due to a missing TA_Features_2013.ccx alongside the base archives) instead of swallowing the error with try?. Broadens the weapons browser to any top-level directory whose name starts with "weapon" (covers the weaponE/weaponsE mod variants that already caused trouble in the unit and unitpic paths) and walks the parsed TDF recursively, identifying weapon blocks by characteristic properties (weapontype, range, reloadtime, damage subobject, …) instead of assuming a flat list. Logs the directories and TDF count on load so empty results are easier to diagnose. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The map fragment shaders used the default clamp_to_edge sampler, so any time the viewport or a partial tile extended beyond the map's pixel area the last row/column of terrain smeared to fill the rest. Both shaders now discard fragments that fall outside the map: the single-quad variant checks texCoord against [0,1], the dynamic tile variant compares world-space position against a new mapSize uniform supplied by both renderers. Samplers also switch to clamp_to_zero so nothing bleeds in if the bounds check misses. TaassetsDocument now opens its window at a reasonable default size (70% of screen width, 80% height, capped at 1600x1100, floored at 1100x750) and persists future size and position under the "TaassetsMainWindow" frame autosave name. A 900x600 minimum stops it from collapsing smaller than the asset browsers can use. The Weapons browser no longer filters blocks through a narrow "looks-like-a-weapon" heuristic. Every top-level block with properties is shown, and the user can narrow the list with the existing search field. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends notes/SwiftTA_Apple_Silicon_Bootstrap.md with a new section covering the browser chrome changes, map viewer improvements, mod-unit discovery, the weapons browser, and the COB divide-by-zero fix, all with links to the relevant source files so future spelunking is fast. Mirrors the highlights into the README fork callout so the GitHub landing page reflects the current feature set. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Metal uniform only carried room for 40 piece matrices, but mod-supplied units (TAESC's CORMKL spider among them) have more, so the extra transforms overflowed the uniform buffer, clobbering later fields and leaving the shader reading zeros for those pieces. Result: legs and other limbs rendered collapsed on the base or vanished. The uniform now carries 128 slots, and the Swift side caps the per-frame copy to that capacity with a one-shot warning if a future unit exceeds the limit. Reworks the unit-view auto-fit to consider the current viewport aspect ratio. The previous implementation multiplied the model extent by 2.3 to pick a scene width, but scene height = sceneWidth · aspectRatio so a wide-but-short window would crop tall mod units. The new computation picks whichever of modelDiameter and modelDiameter / aspectRatio is larger, guaranteeing both axes show the full 2.4·extent box. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends notes/SwiftTA_Apple_Silicon_Bootstrap.md with why the pieces[40] uniform was biting TAESC's CORMKL and what changed on the renderer side, plus the revised computeSceneSize math that now guarantees the full unit fits the viewport in either orientation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UnitModel.init always threw away all but the first sibling of the
root node (root = model.roots.first!) while UnitModel.roots was kept
internal. The BFS parser correctly accumulates every top-level
sibling from offset 0 in the 3DO, but the renderers only ever walked
from model.root, so any piece that lived on a sibling tree — a
pattern several TAESC mod units use for extra appendages — never
reached the vertex buffer or the transformation list. CORMKL in
particular kept its legs on sibling roots, which is why the body
rendered cleanly while the legs disappeared.
Exposes the roots array on UnitModel and walks every root in:
- TAassets' Metal unit renderer (vertex + outline + transform
passes)
- HPIView's Metal model renderer (vertex + outline passes)
- the piece-hierarchy outline in both apps
- UnitModel.maxWorldExtent so the auto-fit camera accounts for
pieces on secondary roots
Adds a startup print of per-unit piece/primitive/script-module
counts plus the full piece name list, so future "X is missing its Y"
reports can be diagnosed straight from /tmp/taassets.log.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Records why CORMKL's legs were missing (UnitModel.init dropped every root past the first) and lists the call sites that were updated to walk model.roots so future mod units with sibling trees render completely. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
getUnitValue and getFunctionResult both just pushed 0 regardless of what the COB script asked for. TA spider units (confirmed with TAESC's CORMKL) lean heavily on the IK trio — PIECE_XZ to query a leg's geometric offset, XZ_ATAN to derive a rotation angle from that packed offset, and XZ_HYPOT for the matching distance — and then turn-now the leg into its starting pose during Create. With everything returning 0 the atan was 0 and every leg that needed this path collapsed onto the body origin, matching the "side legs missing" symptom. Adds UnitModel.parents (ancestor chain per piece) and UnitModel.pieceStaticOffset so the VM can sum a piece's offsets without re-evaluating the whole transform chain. Stores the UnitModel on UnitScript.Context so Instructions can reach the piece tree. Implements packXZ / unpackXZ along with taAtan2 and taHypot (TA encodes a full turn as 65536 angle units). getUnitValue now returns sane defaults for activation, health, standing orders, and position queries; getFunctionResult handles PIECE_XZ, PIECE_Y, XZ_ATAN, XZ_HYPOT, ATAN, HYPOT, and the zero-returning unit/ground queries. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Records why CORMKL's side legs collapsed (PIECE_XZ / XZ_ATAN / XZ_HYPOT all returning 0) and the VM changes that now handle the packed-xz encoding, TA's 65536-unit angle space, and the piece ancestor chain used to resolve world-space piece offsets. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stack.pop(count: n) used suffix(from: n - 1) where it should have used suffix(n). suffix(from:) returns everything from that index to the end, so the returned array count varied with the current stack depth — you only got exactly n elements when the stack happened to hold 2·n - 1 items. Everywhere else it either truncated or over-read the stack, making getFunctionResult, startScript, and callScript pop a buggy mix of params from the wrong positions. Switching to suffix(n) fixes getFunctionResult for real — CORMKL's Create IK (get PIECE_XZ / XZ_ATAN) now receives the piece index at params[0] instead of whatever silent garbage the old implementation returned. Also fixes start-script / call-script parameter passing for any script that supplies more than one argument. Also routes PIECE_XZ / PIECE_Y through the UnitModel's SIMD axis convention: UnitModel remaps 3DO (x, y, z) -> SIMD (x, z, y), so offset.y is TA's Z (horizontal depth) and offset.z is TA's Y (vertical height). The getter now packs (offset.x, offset.y) for PIECE_XZ and returns offset.z for PIECE_Y. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With Stack.pop(count:) now returning items in push order (oldest first) — matching how TA's compiler lays parameters on the stack — the .reversed() in getFunctionResult, startScript, and callScript was flipping params[0] to the last argument instead of the first. For calls like get(PIECE_XZ, piece, 0, 0, 0) that meant the piece index landed in params[3] and params[0] carried a trailing zero, so every PIECE_XZ resolved to whatever piece the script happened to pad with first (typically 0). CORMKL's Create leg-positioning math always computed the same angle for every leg as a result. Dropping .reversed() lines up with the decompiler's existing convention where params[0] is the first written argument. Also dumps each selected unit's decompiled COB to /tmp/taassets-last-cob.txt so future script issues can be compared against the bytecode directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TA Create scripts (CORMKL and every other walker in TAESC) do a binary-search IK where each iteration does `turn Leg1-2 now;` and immediately reads back the resulting end-effector position via get PIECE_XZ / PIECE_Y / HYPOT. Our VM was appending `setAngle` animations to Context.animations that only flushed in the post-run applyAnimations step, so every iteration read the same stale state and the bisection never converged — leaving the legs folded straight under the body. Adds UnitModel.pieceWorldTransform and pieceWorldPosition that walk the piece's ancestor chain from the root, multiplying each piece's local translation + Euler-rotation matrix. Mirrors the renderer's matrix convention so the VM and display agree on where a piece is. Threads the instance through run() as inout / UnsafeMutablePointer so turnPieceNow and movePieceNow update piece.turn / piece.move directly on the model. Subsequent PIECE_XZ / PIECE_Y queries in the same script tick now observe the just-applied angles. Animated (with-speed) turns and moves still flow through Context.animations and the per-frame applyAnimations step. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
waitForTurn and waitForMove parked the thread in a .waitingForTurn / .waitingForMove status but nothing ever released them, so every walker loop stalled on its first synchronization point and kept queueing animations against a frozen piece state. CORMKL's walklegs queue visibly ballooned past 1000 pending rotations while nothing on screen moved. applyAnimations now checks each waiting thread after processing the animation list: if no rotation / spin / translation matching the waited piece and axis is still in flight, the thread flips back to .running for the next run() tick. waitForTurn / waitForMove also shortcircuit when no matching animation is pending at the call site so scripts that wait on something already complete don't stall. Adds startScript + per-second heartbeat logs (module found, thread count, pending animations) so future regressions are easy to eyeball from /tmp/taassets.log. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Create's binary-search IK (CORMKL and every other TAESC walker) bisects the leg-segment angle by comparing the resulting end-effector distance against a target distance. With PIECE_XZ / PIECE_Y packing integer pixels, single-degree increments often produced the same rounded hypot so consecutive bisection iterations made no progress and the loop halved down to local8 = 0 having never moved off the starting angle. Scales piece world positions by 256 before packing, giving the IK eight bits of sub-pixel precision while staying well within 16-bit signed int range for typical unit footprints. XZ_ATAN and XZ_HYPOT only care about the ratio and scale consistency of the packed arguments, so the script comparisons keep working. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With pitch-outermost (R_x·R_y·R_z), a non-zero shoulder turn.x rotated the knee's local x-axis toward vertical, and CORMKL's bisection IK became unimodal — the "bend more → shorter reach" relationship broke and the search froze at the degenerate near-zero angle. Switching the composition to R_z(turn.y)·R_y(turn.z)·R_x(turn.x) keeps a child's pitch axis in the horizontal plane regardless of the parent's pitch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three fixes that together let CORMKL's Create-time bisection converge: - PIECE_XZ / PIECE_Y return native TA integer units again; the 256× sub-pixel scaling from 785890d broke every walker that compared XZ_HYPOT against fixed integer thresholds. - taAtan2 returns unsigned [0, 65536) instead of signed [-32768, 32768]. LegGroups' quadrant checks — `XZ_ATAN(...) > 0 && < 32768` and `> 16384 && < 49152` — relied on the full unsigned range; with signed output every third-quadrant angle failed both checks and the stride-target bisection rolled to the ±200 game-unit edge. - GROUND_HEIGHT returns the world Y of the piece whose current world XZ matches the query (falling through to 0 otherwise). The zero stub made PositionLegs' `move Point to y-axis [GROUND_HEIGHT(PIECE_XZ(Point)) - PIECE_Y(Point)] speed [...]` oscillate every tick; returning the queried piece's own Y collapses the expression to zero so the Point targets sit still and the per-leg IK has a stationary aim. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CORMKL's Create() spawns PositionLegs/LegGroups/SmokeUnit threads with no signal mask, so nothing can kill them and they run a forever-gait over the viewer's empty scene. Once the Create thread returns we drop every remaining thread; user-triggered scripts still work because they create new threads after the freeze point. Also drops the 1-second auto-StartMoving and adds `d` to dump each piece's offset/turn/move/world position/parent chain — needed to diagnose which legs Create's bisection failed to converge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Runs on pushes to main, pull requests against main, version tags (v*), and manual dispatch. Both apps build Release-unsigned on a macos-14 runner and the .app bundles ship as zipped workflow artifacts. Pushing a v-prefixed tag additionally attaches them to a draft GitHub release so non-Xcode users can download the build. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A new segmented control in the map detail header toggles between no overlay, per-cell elevation tinting, and a passability heatmap (green-to-yellow for passable terrain, red where the max slope to any neighbor exceeds a threshold, blue for under-sea cells, and orange where a feature occupies the cell). The threshold slider appears only in passability mode. Intended to help line up AEX's passability computation against the actual TNT heightmap and sea level for each map. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Main-branch pushes now update a single 'latest' prerelease with the current TAassets and HPIView app zips attached, so anyone can grab an up-to-date build from the Releases page without a GitHub login or waiting on tag cadence. Version tags (v*) still cut their own permanent entries, and the draft step is gone — tagged releases publish immediately now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous README mixed three audiences (fork maintainers, the original game-client experiment, and end users trying to run TAassets / HPIView) into one wall of bullets. Split them: - README.md is now a download-first user guide — where to get the apps from the Releases page, first-launch Gatekeeper steps, what TA files the apps need, and how each browser / overlay works. - docs/FORK_NOTES.md keeps the summary of fork additions. - docs/ORIGINAL_README.md preserves the upstream loganjones/SwiftTA README verbatim (Swift 4.2 / Ubuntu 16.04 era game-client notes). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a conditional signing + notarization pipeline so Apple-blessed builds land on the Releases page and downloaders can open the apps with a double-click instead of right-click-Open. When MACOS_CERTIFICATE_P12_BASE64 and the four notarization secrets are present, the workflow imports the Developer ID cert into a temporary keychain, builds with hardened runtime and --timestamp --options=runtime flags, submits each .app to xcrun notarytool, and staples the ticket onto the bundle before zipping for release. The release notes mention whether the drop is signed/notarized. PR builds from forks and any run without secrets still produce unsigned artifacts instead of failing — useful for contributors who can't see the repository secrets. docs/SIGNING.md walks a maintainer through the one-time Developer ID certificate export, Team ID and app-specific password lookup, and the five secrets the workflow needs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… diagnosable. Previous failure mode printed only "Developer ID Application identity not found" with no indication of what was actually imported — a .p12 with just the certificate (no private key) looked identical in the log to a .p12 with the wrong certificate type. Log now prints every codesigning identity and every certificate label present in the keychain before the match, and the error message explicitly calls out the "export both the cert and its key" gotcha. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… settings don't pin signing to the upstream maintainer's team. HPIView.xcodeproj carries DEVELOPMENT_TEAM=544Z2URGY9 from the upstream loganjones fork, so xcodebuild refused to sign with our Developer ID cert: "No certificate for team 544Z2URGY9 matching 'Developer ID Application: Azimuth Systems LLC (D96…)' found." TAassets happened to sign because its xcodeproj had no team baked in. Parse the team ID out of the imported identity's Common Name and pass DEVELOPMENT_TEAM=<parsed> on every xcodebuild invocation so both apps sign under the team that owns the cert. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e diagnosable. Notarization for TAassets landed with status Invalid and the stapler step then failed with no further information. notarytool returns exit code 0 when Apple accepts then rejects a submission, so the workflow believed it had succeeded until stapler complained; and it never pulled the log that would have said *why* Apple rejected it. Switched to --output-format json, parsed the status, ran notarytool log unconditionally so Invalid + Accepted both show their log in CI output, and error-out on anything other than Accepted before stapling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t-task-allow. Apple's notary service returned Invalid with: "The executable requests the com.apple.security.get-task-allow entitlement." When xcodebuild signs a target that has no explicit CODE_SIGN_ENTITLEMENTS set, it injects a default plist that includes get-task-allow=true (so debuggers can attach). That flag is never permitted in notarized binaries, so every submission failed validation. ci/release.entitlements is a minimal plist that pins get-task-allow to false. Both TAassets and HPIView release builds now pass CODE_SIGN_ENTITLEMENTS pointing at it, so Xcode stops injecting the debug-only entitlement and notarization has a clean signature to validate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CORMKL (and most TAESC units) iterate over nearby units in Detect()
with the TA Recorder / Unofficial Patch 3.9 extensions:
for id := get MIN_ID() to get MAX_ID() do
if not get UNIT_ALLIED(id) then … check height, distance, …
Our VM fell through on all the extended IDs and returned 0, which
meant MAX_ID also returned 0, the loop ran once with id=0, and
UNIT_ALLIED(0) returning 0 classified id=0 as hostile — so every
mod unit thought it was surrounded by enemies and flipped armor /
shield state accordingly on load.
Adds the six IDs CORMKL actually hits (MIN_ID=69, MAX_ID=70,
MY_ID=71, UNIT_BUILD_PERCENT_LEFT=73, UNIT_ALLIED=74,
UNIT_IS_ON_THIS_COMP=75) to UnitScript.UnitValue, wires them into
both getUnitValue and getFunctionResult dispatchers, and teaches
the decompiler to render them by name. In the no-sim viewer:
- MIN_ID = 1, MAX_ID = 0 → iteration loop exits without entering
- MY_ID = 0
- UNIT_BUILD_PERCENT_LEFT = 0 (fully built)
- UNIT_ALLIED = 1 (always allied — "skip" branch wins)
- UNIT_IS_ON_THIS_COMP = 1
The full TADR extension set (~150 IDs) is the reference spec for a
real sim like AEX; SwiftTA only implements enough to keep mod
target-scan logic from misfiring.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s the asset inspectors. Removes the SwiftTA macOS, SwiftTA iOS, and SwiftTA Linux source trees, their xcodeproj/Package.swift entries, and the workspace references that pointed at them. CI never built them; they weren't imported by TAassets or HPIView; they were just clutter inherited from the original loganjones/SwiftTA. Anyone who wants the game client can clone upstream directly. The Swift packages the game client once shared with the inspectors (SwiftTA-Core, SwiftTA-Ctypes, SwiftTA-Metal, SwiftTA-OpenGL3) all stay — TAassets and HPIView depend on them. Also relocates the legacy screenshots (SwiftTA.jpg, TAassets.gif, HpiView.jpg) under docs/images/ and fixes the references in docs/ORIGINAL_README.md, so the preserved upstream README still renders intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Core adds: - TaMapModel.writeTnt() — serializes a TA-version TNT map to a binary blob byte-equivalent at the section level to what Cavedog writes (header/ext-header/tile-index/map-info/tile-array/features/minimap). Validates invariants (sample counts, tile sizes, feature indices) before emission. - TdfParser.Object.serializeAsTdf() and a Dictionary extension for the top-level .ota / .fbi / .tdf shape, so OTA round-trips happen at the lossless parser-object level instead of through the MapInfo view. Keys are sorted for determinism; the emitter is stable under repeated round-trips. Tests cover both writers with synthetic maps, no-feature edge cases, error paths (oversized feature names, mismatched sample counts), mutation-then-round-trip, and an env-gated real-map round-trip (SWIFTTA_TEST_MAPS_DIR) that stays skipped until someone points it at loose .tnt files. CI now runs `swift test` before building the apps so writer regressions fail fast instead of getting masked by a clean app build. No UI changes yet — that's Phase 2, which creates the AEX-MapEditor app target and the height-brush MVP. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The editor is a new macOS app target, linked against SwiftTA-Core to reuse Phase 1's TNT writer. Open a loose .tnt file, left-drag to raise heights, right-click to toggle erase (lower), Cmd-Z/Cmd-Shift-Z undo/redo, Cmd-S saves back to the file with a <name>.tnt.bak snapshot on first write. Any .ota sibling gets loaded and round-tripped through the TDF writer on save so existing metadata doesn't get discarded even though we aren't editing it yet. Rendering in this MVP is Core Graphics grayscale — Phase 4 (tile painting) will promote to the Metal map renderer shared with TAassets. Five source files total: - AppDelegate programmatic menu + File → Open - EditableMap wrapping TaMapModel + OTA with save-with-backup - HeightBrushCommand and HeightBrushStroke for the brush tool + one-stroke-per-undo semantics - MapCanvasView for the raster + brush-footprint overlay + mouse - MapEditorWindowController owning the tool palette + undo manager CI now builds / signs / notarizes the third app and ships AEX-MapEditor-macOS.zip to the rolling Latest release alongside the existing two. README lists it as early access. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Setting a title on the NSMenu but not the NSMenuItem that contains it left the menu bar showing only the app-name menu — the File, Edit, and Window headers were empty strings. macOS renders the first top-level item using the process name regardless of what we set, but every subsequent item needs its own title string. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…u bar. Without NSMainNibFile and without an @main stub that calls NSApplicationMain explicitly, nothing in the launch path sets the process activation policy to .regular. macOS then kept the previous app's menu bar and AEX-MapEditor ran as an accessory with no UI focus. Setting the policy and activating at launch gives it a proper menu bar and foreground presence. Also drops the private _setMenuName: selector fiddle for the Open Recent menu — AppKit populates that from NSDocumentController automatically for document-typed apps; we can wire a custom recent- documents menu later if needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@main on an NSApplicationDelegate calls NSApplicationMain under the hood, which reads NSMainNibFile from Info.plist to locate the delegate. This app ships without a MainMenu.xib and therefore without that key, so NSApplicationMain fell through to the default delegate — nothing. applicationWill/DidFinishLaunching never fired, installMainMenu() never ran, and the menu bar stayed at the minimum two system items (Apple + process name). main.swift now instantiates AppDelegate directly, assigns it to NSApplication.shared, sets the activation policy, and calls run(). Delegate callbacks fire, the programmatic four-menu bar installs cleanly, and the app activates as a regular foreground app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
applicationShouldTerminateAfterLastWindowClosed was returning true unconditionally, and AppKit counts the File → Open panel as a window. So dismissing the panel — cancel OR even the "just opened, haven't picked yet" state — triggered the last-window-closed rule and the app exited before anything useful happened. Gate the terminate-on-close behaviour on "has an actual map window ever been opened" so a freshly-launched editor stays running until the user has opened at least one map and then closed it. Cancelling the open panel now leaves the app idle in the menu bar, as expected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Most TA-format maps ship inside Cavedog archives, not loose on disk. Requiring users to extract with an external tool before they could edit anything was an onboarding cliff. File → Open now accepts both .tnt and the five archive formats. When the user picks an archive: - ArchiveMapPicker walks the archive's directory tree, collects every .tnt file, and pairs it with its same-basename .ota sidecar when present. - A modal popup picker lets the user choose among the contained maps, sorted by name. - The selected .tnt and (if present) .ota are extracted to a per- archive folder under Application Support/AEX-MapEditor/Extracted/ so future opens of the same archive find the files already staged and the user has a known-writable location for edits. - The editor then opens the extracted .tnt like any loose map. Save writes back to that extracted copy; Save As can move it elsewhere. OTA-extract failures log but don't block the map from opening — they just mean the editor treats the map as having no metadata. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Palette now has a Heights / Features tool switcher. Features mode surfaces a popup of every feature type already declared in the map's feature table plus an "Add feature type…" button for typing a new name (e.g. Tree01, MetalPatch). Left-click assigns the selected feature to a cell, right-click erases whatever feature is at the cell. The map canvas draws an orange square on every cell carrying a feature so the layout is visible at a glance. Every edit — cell assignment, cell erasure, feature-type append — routes through its own MapCommand and lands on the window's undo stack, so Cmd-Z rolls back in the same order the user painted. Undoing an append correctly leaves existing assigns intact unless they pointed at the removed index; future hardening can validate that invariant explicitly. Rendering the actual feature sprite (loading the planet's feature pack GAFs) is Phase 3.x; the orange-cell overlay is enough for laying out obstacles and lining up with AEX passability. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three pieces land together: - MapRasterizer assembles a full-resolution RGBA image of the map's current tile layout by walking the tile-index map and blitting each referenced tile from the tileset with the supplied palette applied. Cached on the canvas and invalidated whenever a tile command or undo/redo lands. - A Tiles tool joins Heights and Features on the palette. In Tiles mode the canvas renders the actual painted map in colour, a dropdown lists every tile in the map's tileset (with 32×32 previews as menu-item images), and clicking on the map replaces the tile at the clicked 32×32 cell with whatever the dropdown has selected. Each paint is a TilePaintCommand on the undo stack. - The vanilla Cavedog palette (PALETTE.PAL, 256 RGBA entries) ships as a bundled resource and loads lazily when a map opens. EditableMap.palette exposes it so later phases can substitute a per-planet palette when we wire feature-pack loading. Known gaps — no minimap regeneration on tile edits yet (minimap is cached as the author shipped it), no tile-picker grid (dropdown is functional but not a true palette), no bucket-fill. Those stay on the Phase 4.x list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Support for modern Mac hardware and latest OS. Added some useful features to the programs and mod compatibility for viewing mod units.