Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
open-pull-requests-limit: 5
67 changes: 67 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: CI

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: Release x64
runs-on: windows-2022
timeout-minutes: 30

steps:
- name: Check out the recursive source tree
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: 1
persist-credentials: false
submodules: recursive

- name: Install Xmake 3.0.9
uses: xmake-io/github-action-setup-xmake@3a1a5dddfc7fa625d9a698738334bf55655a861a
with:
xmake-version: '3.0.9'

- name: Verify dependency pins
shell: pwsh
run: ./scripts/Test-SubmodulePins.ps1

- name: Verify deterministic recursive source export
shell: pwsh
run: |
$first = Join-Path $env:RUNNER_TEMP 'source-first.zip'
$second = Join-Path $env:RUNNER_TEMP 'source-second.zip'
./scripts/New-RecursiveSourceArchive.ps1 -OutputPath $first
./scripts/New-RecursiveSourceArchive.ps1 -OutputPath $second
$firstHash = (Get-FileHash -LiteralPath $first -Algorithm SHA256).Hash
$secondHash = (Get-FileHash -LiteralPath $second -Algorithm SHA256).Hash
if ($firstHash -cne $secondHash) {
throw "Recursive source export is not deterministic: $firstHash != $secondHash"
}

- name: Configure Release x64
shell: pwsh
run: xmake f -c -m release -a x64 -p windows -y

- name: Build
shell: pwsh
run: xmake -r -y TrackQuestSurfaceNativeOnly

- name: Verify binary payload contract
shell: pwsh
run: |
$payload = Join-Path $env:RUNNER_TEMP 'payload'
$pluginDirectory = Join-Path $payload 'SFSE/Plugins'
New-Item -ItemType Directory -Path $pluginDirectory -Force | Out-Null
Copy-Item -LiteralPath 'build/windows/x64/release/TrackQuestSurfaceNativeOnly.dll' -Destination $pluginDirectory
./scripts/Test-BinaryPayload.ps1 -PayloadRoot $payload
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## Unreleased

- Moved reusable input, quest, vector-bound, and Surface Map engine contracts
into focused commits on the QTR CommonLibSF fork.
- Split the native plugin into entrypoint, hook transaction, Star Map input,
and Surface Map ownership/activation units with a real `PCH.h` and QTR
`logger::` usage.
- Tightened native marker capture into an owner-thread copy phase followed by
form resolution, validation, logging, and cache publication from owned data.
- Added pinned Windows CI plus deterministic recursive-source and one-DLL
payload verification. CI does not publish artifacts or claim gameplay proof.
- No development DLL from this refactor is a release candidate until its exact
hash passes the gameplay regression matrix.

## 0.2.2 — 2026-08-21

- Added support for large standalone Surface Map quest markers.
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ xmake f -c -m release -a x64 -p windows -y
xmake -r -y TrackQuestSurfaceNativeOnly
```

`COMMONLIBSF_PATH` is a developer-only override. Release builds use the pinned
`lib/commonlibsf` submodule. The build target must remain side-effect free.
Builds always use the pinned `lib/commonlibsf` submodule. The build target must
remain side-effect free.

## Change workflow

Expand Down
4 changes: 4 additions & 0 deletions MANIFEST.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Release verification manifest

This manifest records the immutable gameplay-tested `v0.2.2` release at source
commit `77671bb579fe882996c44947c30d2756c186ed07`. Development-branch builds
have different source and DLL hashes and are not covered by this release proof.

- Public name: Track Quest from Map
- Internal plugin/DLL name: TrackQuestSurfaceNativeOnly
- Version: 0.2.2.0
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,16 @@ dependency are present:
```powershell
git clone --recursive https://github.com/QTR-Modding/TrackQuestFromMap.git
cd TrackQuestFromMap
xmake f -m release -a x64 -y
xmake -y TrackQuestSurfaceNativeOnly
xmake f -c -m release -a x64 -p windows -y
xmake -r -y TrackQuestSurfaceNativeOnly
```

`COMMONLIBSF_PATH` may point at an equivalent local checkout. Building has no
install, deploy, mod-manager, or game-launch step.
The build always consumes the repository-pinned `lib/commonlibsf` submodule.
Building has no install, deploy, mod-manager, or game-launch step.

The tested 0.2.2 dependency revisions are listed in [SOURCE.md](SOURCE.md).
The tested 0.2.2 dependency revisions and the separate development dependency
pin are listed in [SOURCE.md](SOURCE.md). A CI build is compile evidence, not a
gameplay-tested release candidate.

## Development

Expand Down
11 changes: 11 additions & 0 deletions SOURCE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Corresponding source and build provenance

## Current development branch

The `refactor/qtr-native-conventions` branch pins QTR CommonLibSF commit
`04a3d88e2925806355000190c9c3a9df586ebf3f`. That five-commit series adds
the verified input-event ABI correction, maps the generic menu button-event
handler, exposes quest-instance tracking state, adds raw engine-vector bounds,
and provides typed Surface Map runtime contracts consumed by the refactor. It
is development provenance, not a claim about the released 0.2.2 DLL.

## Released 0.2.2 artifact

The distributed `TrackQuestSurfaceNativeOnly.dll` statically incorporates code
from these exact revisions:

Expand Down
53 changes: 37 additions & 16 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,21 @@ sent to Flash. The plugin bridges those two views without modifying a SWF.

## Ownership capture

Version 0.2.2 transactionally hooks the reviewed Surface Map gather and
quest-composition calls. While the game's map-state lock is held, the inner
hook copies only bounded primitive FormID/instance pairs into thread-local
storage. It does not allocate, log, look up forms, touch UI, or retain engine
pointers under that lock.

After gather returns, the plugin copies each native marker row into owned
storage:
The unreleased development refactor preserves version 0.2.2's transactional
Surface Map gather and quest-composition hooks while tightening the capture
boundary. The inner composition callback executes while the
engine holds a PlayerCharacter-owned `BSSpinLock`; it copies only bounded
primitive FormID/instance pairs into fixed-capacity thread-local storage. It
does not allocate, log, look up forms, touch UI, or retain engine pointers in
that callback.

The engine releases that lock before gather returns. The outer hook then runs
synchronously on the Surface Map state's owner/UI thread, before the vanilla
caller resumes and walks the same marker vector. Unlike the immutable 0.2.2
release, the refactor first copies every relevant
native row into plugin-owned storage without retaining a pointer or view. Only
after that copy completes does it resolve forms, log, validate, and publish the
generation:

- handle, type, location/target/active flags;
- the representation-specific raw label fields; and
Expand All @@ -32,6 +39,10 @@ storage:
Rows are kept individually because marker handles are not identities and can
repeat.

This lifetime is same-thread and call-path bounded; it is not a claim that the
marker vector is mutex-protected or generically thread-safe. Rebuild, refresh,
state transition, and destruction invalidate its native storage.

## Marker representations

### Ordinary quest overlay
Expand Down Expand Up @@ -80,14 +91,24 @@ not undo successful tracking.

## Reviewed 1.16.244 contracts

- Surface rebuild: `REL::ID(95000) + 0x77 -> REL::ID(95012)`
- Quest composition: `REL::ID(95012) + 0x237 -> REL::ID(95013)`
- Star Map input: `REL::ID(94684) + 0x10C -> REL::ID(130632)`
- Tracking helper: `REL::ID(91440)`
- Current Surface Map state accessor: `REL::ID(94755)`
- Surface Map refresh: `REL::ID(95003)`
- Star Map and Surface Map primary vtables: `REL::ID(446845)` and
`REL::ID(447074)`
- Surface rebuild:
`RE::ID::StarMap::SurfaceMapState::RebuildSurfaceMarkers` (95000) `+ 0x77`
to `GatherSurfaceQuestTargets` (95012)
- Quest composition: gather (95012) `+ 0x237` to
`RE::ID::StarMap::ComposeSurfaceQuestTarget` (95013)
- Star Map input: `RE::ID::StarMap::StarMapMenu::OnButtonEvent` (94684)
`+ 0x10C` to `RE::ID::IMenu::OnButtonEvent` (130632)
- Tracking helper: `RE::TESQuest::ToggleTracking` (91440)
- Current Surface Map state: `RE::StarMap::StarMapMenu::GetSurfaceMapState`
(94755)
- Surface Map repaint: `RE::StarMap::SurfaceMapState::Refresh` (95003)
- Primary vtables: `RE::StarMap::StarMapMenu::PRIMARY_VTABLE` (446845) and
`RE::StarMap::SurfaceMapState::PRIMARY_VTABLE` (447074)

Those reusable APIs, layouts, flags, marker types, and relocation IDs live in
the QTR CommonLibSF fork. The plugin keeps only its chosen callsite offsets and
signatures, the incomplete composition-context offset, GFx member names, and
its matching, caching, and transactional-install policy.

Detailed offsets and the exact executable hash are retained in source and
`MANIFEST.md`. They are not portable contracts.
25 changes: 17 additions & 8 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@
## Style

- C++23, Windows x64, all-extra warnings.
- Two-space indentation and attached opening braces.
- Match the surrounding file and keep format-only churn out of behavior or ABI
changes. Canonical workspace guidance does not yet choose one indentation or
brace-placement standard, so this repository does not invent one.
- Types and functions use `PascalCase`; variables use `lowerCamelCase`;
constants and enumerators use `kPascalCase`; parameters use `a_name`.
- Use fixed-width integer types at ABI and serialized boundaries.
- Keep translation units responsibility-based and few. Centralize runtime IDs,
offsets, signatures, and bounds.
- Reusable engine mappings belong in QTR CommonLibSF; plugin-specific hooks and
safety policy remain here.
- Missing reusable engine contracts are added as focused, upstream-ready
commits to the QTR CommonLibSF fork and consumed by an exact gitlink. Never
open an upstream CommonLibSF pull request without explicit permission.

The tagged 0.2.2 source deliberately stays byte-identical to the gameplay-
tested prototype source. It predates the preferred uppercase `PCH.h` and
`logger::` style. Normalize those in a separate no-behavior-change pull request
and repeat binary plus gameplay verification.
The tagged 0.2.2 source remains byte-identical to the gameplay-tested prototype
source. The development refactor uses the required uppercase `PCH.h`, focused
translation units, and QTR `logger::` style. Its changed DLL hash requires a
fresh gameplay regression before any versioned release.

## ABI rules

Expand All @@ -29,9 +34,13 @@ and repeat binary plus gameplay verification.

## Locks, ownership, and threads

- Under an engine lock, perform bounded primitive/thread-local capture only.
- Do not allocate, log, resolve forms, inspect UI, or retain an engine pointer
under that lock.
- The quest-composition callback runs under a PlayerCharacter-owned
`BSSpinLock`; perform only bounded primitive/thread-local capture there.
- The post-gather marker vector is owner-thread-only, non-reentrant, and not
protected by that lock. Copy its relevant rows synchronously before the
vanilla caller resumes; never retain a pointer or view.
- Do not allocate, log, resolve forms, or inspect UI inside the composition
callback. Resolve and publish only after native rows are fully owned.
- Copy native and GFx text immediately into bounded owned storage.
- Queue quest mutation through SFSE's main-thread interface.
- Re-resolve FormID plus instance ID and recheck state before calling a toggle
Expand Down
40 changes: 40 additions & 0 deletions docs/REPOSITORY_AUTOMATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Repository automation

The CI workflow performs three repository checks on Windows Server 2022:

1. every recursive submodule must be initialized, clean, and checked out at
the commit recorded by its parent repository;
2. two recursive source exports from Git objects must have identical SHA-256
hashes; and
3. a clean Xmake 3.0.9 Release x64 build must fit the one-DLL payload contract.

CI is compile and packaging-structure evidence only. It is not gameplay proof,
does not create a release, and does not upload artifacts.

The private repository is on QTR's GitHub Free plan, which does not enforce the
desired private-repository branch-protection rules. Changes therefore follow a
PR-only project process; the repository does not claim that GitHub currently
enforces that process.

## Local commands

```powershell
./scripts/Test-SubmodulePins.ps1
./scripts/New-RecursiveSourceArchive.ps1 -OutputPath C:\tmp\TrackQuestFromMap-source.zip

$payload = 'C:\tmp\TrackQuestFromMap-payload'
New-Item -ItemType Directory -Path "$payload\SFSE\Plugins" -Force | Out-Null
Copy-Item .\build\windows\x64\release\TrackQuestSurfaceNativeOnly.dll "$payload\SFSE\Plugins"
./scripts/Test-BinaryPayload.ps1 -PayloadRoot $payload
```

The source exporter reads tracked blobs from the root repository and every
pinned recursive submodule. It never packages working-tree files, build output,
or untracked files. Entry order, timestamps, attributes, and compression mode
are fixed so two exports of the same recursive commit graph are byte-identical.
The command refuses to overwrite an existing archive.

The binary verifier accepts exactly
`SFSE/Plugins/TrackQuestSurfaceNativeOnly.dll`, confirms that it is an AMD64 PE
DLL, and can optionally enforce an expected SHA-256 with `-ExpectedSha256`.
Passing these checks does not make a build a gameplay-tested release candidate.
4 changes: 2 additions & 2 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
- [x] Surface Map large standalone quest markers
- [x] Native quest tracking without SWF replacement
- [x] Live Surface Map rebuild after tracking
- [x] QTR CommonLib-first API layer, real `PCH.h`, focused translation units,
and `logger::` convention on the development branch

## Candidate pull requests

- [ ] Resolve the exact inactive visible owner on mixed-active shared location
overlays without using the aggregate active flag.
- [ ] Normalize the tested prototype to the preferred `PCH.h` and `logger::`
project style in a no-behavior-change PR.
- [ ] Add galaxy/system-map support after independently tracing its marker
ownership path.
- [ ] Add orbital/planet-overview support after independently tracing its
Expand Down
Loading