Skip to content

feat: render server-generated terrain - #6

Closed
JJiang28 wants to merge 1 commit into
mainfrom
feat/terrain-rendering
Closed

feat: render server-generated terrain#6
JJiang28 wants to merge 1 commit into
mainfrom
feat/terrain-rendering

Conversation

@JJiang28

@JJiang28 JJiang28 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fetches the terrain planes once from the new MapService.GetTerrain and renders ground, relief, vegetation, rivers and special resources as separate composited sprites. Previously every visible tile drew one grass texture and everything else drew fog.
  • Splits tiles.ts, which baked each building into its own opaque tile texture and so could only ever draw a farm on grass — on desert it carried a patch of grassland with it. Buildings are now transparent sprites over whatever ground they occupy.
  • Corrects the gameConfig defaults, which claimed a 128-wide map and a vision radius of 5 against a server serving 75 and 3.

Notes for reviewers

All layers share one texture geometry and therefore one anchor, deliberately. Per-layer heights would save ~0.4 MB, but each then needs its own anchor derived from its own CY / TEX_H; get one wrong and that layer sits ~26 px off while still looking almost right. Worse, hit-testing is pure math via pixelToHex and stays correct only while a sprite sits at exactly hexToPixel(col, row) — so "fixing" such an offset by nudging sprite.y would silently desync clicks from what is drawn. One geometry makes that unrepresentable.

Terrain outside vision is desaturated rather than blacked out. Nothing leaks: the server already filters entities by vision, so a fogged tile has no city or building to reveal.

getVisDist stays on Chebyshev distance on purpose — it mirrors domain.PointVisible server-side, and switching the client to true hex distance would make the fog ring disagree with which entities actually arrive.

Depends on city-io/backend#13, which adds GetTerrain. Merge that and deploy it first, or this build has no terrain to fetch.

proto/ here mirrors only the terrain additions. The army protos from backend #12 are not yet mirrored into this repo; that sync belongs with the armies frontend work rather than being smuggled in here.

Known follow-up

The renderer still tears down and rebuilds every visible tile on each ~3 s stream tick, and never frees tiles panned past. That predates this change, but terrain multiplies per-tile sprites, so the cost is now several times higher. A renderer restructure (pooling, real culling, diff updates, texture atlas) is the intended next change.

Test plan

  • yarn check and yarn build clean
  • yarn generate reproduces the committed generated code exactly
  • Verified against a local backend: login, terrain renders under real city and building data, buildings composite onto their actual ground
  • Fog desaturates terrain rather than hiding it; entities outside vision remain absent
  • Construction overlay, territory borders, starvation border and tile selection all still render
  • No console errors on load

The map drew one grass texture for every visible tile and fog for the
rest, because the server had no terrain to describe. It does now, so
fetch the planes once via MapService.GetTerrain and render them.

Split tiles.ts, which baked each building into its own opaque tile
texture and so could only ever draw a farm on grass — on desert it
carried a patch of grassland with it. Ground, relief, vegetation,
rivers, resources and buildings are now separate sprites composited
per tile, so a structure sits on whatever ground it occupies.

Terrain outside vision is desaturated rather than blacked out. Nothing
leaks: the server filters entities by vision, so a fogged tile has no
city or building to reveal, and a coastline is not worth hiding.

All layers deliberately share one texture geometry and therefore one
anchor. Per-layer heights would save about 0.4MB but each needs its
own anchor derived from its own CY/TEX_H; get one wrong and that layer
sits ~26px off while still looking almost right. Worse, hit-testing is
pure math via pixelToHex and stays correct only while a sprite sits at
exactly hexToPixel(col, row), so "fixing" such an offset by nudging
sprite.y would silently desync clicks from what is drawn.

Enums come from the generated proto rather than a hand-written copy,
so the byte values packed into each plane have one definition.

Also corrects the gameConfig defaults, which claimed a 128-wide map
and a vision radius of 5 against a server serving 75 and 3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JJiang28 JJiang28 closed this Aug 5, 2026
@prayujt
prayujt deleted the feat/terrain-rendering branch August 5, 2026 02:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant