16 Commits

Author SHA1 Message Date
octoturge 3b4e76ad49 Fix mc-1_7_10's FORGE_VERSION: itzg rejects the "-1.7.10" suffix
gradle.properties pins forge_1_7_10_version=10.13.4.1614-1.7.10, but itzg's
installer's --forge-version validation only accepts the plain dot-separated
build number and rejected the full string with "Invalid value for
--forge-version". Confirmed by booting a real container: trimming to
10.13.4.1614 passes validation and moves on to actually downloading the
installer.
2026-08-10 07:32:43 +02:00
octoturge 6e2739672c Add itzg-based docker-compose for live-testing the mod against real MC servers
Three services (mc-1_12_2 default, mc-1_7_10/mc-neoforge-26_1 opt-in via
Compose profiles) using itzg/docker-minecraft-server, with mods/config bind-
mounted per loader/version (mods/<loader>/<version>/) so jars can be dropped
in and swapped without touching the compose file. Verified against real
containers: fixed a WSL docker credential-helper misconfig blocking pulls,
and pinned legacy Forge services to the java8 image tag after itzg's
:latest (Java 25) crashed LaunchWrapper with a ClassCastException — mc-1_12_2
now boots cleanly to "Done" on java8.
2026-08-10 06:08:49 +02:00
octoturge 376d2d0507 Phase 13: forge-1_12_2 ships blockstate/model JSON for non-cube models
BlockAssetExtractor#extractModels walks every active mod's own
jar/directory (Loader#getActiveModList's ModContainer#getSource, not
the classloader, since directory listing isn't a classloader
operation) for every blockstates/*.json and models/block/**/*.json
file, shipping all of them over a new batched block_models WS message
alongside the existing Phase 11 block_registry/block_textures dump.
Resolution of this data into real non-cube geometry is entirely
backend-side (see MCMapper-Backend's worker/src/models.rs).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tKdPZt78zbPUZMXWzKEKt
2026-08-10 01:19:36 +02:00
octoturge 47490309fe Phase 11: extract and ship block textures/registry (forge-1_12_2 leaf)
Forge doesn't split client/server jars, so a modded server's own classpath
already has every loaded mod's block textures, unused server-side.
BlockAssetExtractor reads them via the classloader (best-effort convention
match on registry name -> texture filename) plus the numeric-id -> registry-
name mapping, and ships both to the backend once per connection via two new
wire messages (block_registry, block_textures), fired through a new
BackendConnection#setReadyListener callback so the one-time send can't race
the async WS handshake. Only forge-1_12_2 wires it up so far; the other two
leaves can adopt it later with no protocol change.
2026-08-09 23:20:53 +02:00
octoturge 52417a7b92 Phase 10: implement neoforge-26_1 leaf (WS, deltas, chat, link, reconciliation, player tracking)
Ports the full feature set proven by the two legacy leaves to modern, post-Flattening
NeoForge (loader assumption confirmed: NeoForge 26.1.2.94 is real and published).
Neoforge261ChunkAdapter reads BlockState via LevelChunk/LevelChunkSection instead of raw
id+meta, carrying the full packed state id in DeltaEvent#blockStateId (a lossless int,
unlike the pre-Flattening 16-bit encoding) while documenting a known truncation caveat for
SectionData's char[]-based 3D backfill on very large modded registries. MCMapperMod uses
constructor-injected event buses (IEventBus/ModContainer) and NeoForge.EVENT_BUS instead of
@Mod.EventHandler methods, ModConfigSpec instead of legacy Configuration, and LinkCommand is
a Brigadier registration (no CommandBase in this era) fired from RegisterCommandsEvent.
Tracks its own loaded-chunk set via ChunkEvent.Load/Unload rather than querying chunk
provider internals (no stable public API for that in modern MC).

Gets its own standalone Gradle wrapper + settings.gradle (Foojay toolchain resolver) since
ModDevGradle needs Gradle 8+ and a Java 25 toolchain (MC itself now requires Java 25),
incompatible with the legacy leaves' Gradle-7/JDK-8 pin in one invocation.

Verified against real NeoForge 26.1.2.94 + decompiled MC 26.1.2 source via
./gradlew build from inside neoforge-26_1/ (two real API mismatches caught and fixed by
the compiler: ChunkPos is now a record — x()/z() methods, not fields — and
ResourceLocation was renamed to Identifier, ResourceKey#identifier() not #location()).
2026-08-09 22:44:06 +02:00
octoturge 0d8e670cd8 Phase 9: implement forge-1_7_10 leaf (WS, deltas, chat, link, reconciliation, player tracking)
Ports the full feature set proven by forge-1_12_2 (Phases 1-2-3-7-7b) to MC 1.7.10's
older, pre-block-state Forge/FML generation: Forge1710ChunkAdapter reads raw Block+meta
via Chunk/ExtendedBlockStorage instead of IBlockState, hooks BlockEvent/ChunkEvent/TickEvent
under cpw.mods.fml, and MCMapperMod/LinkCommand/Forge1710ChatBridge adapt to 1.7.10's
CommandBase/ChatComponentText/ServerConfigurationManager API shapes. No MultiPlaceEvent hook
at this Forge version (falls back to the reconciliation sweep for multi-block placements).
Verified against real Forge 10.13.4.1614-1.7.10 via ./gradlew :forge-1_7_10:build.
2026-08-09 22:13:45 +02:00
octoturge 890ce46c4f Update README for Phase 7b and fix stale Phase 7 reference
Reconciliation sweep already shipped in 52583f2; document the new
playerTrackingEnabled/playerPositionIntervalTicks config options.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tKdPZt78zbPUZMXWzKEKt
2026-08-09 20:06:48 +02:00
octoturge ef47d4481a Add throttled player-position tracking, mod side (Phase 7b)
Adds PlayerPosition (common/protocol) and BackendConnection.sendPlayerPositions(),
wired into MCMapperMod.java: a new playerPositionIntervalTicks-throttled tick timer
(gated by playerTrackingEnabled) sends the full current overworld online-player
roster to the backend each interval, mirroring the "current state, not a diff"
philosophy of columns/sections — a player logging out just stops appearing next
send, no separate leave message needed.

Wire message: {"type":"player_positions","dimension":0,"players":[...]}.
Backend-side receive/relay + admin visibility toggle land in a follow-up commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tKdPZt78zbPUZMXWzKEKt
2026-08-09 19:38:16 +02:00
octoturge 52583f2056 Add periodic reconciliation sweep (Phase 7)
Adds a rotating, non-overlapping-batch ReconciliationScheduler (common/,
loader-agnostic) and wires it into the 1.12.2 leaf: a new tick timer
(reconciliationIntervalTicks, mirroring the existing delta-flush timer)
periodically re-reads and resends a bounded slice of currently-loaded
chunks, catching mutations that never fire a block event (world-gen,
other mods writing blocks directly, /fill, etc.).

ChunkAdapter gains loadedChunkKeys(); Forge1122ChunkAdapter implements it
via ChunkProviderServer.getLoadedChunks(). MapperConfig gains
reconciliationChunksPerSweep to bound sweep cost.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tKdPZt78zbPUZMXWzKEKt
2026-08-09 19:30:32 +02:00
octoturge 41034356ac Phase 4: receive waypoint shares and render JourneyMap/Xaero chat links
DefaultBackendConnection handles the new waypoint_share message and
dispatches it through a WaypointShareListener. WaypointChatFormatter
builds the actual chat text for both formats from publicly documented
wire formats (see THIRD_PARTY_NOTICES.md for sources/attribution) —
neither needs a click-event component, since both client mods
auto-detect the right plain-text shape. Forge1122ChatBridge wires this
into a real broadcast; MCMapperMod registers the listener.

Built test-first per the project's TDD workflow; verified live against
the real backend, including a byte-exact JourneyMap-format chat
message produced from a real WS payload.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tKdPZt78zbPUZMXWzKEKt
2026-08-08 19:21:30 +02:00
octoturge 73e10d34e5 Phase 3: /mcmapper link command and two-way chat bridge
Test-first from here on (per request after Phase 2's backend commit):
LinkCodeGenerator has a standalone test (common/src/test) written and
confirmed failing before the implementation existed.

common: LinkCodeGenerator produces a 6-character code from an
unambiguous charset (excludes 0/O/1/I/L — it gets read off a chat line
and typed back). BackendConnection grows sendLinkRequest (now actually
implemented, was a Phase 1 stub), sendChatMessage, and setChatListener/
ChatListener for inbound web->game chat, all wired into
DefaultBackendConnection's existing JSON wire protocol.

forge-1_12_2: LinkCommand (/mcmapper link) generates a code, resolves
online/offline from the server's actual auth mode
(MinecraftServer#isServerInOnlineMode), and shows it to the player.
Forge1122ChatBridge implements the inbound half (injects web chat into
real in-game chat via the player list) — injectWaypointShare is a
documented Phase 4 stub, same pattern as sendLinkRequest was in Phase 1.
MCMapperMod hooks ServerChatEvent to forward in-game chat out and wires
the chat listener to the bridge.

Verified end-to-end against a live MCMapper-Backend instance through the
real Java client (not a stand-in): a mod-generated link code correctly
redeems via the backend's HTTP endpoint to an account with the mod-
supplied username, and a browser chat message correctly round-trips all
the way to the mod's live ChatListener callback.
2026-08-08 17:09:09 +02:00
octoturge ab94cca1b2 Document common test runner as 'bash run-tests.sh' (executable bit doesn't survive this filesystem's git add) 2026-08-08 16:40:33 +02:00
octoturge 53cba762b4 Add standalone test coverage for common/'s MiniJson
Requested after MCMapper-Backend's Phase 2: development follows TDD
(test-first) from here on — this retrofits the piece already built
(MiniJson, the hand-rolled JSON codec) before that request landed.

No JUnit/Gradle involved, matching common/'s "must compile standalone
under plain javac" constraint (see README.md) — a hand-rolled assertion
runner in src/test/java, compiled and run via the new run-tests.sh.
Covers write escaping, nested object/array encoding, parsing (including
the exact columns-message shape DefaultBackendConnection actually
builds), and a write-then-parse round-trip.

Deliberately not wired into either leaf's build.gradle sourceSets (which
only pull in src/main/java) — verified both forge-1_12_2 and forge-1_7_10
still compile with src/test/ present, confirming test code doesn't leak
into the shipped mod jar.
2026-08-08 16:39:56 +02:00
octoturge 2b7e0e090d Phase 2: forge-1_12_2 full chunk section backfill/flush
Extends ChunkAdapter with readSections() (reads non-empty 16x16x16
sections via the chunk's ExtendedBlockStorage array, skipping fully-air
ones for free) and DeltaSink with onChunkDirty(), additive to Phase 1's
column-based readChunk()/onDelta — 2D column tracking is unchanged.
BackendConnection grows sendSections(), base64-encoding each section's
4096-entry char[] (char, not short, since short would overflow for any
blockId >= 2048 — see SectionData's javadoc) into the wire protocol's new
"sections" message, mirrored in MCMapper-Backend's matching commit.

MCMapperMod now tracks dirty chunks (not just dirty columns) and, on the
same flush tick as column deltas, re-reads and resends full section data
for any chunk touched since the last flush — same "current state, not a
diff" approach as columns, at chunk instead of column granularity.

Verified against a live MCMapper-Backend instance: a known half-solid
section sent through this exact code path round-trips to a mesh with
exactly 24 vertices / 36 indices at the backend's mesh-serving endpoint,
matching the worker's greedy-mesher unit tests for a uniform section.
2026-08-08 16:19:32 +02:00
octoturge 699f09f081 Phase 1: forge-1_12_2 WS connection and column delta capture
Adds a hand-rolled RFC 6455 WS client and minimal JSON codec to common/ (no
third-party deps, keeping legacy ForgeGradle's classpath untouched), a
DefaultBackendConnection implementing hello/hello_ack auth and reconnect
with backoff, and Forge1122ChunkAdapter deriving top-of-column state from
the vanilla heightmap for both initial chunk backfill and event-driven
deltas (block break/place, deferred one tick to read post-mutation state).
MCMapperMod wires it up with config-driven backendUrl/serverToken and a
tick-based flush batch. Verified against a live MCMapper-Backend api
instance (real WS handshake, hello_ack, and 256-column batch landing
correctly in Postgres).
2026-08-08 15:35:51 +02:00
octoturge 621ea7c10d Phase 0: scaffold Gradle multi-project mod skeleton
common/ (shared source) plus three leaf modules: forge-1_12_2 (primary,
Enigmatica 2 target) and forge-1_7_10 both build via anatawa12's
Gradle-7-compatible ForgeGradle 1.2/2.3 forks (verified: both leaves build
clean on Gradle 7.6 / JDK 8). neoforge-26_1 is structurally scaffolded but
excluded from the default build pending its own toolchain in Phase 10.
2026-08-08 14:10:01 +02:00