You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Generate world and region imagery through the shared renderer #13
This preserved product issue is now part of the fresh MIT replacement program. Final implementation owner: atrinik/renderer. Legacy C/SDL2, packet, global-state, and file-path details below are historical evidence only.
Replacement implementation contract
Keep this as the imagery delivery epic. Use deterministic complete scene bundles and the exact shared wgpu render path for client, editor, and atrinik-render; children own scene contexts, projections/products, CI packaging, and release evidence.
New implementation and tests are independent MIT work unless an exact contribution by an approved MIT provenance grantor is admitted through the recorded file-level MIT grant. Preserve every player-facing, accessibility, disclosure, and performance design decision below.
Required verification
Test pure state/geometry/material/UI behavior headlessly where possible and run supported Linux/Windows integration paths.
Add bounded malformed, stale, lifecycle, resource-loss, and recovery cases appropriate to the owner.
Add shared Go/Rust protocol fixtures for authoritative fields; presentation never reconstructs hidden rules.
Use released shared renderer/protocol/toolkit contracts and wrapper-managed replacement scenarios.
Preserved product/design specification and historical implementation notes
Summary
Add a deterministic offline world-image generator that executes the same compiled map renderer, sprite/effect code, lighting code, and asset lookup used by the legacy client. It should produce an unstyled, accurate isometric source render plus optional derived products suitable for minimaps, region maps, documentation, editor previews, and other tooling.
This is narrower than the native editor proposed in atrinik/editor#13: it establishes the reusable scene-capture, offscreen-render, projection-metadata, and export pipeline that the editor can later consume.
What exists today
The current atrinik-server --worldmaker pipeline is useful for navigation, but it is intentionally not a replica of the game view:
server/src/modules/world_maker.c defines each world tile as a 3x3 pixel box.
It decodes each face with libgd and reduces the image to an average RGB color.
render_object() filters the map to selected structural object types rather than drawing every client-visible layer.
It traverses only the four cardinal tiled-map links for image placement.
It records compatible TILED_UP maps in the .def metadata, but the PNG pass renders only each region's base horizontal map.
It has no client isometric projection, sprite alpha/compositing, multipart handling, animation, zoom/rotation/glow, tile stretching, unified cross-level painter order, smooth structural lighting, fog/cutaway behavior, or client effect path.
The exact rendering machinery already exists in the client:
client/src/gui/widgets/map.c:map_draw_map() draws every active physical depth through one global back-to-front painter queue.
client/src/client/sprite.c, tilestretcher.c, and animations.c own sprite transforms and animation behavior.
client/src/client/lighting.c owns ground and structural lighting.
The dynamic minimap already calls map_draw_map() on a separate surface, proving that the world path can render to a target other than the main map widget.
The current region-map consumer cannot simply swap in an isometric PNG. client/src/client/region_map.c and the region-map popup assume a rectangular image where pixel_size converts (map path, x, y) directly to image X/Y. Fog, player markers, panning, hit testing, labels, and tooltips all depend on that contract.
Proposed decision
Build a client-owned atrinik-map-render executable and a server-owned deterministic scene exporter, orchestrated by an updated generate-region-maps.sh (or a more general generate-world-images.sh).
Do not add another renderer to the server, Python tools, or a browser. The connected game and the offline generator must link and call the same renderer modules. A visual rule fixed in the client must therefore affect the next generated image without being reimplemented elsewhere.
The pipeline should be two-stage so the server runtime model and client renderer remain cleanly separated:
authored maps + collected archetypes/faces/animations
|
v
server deterministic scene exporter
(map loading and client-view semantics)
|
versioned scene snapshots
|
v
client-owned offscreen map renderer
(the actual client render path)
|
raw RGBA + projection metadata
|
v
optional trusted export profiles
(masks, grading, outlines, downsampling,
labels, tiles/pyramids, packaging)
Architecture
1. Transport-independent server scene capture
Factor the scene-building portion of server/src/socket/request.c:draw_client_map2() away from socket delivery and incremental per-connection bookkeeping. It should be possible to produce a complete bounded client scene snapshot for a map/camera coordinate with an explicit visibility policy.
The first implementation may serialize length-delimited CLIENT_CMD_MAP payloads and replay them through the normal client command decoder. That is a strong equivalence fixture because the generator then populates the same MapCell representation as a connected client. The durable format should be versioned and snapshot-oriented rather than accidentally treating network delta state as a permanent file format.
The exporter must use normal server map loading and collected data, including archetype inheritance, multipart objects, map-space layer selection, linked physical levels, support height, light samples, and the same face/animation IDs exposed to the client. It must not invent another authored-map parser.
Keep player-dependent choices explicit. At minimum support:
world: omniscient static-world output, no fog, no player avatar/UI, no camera cutaway, no transient living/projectile/damage objects, and all intended structural depths visible;
player-view: the real LOS, fog, cutaway, special-vision, sub-layer, and depth semantics for a supplied map/coordinate, used for equivalence tests and diagnostic screenshots; and
named future profiles that select layers/entities without changing the renderer itself.
The snapshot manifest must record the policy, map/content digests, render clock, lighting/time-of-day inputs, resource catalog identity, and renderer settings. “Accurate” must always be reproducible rather than depending on ambient server time or mutable player state.
2. Reusable client renderer context and headless executable
Extract the map renderer's process globals/widget assumptions behind a context that accepts:
one or more physical-depth scene caches;
viewport/camera origin and output dimensions;
a renderer-owned resource provider;
a fixed render clock and animation policy;
lighting, fog, cutaway, annotation, and UI options; and
an explicit output target.
Both atrinik and atrinik-map-render must call this API. Do not copy map.c into a tool or maintain separate interactive/headless drawing branches.
The offline asset provider should load the collected bmaps, animations, and atrinik.0 catalog directly from isolated generation inputs. It must not connect to a server, depend on an existing user's cache/configuration, or silently omit missing faces. A missing or mismatched asset should fail generation with the face/map context.
For the current software client, the target can be an SDL memory surface. After atrinik/atrinik#128 and #17, the same executable should use an offscreen GPU render target plus readback; renderer ownership and scene APIs introduced there should be designed so an offline target is a normal consumer. Headless mode must fail clearly when no supported render backend is available instead of falling back to a divergent renderer.
3. Deterministic world composition
A complete region is larger than one client viewport. Rendering independent screenshots and placing them edge-to-edge will create seams because tall sprites, smooth lighting, floor masks, roofs, and stacked levels cross chunk boundaries.
Use one world projection and chunk it with guard bands:
derive horizontal map placement from the server's tiled-map graph and include linked physical depths;
project map/tile anchors through the same isometric transform used by the client;
compute conservative projected bounds from actual render commands/resources, including doubled, rotated, zoomed, stretched, multipart, tall, and effect sprites;
render each chunk with the required neighboring scene and light samples;
crop only the guard band after rendering; and
verify that the stitched chunks match an equivalent monolithic render on representative fixtures.
Do not composite complete physical levels or complete maps independently. Commands from adjacent maps and depths must enter the same painter order wherever their projected bounds overlap.
Freeze the render clock by default. Animated faces and glow/effect parameters should select a documented frame/time. Optional frame sequences or animated exports can be added later, but a normal build must produce byte-stable output for the same renderer/backend and inputs.
4. Replace scalar region-map coordinates with projection metadata
Emit a versioned manifest alongside the raw image or image tiles. It should contain:
image/tile-pyramid dimensions and content hashes;
the world-to-image projection basis and origin;
every map path/depth and its world placement;
a mapping from (map path, x, y, depth) to an image-space anchor or diamond/polygon;
labels, tooltips, hidden regions, and region membership currently emitted in .def files;
transparent/void coverage; and
the exact snapshot/render/export profile identities.
Update region-map fog, player markers, click-to-coordinate inspection, labels, and tooltip hit testing to use this transform. Fog should remain semantic map-tile state, then rasterize through the manifest's projected tile footprint; it should not become a fragile image-pixel history.
The greenfield posture allows replacing the current .def/single-PNG contract and updating all current consumers together. Do not retain both coordinate models solely for hypothetical old clients.
Large worlds should support chunked images and a zoom pyramid so the client does not decode one unbounded PNG. Small regions may still be packaged as one image when the same manifest contract is used.
5. Raw source render, auxiliary masks, and derived products
Always retain a lossless raw RGBA render before stylization. Export profiles may then derive purpose-specific assets without re-reading or reinterpreting map objects.
Where selective effects need semantic information, allow the same ordered render commands to produce auxiliary targets such as tile/world coordinates, structural-versus-transient coverage, depth, object class/layer, or emissive/light masks. These are alternate renderer materials/outputs, not a second geometry or painter implementation.
Example trusted profiles:
region: static structural scene, full region atlas/pyramid, labels and tooltips, fog/marker metadata;
minimap: static structural scene, reduced resolution, stronger silhouettes/contrast, optional color grading and outlines;
documentation: raw high-resolution render with transparent background and no navigation overlays; and
player-view: viewport-sized exact diagnostic output with the requested client settings.
Initially, deterministic CPU post-processing after the raw render is acceptable for simple resize, palette, contrast, or outline steps. Once #17/#131 land, reusable packaged material/postprocess stages should use that trusted renderer-owned effect system. Never accept server-supplied shader source, bytecode, arbitrary filesystem paths, or backend-specific resources.
6. Isolated generation and packaging
Preserve the useful behavior of the existing workflows while tightening isolation:
collect resources into a build/staging directory;
never create or modify source-tree server/data as a side effect;
build/run the native host exporter and renderer even when producing a MinGW server package;
cache output by authored content, collected catalogs, renderer code/version, profile, and relevant settings;
write to a temporary output directory and atomically replace only a complete successful generation; and
package the manifest, raw/derived images, and hashes through the server's existing authenticated asset-source path.
Phased delivery
Phase 0: equivalence proof
Capture a bounded complete map snapshot using the server's current client-map semantics.
Add a minimal offscreen client target that replays it through the normal client decoder and calls the existing map_draw_map().
Demonstrate pixel-identical output to /screenshot map under the software renderer with identical assets, clock, viewport, and settings.
Add fixtures for ordinary terrain, tall/multipart sprites, tile stretching, animation, smooth/discrete lighting, fog/cutaway, and depths +1/+2.
Phase 1: reusable contexts and deterministic profiles
Separate server scene capture from socket/delta ownership.
Separate client scene rendering from widgets/process globals.
The connected client and offline generator compile and call the same map rendering, sprite transform, lighting, animation, and painter-order modules; repository search finds no second map renderer in the new pipeline.
A captured player-view fixture matches the interactive client screenshot exactly under the current software backend. After GPU migration, comparison uses a documented small per-pixel tolerance across supported backends while painter/coverage masks remain exact.
Raw output correctly covers ordinary floors/masks/walls, tall and multipart objects, alpha, zoom/rotation/doubling, tile stretching/elevation, animation at a fixed clock, glow, smooth structural lighting, fog/cutaways, and at least depths +1 and +2.
Adjacent map and chunk boundaries have no missing/duplicated sprites, lighting discontinuities, clipping, or painter-order seams; chunked output matches a monolithic fixture.
The same inputs, profile, backend, and renderer version produce deterministic manifests and image hashes on repeated runs.
Region-map fog, player marker placement, coordinate inspection, labels, tooltips, and zoom/pan remain correct on the isometric output.
Missing assets, malformed snapshots, unsupported render backends, inconsistent topology, and partial output fail with actionable diagnostics and do not publish a mixed generation.
Generation uses isolated build/runtime directories, does not require a live network service or user cache, and never mutates authored maps, collected source aggregates, or source-tree runtime state.
Linux generation and native-host generation for Windows packaging pass with warnings as errors; representative visual fixtures run in CI on a supported deterministic backend.
Non-goals
Reimplementing the client renderer in libgd, Python, PHP, Canvas, or the next/ client.
Capturing mutable production server state or player-private information into packaged maps.
Making one ambiguous image that mixes static world truth with unspecified LOS, roof cutaway, animation, weather, or time-of-day state.
Replacing the authored map format or the server's authoritative runtime loader.
The Phase 0 packet-replay proof can start on the current software renderer; the durable extraction and GPU implementation should be coordinated with those issues rather than building a temporary parallel renderer.
Child issues
These GitHub sub-issues are the executable delivery units tracked by atrinik/atrinik#168. Each belongs to one roadmap milestone; this parent remains open until all are complete.
Important
This preserved product issue is now part of the fresh MIT replacement program. Final implementation owner:
atrinik/renderer. Legacy C/SDL2, packet, global-state, and file-path details below are historical evidence only.Replacement implementation contract
Keep this as the imagery delivery epic. Use deterministic complete scene bundles and the exact shared wgpu render path for client, editor, and
atrinik-render; children own scene contexts, projections/products, CI packaging, and release evidence.New implementation and tests are independent MIT work unless an exact contribution by an approved MIT provenance grantor is admitted through the recorded file-level MIT grant. Preserve every player-facing, accessibility, disclosure, and performance design decision below.
Required verification
Preserved product/design specification and historical implementation notes
Summary
Add a deterministic offline world-image generator that executes the same compiled map renderer, sprite/effect code, lighting code, and asset lookup used by the legacy client. It should produce an unstyled, accurate isometric source render plus optional derived products suitable for minimaps, region maps, documentation, editor previews, and other tooling.
This is narrower than the native editor proposed in atrinik/editor#13: it establishes the reusable scene-capture, offscreen-render, projection-metadata, and export pipeline that the editor can later consume.
What exists today
The current
atrinik-server --worldmakerpipeline is useful for navigation, but it is intentionally not a replica of the game view:server/src/modules/world_maker.cdefines each world tile as a 3x3 pixel box.render_object()filters the map to selected structural object types rather than drawing every client-visible layer.TILED_UPmaps in the.defmetadata, but the PNG pass renders only each region's base horizontal map.The exact rendering machinery already exists in the client:
client/src/gui/widgets/map.c:map_draw_map()draws every active physical depth through one global back-to-front painter queue.client/src/client/sprite.c,tilestretcher.c, andanimations.cown sprite transforms and animation behavior.client/src/client/lighting.cowns ground and structural lighting.map_draw_map()on a separate surface, proving that the world path can render to a target other than the main map widget.The current region-map consumer cannot simply swap in an isometric PNG.
client/src/client/region_map.cand the region-map popup assume a rectangular image wherepixel_sizeconverts(map path, x, y)directly to image X/Y. Fog, player markers, panning, hit testing, labels, and tooltips all depend on that contract.Proposed decision
Build a client-owned
atrinik-map-renderexecutable and a server-owned deterministic scene exporter, orchestrated by an updatedgenerate-region-maps.sh(or a more generalgenerate-world-images.sh).Do not add another renderer to the server, Python tools, or a browser. The connected game and the offline generator must link and call the same renderer modules. A visual rule fixed in the client must therefore affect the next generated image without being reimplemented elsewhere.
The pipeline should be two-stage so the server runtime model and client renderer remain cleanly separated:
Architecture
1. Transport-independent server scene capture
Factor the scene-building portion of
server/src/socket/request.c:draw_client_map2()away from socket delivery and incremental per-connection bookkeeping. It should be possible to produce a complete bounded client scene snapshot for a map/camera coordinate with an explicit visibility policy.The first implementation may serialize length-delimited
CLIENT_CMD_MAPpayloads and replay them through the normal client command decoder. That is a strong equivalence fixture because the generator then populates the sameMapCellrepresentation as a connected client. The durable format should be versioned and snapshot-oriented rather than accidentally treating network delta state as a permanent file format.The exporter must use normal server map loading and collected data, including archetype inheritance, multipart objects, map-space layer selection, linked physical levels, support height, light samples, and the same face/animation IDs exposed to the client. It must not invent another authored-map parser.
Keep player-dependent choices explicit. At minimum support:
world: omniscient static-world output, no fog, no player avatar/UI, no camera cutaway, no transient living/projectile/damage objects, and all intended structural depths visible;player-view: the real LOS, fog, cutaway, special-vision, sub-layer, and depth semantics for a supplied map/coordinate, used for equivalence tests and diagnostic screenshots; andThe snapshot manifest must record the policy, map/content digests, render clock, lighting/time-of-day inputs, resource catalog identity, and renderer settings. “Accurate” must always be reproducible rather than depending on ambient server time or mutable player state.
2. Reusable client renderer context and headless executable
Extract the map renderer's process globals/widget assumptions behind a context that accepts:
Both
atrinikandatrinik-map-rendermust call this API. Do not copymap.cinto a tool or maintain separate interactive/headless drawing branches.The offline asset provider should load the collected
bmaps,animations, andatrinik.0catalog directly from isolated generation inputs. It must not connect to a server, depend on an existing user's cache/configuration, or silently omit missing faces. A missing or mismatched asset should fail generation with the face/map context.For the current software client, the target can be an SDL memory surface. After atrinik/atrinik#128 and #17, the same executable should use an offscreen GPU render target plus readback; renderer ownership and scene APIs introduced there should be designed so an offline target is a normal consumer. Headless mode must fail clearly when no supported render backend is available instead of falling back to a divergent renderer.
3. Deterministic world composition
A complete region is larger than one client viewport. Rendering independent screenshots and placing them edge-to-edge will create seams because tall sprites, smooth lighting, floor masks, roofs, and stacked levels cross chunk boundaries.
Use one world projection and chunk it with guard bands:
Do not composite complete physical levels or complete maps independently. Commands from adjacent maps and depths must enter the same painter order wherever their projected bounds overlap.
Freeze the render clock by default. Animated faces and glow/effect parameters should select a documented frame/time. Optional frame sequences or animated exports can be added later, but a normal build must produce byte-stable output for the same renderer/backend and inputs.
4. Replace scalar region-map coordinates with projection metadata
Emit a versioned manifest alongside the raw image or image tiles. It should contain:
(map path, x, y, depth)to an image-space anchor or diamond/polygon;.deffiles;Update region-map fog, player markers, click-to-coordinate inspection, labels, and tooltip hit testing to use this transform. Fog should remain semantic map-tile state, then rasterize through the manifest's projected tile footprint; it should not become a fragile image-pixel history.
The greenfield posture allows replacing the current
.def/single-PNG contract and updating all current consumers together. Do not retain both coordinate models solely for hypothetical old clients.Large worlds should support chunked images and a zoom pyramid so the client does not decode one unbounded PNG. Small regions may still be packaged as one image when the same manifest contract is used.
5. Raw source render, auxiliary masks, and derived products
Always retain a lossless raw RGBA render before stylization. Export profiles may then derive purpose-specific assets without re-reading or reinterpreting map objects.
Where selective effects need semantic information, allow the same ordered render commands to produce auxiliary targets such as tile/world coordinates, structural-versus-transient coverage, depth, object class/layer, or emissive/light masks. These are alternate renderer materials/outputs, not a second geometry or painter implementation.
Example trusted profiles:
region: static structural scene, full region atlas/pyramid, labels and tooltips, fog/marker metadata;minimap: static structural scene, reduced resolution, stronger silhouettes/contrast, optional color grading and outlines;documentation: raw high-resolution render with transparent background and no navigation overlays; andplayer-view: viewport-sized exact diagnostic output with the requested client settings.Initially, deterministic CPU post-processing after the raw render is acceptable for simple resize, palette, contrast, or outline steps. Once #17/#131 land, reusable packaged material/postprocess stages should use that trusted renderer-owned effect system. Never accept server-supplied shader source, bytecode, arbitrary filesystem paths, or backend-specific resources.
6. Isolated generation and packaging
Preserve the useful behavior of the existing workflows while tightening isolation:
server/dataas a side effect;Phased delivery
Phase 0: equivalence proof
map_draw_map()./screenshot mapunder the software renderer with identical assets, clock, viewport, and settings.Phase 1: reusable contexts and deterministic profiles
Phase 2: world projection and chunk export
Phase 3: client consumption and products
Phase 4: workflow integration
INSTALL, component guidance, and architecture docs.Acceptance criteria
player-viewfixture matches the interactive client screenshot exactly under the current software backend. After GPU migration, comparison uses a documented small per-pixel tolerance across supported backends while painter/coverage masks remain exact.Non-goals
next/client.Relationships
The Phase 0 packet-replay proof can start on the current software renderer; the durable extraction and GPU implementation should be coordinated with those issues rather than building a temporary parallel renderer.
Child issues
These GitHub sub-issues are the executable delivery units tracked by atrinik/atrinik#168. Each belongs to one roadmap milestone; this parent remains open until all are complete.
M1
M2
M3
M4