diff --git a/.github/workflows/docs-check.yaml b/.github/workflows/docs-check.yaml
index ee56009..3a174be 100644
--- a/.github/workflows/docs-check.yaml
+++ b/.github/workflows/docs-check.yaml
@@ -1,31 +1,32 @@
name: Documentation Check
-# Trigger conditions for documentation workflow
on:
- # Push events to docs-related paths on active branches
push:
branches:
- main
- develop
- - documentation
- 'release/**'
- 'releases/**'
paths:
- '**.md'
- 'docs/**'
- '.github/workflows/docs-check.yaml'
- # Pull requests with docs-related changes
+ - 'tools/sync-doc-metadata.ps1'
pull_request:
branches:
- main
- develop
- - documentation
- 'release/**'
- 'releases/**'
paths:
- '**.md'
- 'docs/**'
- '.github/workflows/docs-check.yaml'
+ - 'tools/sync-doc-metadata.ps1'
+
+env:
+ DOTNET_VERSION: '9.0.x'
+ PROJECT_PATH: 'src/'
jobs:
docs-check:
@@ -33,108 +34,27 @@ jobs:
runs-on: ubuntu-latest
steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Check for build-affecting changes in documentation branch
- if: |
- (github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'documentation') ||
- (github.event_name == 'push' && github.ref == 'refs/heads/documentation')
- run: |
- echo "🔍 Checking for build-affecting changes in documentation branch..."
- echo ""
-
- # Determine the base reference for comparison
- if [ "${{ github.event_name }}" == "pull_request" ]; then
- # For pull requests, compare PR head with base
- BASE_REF="${{ github.event.pull_request.base.sha }}"
- HEAD_REF="${{ github.event.pull_request.head.sha }}"
- echo "📋 Comparing PR changes:"
- echo " Base: $BASE_REF"
- echo " Head: $HEAD_REF"
- else
- # For push events, compare with the previous commit
- BASE_REF="HEAD^"
- HEAD_REF="HEAD"
- echo "📋 Comparing push changes:"
- echo " Base: $BASE_REF"
- echo " Head: $HEAD_REF"
- fi
- echo ""
-
- # Get all changed files
- CHANGED_FILES=$(git diff --name-only "$BASE_REF" "$HEAD_REF" 2>&1)
-
- if [ $? -ne 0 ]; then
- echo "⚠️ Warning: git diff command failed"
- echo "$CHANGED_FILES"
- exit 1
- fi
-
- if [ -z "$CHANGED_FILES" ]; then
- echo "ℹ️ No files changed"
- exit 0
- fi
-
- echo "📝 Changed files:"
- echo "$CHANGED_FILES"
- echo ""
-
- # Define patterns for build-affecting files
- # These are files that should NOT be changed in a documentation-only PR
- BUILD_AFFECTING_PATTERNS=(
- "^src/"
- "^GitVersion\.yml$"
- )
-
- BLOCKED_FILES=""
- while IFS= read -r file; do
- [ -z "$file" ] && continue
- is_blocked=false
- for pattern in "${BUILD_AFFECTING_PATTERNS[@]}"; do
- if echo "$file" | grep -qE "$pattern"; then
- is_blocked=true
- break
- fi
- done
-
- if echo "$file" | grep -qE "^\.github/workflows/" && ! echo "$file" | grep -qE "^\.github/workflows/docs-"; then
- is_blocked=true
- fi
-
- if [ "$is_blocked" = true ]; then
- BLOCKED_FILES="$BLOCKED_FILES$file"$'\n'
- fi
- done <<< "$CHANGED_FILES"
-
- if [ -n "$BLOCKED_FILES" ]; then
- echo "❌ ERROR: Found build-affecting changes in documentation branch!"
- echo ""
- echo "The following files affect the build and should not be modified in PRs to the documentation branch:"
- echo "$BLOCKED_FILES"
- echo ""
- echo "The documentation branch should only contain:"
- echo " - Markdown files (*.md)"
- echo " - Files in docs/ directory"
- echo " - Documentation workflow files (docs-*.yaml)"
- echo ""
- exit 1
- fi
-
- echo "✅ All changes are documentation-only"
-
- - name: Documentation validation
- run: |
- echo "📚 Documentation Check"
- echo "Branch: ${{ github.ref }}"
- echo "Event: ${{ github.event_name }}"
- echo ""
- echo "✅ Documentation files detected and validated"
- echo ""
- echo "Future enhancements:"
- echo "- Markdown lint validation"
- echo "- Link validation"
- echo "- Documentation coverage checks"
- echo "- Build documentation site"
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: ${{ env.DOTNET_VERSION }}
+
+ - name: Restore dependencies
+ run: dotnet restore ${{ env.PROJECT_PATH }}
+
+ - name: Check docs metadata sync
+ shell: pwsh
+ run: ./tools/sync-doc-metadata.ps1 -Mode check
+
+ - name: Documentation validation summary
+ run: |
+ echo "📚 Documentation Check"
+ echo "Branch: ${{ github.ref }}"
+ echo "Event: ${{ github.event_name }}"
+ echo ""
+ echo "✅ Documentation metadata is in sync"
diff --git a/GitVersion.yml b/GitVersion.yml
index b31c063..652d44b 100644
--- a/GitVersion.yml
+++ b/GitVersion.yml
@@ -27,7 +27,7 @@ next-version: 'v0.2.1'
# Branch-specific configuration
branches:
- # Main branch: Production releases
+ # Main branch: Stable promotions
main:
# Prevent version jumping when merging from develop
# Version increments based on main's own version, not the merged branch
@@ -38,7 +38,7 @@ branches:
regex: ^master$|^main$
source-branches: ['develop', 'hotfix']
- # Develop branch: Integration branch for ongoing development
+ # Develop branch: Daily integration
develop:
# Allow normal version increment from merged branches
prevent-increment:
@@ -52,18 +52,7 @@ branches:
source-branches: ['feature', 'bugfix', 'hotfix']
track-merge-target: false
- # Documentation branch: Documentation maintenance
- documentation:
- # Don't affect main version when merging documentation
- prevent-increment:
- when-branch-merged: true
- increment: Patch
- # Docs tag for documentation releases
- label: docs
- regex: ^docs?$|^documentation$
- source-branches: []
-
- # Feature branches: New features under development
+ # Feature branches: Optional short-lived feature work
feature:
increment: Minor
# Alpha tag for feature development
@@ -71,14 +60,14 @@ branches:
regex: ^features?[/-](?\d+)
source-branches: ['develop']
- # Bugfix branches: Bug fixes under development
+ # Bugfix branches: Optional short-lived bugfix work
bugfix:
increment: Patch
label: 'bugfix.{BranchName}'
regex: ^bug(fix)?[/-]
source-branches: ['develop']
- # Hotfix branches: Emergency fixes from production
+ # Hotfix branches: Exceptional fixes based on main
hotfix:
increment: Patch
label: hotfix
@@ -93,12 +82,12 @@ branches:
# Use PR number in version (e.g., 1.1.0-pr.123)
regex: ^(pull|pull\-requests|pr)[/-](?\d+)
- # Release branches: Release preparation
+ # Release branches: Release execution from main
release:
increment: None
label: rc
regex: ^releases?[/-]
- source-branches: ['develop', 'main']
+ source-branches: ['main']
is-main-branch: false
prevent-increment:
when-branch-merged: true
diff --git a/README.md b/README.md
index 7351fe0..a7d698e 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@
- Host multiple Terraria worlds in one launcher process, keep worlds isolated, and keep extending behavior with plugins and publisher tooling on OTAPI USP.
+ Run multiple Terraria worlds from one launcher, keep each world in its own context, and handle routing, data sharing, and plugin-driven extension inside the same OTAPI USP runtime.
---
@@ -51,15 +51,14 @@
UnifierTSL wraps [OTAPI Unified Server Process](https://github.com/CedaryCat/OTAPI.UnifiedServerProcess) into a runtime you can run directly to host **multiple Terraria worlds in one launcher process**.
-The launcher handles world lifecycle, player join routing, and spins up a dedicated console client per world context so each world's I/O stays separate.
-Those console sessions now use an ANSI-safe prompt/status protocol, so launcher and per-world consoles can keep semantic readline state, replay status frames, and recover cleanly after reconnects instead of acting like plain text pipes.
-Compared with classic single-world servers or packet-routed multi-process world stacks, Unifier keeps join routing, world handoff, and extension hooks in one runtime surface instead of scattering that logic across process boundaries.
+In traditional multi-process multi-world stacks, building a cluster of cooperating worlds usually means extra cross-process routing, state synchronization, and serialization design. Moving players between instances often relies on packet relays and side channels; when plugin-attached data, temporary state, or runtime objects need to cross worlds, problems that could otherwise stay in-process often have to be rewritten as protocols and synchronization flows.
+
+Compared with approaches that push this coordination outside process boundaries, Unifier, based on OTAPI USP, keeps join routing, world switching, and extension hooks inside the same runtime plane and treats cross-world coordination as a first-class concern from the start. The launcher manages multi-world lifecycle centrally, lets each world run independently and in parallel in its own `ServerContext`, and provides a dedicated console per world so I/O stays isolated.
`UnifiedServerCoordinator` handles coordination, `UnifierApi.EventHub` carries event traffic, and `PluginHost.PluginOrchestrator` runs plugin hosting.
-With shared connection and state surfaces, you can operate worlds together and build tighter cross-world interactions, while policy-based routing and transfer hooks still leave room for world-level fallback behavior.
+This shared listener-and-coordination model reduces the extra overhead and complexity introduced by cross-process relays, making cross-world interaction, data interchange, and unified operations easier while still leaving enough routing control to define the default join target and take over later world-switch flows.
-If you push this model further, you can build more gameplay-driven setups: fully connected multi-instance world clusters, elastic worlds that load or unload region-sized shards on demand, or private worlds tuned per player for logic and resource budgets.
-These are achievable directions, not out-of-the-box defaults.
-Some heavier implementations may stay outside launcher core, but you can expect practical sample plugins for these patterns to land over time in the `plugins/` ecosystem.
+From the player's side, this still behaves like a normal Terraria entry point: clients connect to one shared listener port, and `UnifiedServerCoordinator` routes each connection to the selected world inside the same process. If you push this model further, you can build more gameplay-driven setups: fully connected multi-instance world clusters, elastic worlds that load or unload region-sized shards on demand, or private worlds tuned per player for logic and resource budgets.
+These are reachable directions, even though the launcher does not currently ship them as default out-of-the-box features, and heavier implementations like these may stay out of the launcher core itself; you can still expect usable example plugins to land under `plugins/` over time.
---
@@ -75,7 +74,7 @@ Some heavier implementations may stay outside launcher core, but you can expect
| 📦 **Collectible module contexts** | `ModuleLoadContext` gives you unloadable plugin domains and staged dependency handling |
| 📝 **Shared logging pipeline** | `UnifierApi.LogCore` supports custom filters, writers, and metadata injectors |
| 🛡 **Bundled TShock port** | Ships with a USP-adapted TShock baseline ready for use |
-| 💻 **Per-context console isolation** | Named-pipe console sessions with ANSI-safe logs, semantic readline prompts, and live status bars per world context |
+| 💻 **Per-context console isolation** | Independent, auto-reconnecting console I/O windows for each world context, plus semantic readline prompts and live status bars |
| 🚀 **RID-targeted publishing** | Publisher produces reproducible, runtime-specific directory trees |
---
@@ -124,7 +123,7 @@ Additional dependency baselines:
-Actual runtime startup flow:
+If you want the real boot order, it looks like this:
1. `Program.Main` initializes assembly resolver, applies pre-run CLI language overrides, and prints runtime version details.
2. `Initializer.Initialize()` prepares Terraria/USP runtime state and loads core hooks (`UnifiedNetworkPatcher`, `UnifiedServerCoordinator`, `ServerContext` setup).
@@ -148,6 +147,8 @@ Actual runtime startup flow:
### Pick Your Path
+If you already know why you're here, jump in from the track that matches your role:
+
| Role | Start Here | Why |
|:--|:--|:--|
| 🖥 Server operator | [Quick Start ↓](#quick-start) | Bring up a usable multi-world host with minimal setup |
@@ -158,6 +159,8 @@ Actual runtime startup flow:
## 🚀 Quick Start
+If your main goal is "get a launcher up and see worlds come online," start here.
+
### Prerequisites
Choose the requirement set that matches how you plan to run UnifierTSL:
@@ -169,6 +172,8 @@ Choose the requirement set that matches how you plan to run UnifierTSL:
### Option A: Use a Release Bundle
+If you just want to run it, this is the shortest path.
+
**1.** Download the release asset that matches your platform from [GitHub Releases](https://github.com/CedaryCat/UnifierTSL/releases):
| Platform | File pattern |
@@ -209,7 +214,7 @@ chmod +x UnifierTSL
### Option B: Run from Source
-Use this path for local debugging, CI integration, or custom bundle output.
+Take this path if you want local debugging, CI integration, or your own Publisher output.
**1.** Clone and restore:
@@ -253,6 +258,23 @@ dotnet run --project src/UnifierTSL/UnifierTSL.csproj -- \
3. Switch startup project to `UnifierTSL`, choose the `Executable` launch profile, and start debugging.
4. That profile runs the published launcher from `utsl-publish` and debugs the published program directly.
+### What Happens on First Boot
+
+- On the first successful launch, `config/config.json` is created automatically and stores the effective launcher startup snapshot. CLI arguments still win for the launch you are doing right now.
+- Plugin configs live under `config//`. For the bundled TShock port, that root is `config/TShockAPI/`; it also stores other TShock runtime files such as `tshock.sqlite` when SQLite is enabled, so in practice it fills the same role as the standalone TShock `tshock/` directory.
+- Published bundles start with a flat `plugins/` directory. During startup, the module loader may reshuffle modules into subfolders when dependency or core-module metadata says it should.
+- If everything went well, you should see the shared listener bind, the configured worlds start, the launcher status output begin updating, and, under the default console I/O implementation, one dedicated console window appear for each world.
+
+### Bundled TShock Notes
+
+- The bundled TShock here is a migration for the UnifierTSL / OTAPI USP runtime. Its lower-level logic is reimplemented by prioritizing UTSL/USP-native runtime APIs, event surfaces, packet models, and similar built-in capabilities, without maintaining an extra compatibility layer, while still aiming to keep the behavior and operator experience of TShock's higher-level features as close to upstream TShock as possible within a multi-world, single-process runtime model.
+- This port is maintained to keep tracking upstream TShock. You can inspect the current migration baseline directly in `src/Plugins/TShockAPI/TShockAPI.csproj`, especially `MainlineSyncBranch`, `MainlineSyncCommit`, and `MainlineVersion`.
+- Launcher settings stay in `config/config.json`, while the bundled TShock uses its own config-and-data root under `config/TShockAPI/`, separate from the launcher root config. This is also where other TShock runtime files live, such as `tshock.sqlite` when SQLite is enabled, so this directory effectively plays the same role as the standalone TShock `tshock/` folder.
+- `config/TShockAPI/config.json` holds global TShock defaults, while `config/TShockAPI/config.override.json` stores per-server override patches keyed by configured server name, for example `"S1": { "MaxSlots": 16 }`. `config/TShockAPI/sscconfig.json` remains a separate file for SSC settings.
+- Because the runtime hosts multiple worlds at once, some TShock data access that is usually implicit in a single-world flow becomes explicit here; for example, warp-related code paths resolve entries with an explicit `worldId` instead of only relying on the current global world state.
+- Editing `config.json` or `config.override.json` externally updates the watched config handles and reapplies runtime TShock server settings. `/reload` still matters because it additionally refreshes permissions, regions, bans, whitelist-backed state, and the classic TShock reload flow. Some changes still require a restart.
+- Finally, thanks to the TShock project and its contributors for the functionality, design work, and ecosystem this migration builds upon.
+
---
@@ -533,6 +555,8 @@ This table reflects the currently maintained/documented packaging targets, not e
|:--|:--|
| Developer Overview | [docs/dev-overview.md](./docs/dev-overview.md) |
| Plugin Development Guide | [docs/dev-plugin.md](./docs/dev-plugin.md) |
+| Branch Workflow Guide | [docs/branch-setup-guide.md](./docs/branch-setup-guide.md) |
+| Branch Workflow Quick Reference | [docs/branch-strategy-quick-reference.md](./docs/branch-strategy-quick-reference.md) |
| OTAPI Unified Server Process | [GitHub](https://github.com/CedaryCat/OTAPI.UnifiedServerProcess) |
| Upstream TShock | [GitHub](https://github.com/Pryaxis/TShock) |
| DeepWiki AI Analysis | [deepwiki.com](https://deepwiki.com/CedaryCat/UnifierTSL) *(reference only)* |
@@ -542,6 +566,3 @@ This table reflects the currently maintained/documented packaging targets, not e
Made with ❤️ by the UnifierTSL contributors · Licensed under GPL-3.0