TurfSynth AR (My Block Warfare)
Your neighborhood procedurally generates the game
The Problem: Geography as Decoration
Location-based games have a geography problem. They overlay generic game mechanics onto real-world maps — random spawn points, uniform resource distribution, identical gameplay from Brooklyn to Boise — treating the physical world as a coordinate grid rather than a designed space with meaning.[1] Schell's lens of "venue" asks what properties the play space has and how they affect gameplay, yet most location-based games answer this question with indifference: the space is GPS coordinates, nothing more. TurfSynth AR inverts this relationship. Instead of spawning content at locations, it reads the built environment — street networks, building density, land use classifications, terrain elevation — and procedurally generates game mechanics that are structurally different depending on where you stand.[2] Bogost's concept of "procedural rhetoric" — the practice of using computational processes to make arguments — applies directly: TurfSynth doesn't just use geography as a backdrop, it makes an argument about how physical space structures human experience, expressed through gameplay systems that respond to the actual character of each neighborhood.
Technical Architecture: TypeScript Monorepo
TurfSynth is structured as a TypeScript monorepo managed with pnpm workspaces, targeting Node.js 20+ with strict TypeScript compilation. The architecture separates concerns into five packages: @turfsynth/geo-core for geospatial primitives and data ingestion, @turfsynth/procgen for procedural generation logic, @turfsynth/territory for the zone control system, @turfsynth/ar-layer for device-level rendering, and @turfsynth/api for the coordination server.[3] The package boundaries follow the Facade pattern from Gamma et al.: each package exposes a clean public API while hiding the complexity of its internal data structures. The geospatial core, for instance, wraps Turf.js operations in typed abstractions that enforce coordinate system consistency (WGS84 throughout) and prevent the class of bugs that arise from mixing projected and unprojected coordinates.[4] Fowler's principle of "separated interface" — defining an abstraction in one package and its implementation in another — governs how the procedural generation engine consumes geographic data without coupling to specific data sources. Swap OpenStreetMap for a proprietary dataset, and the procgen package never knows.
import type { GeoFeature, BBox } from '@turfsynth/geo-core';
import { classifyLandUse, calculateDensity } from '@turfsynth/geo-core';
/** Game mechanic types derived from real geography */
export type GameMechanicType =
| 'resource_zone'
| 'trade_hub'
| 'movement_corridor'
| 'strategic_objective'
| 'fortification_point'
| 'contested_ground';
export interface GameMechanic {
readonly type: GameMechanicType;
readonly origin: GeoFeature;
readonly intensity: number; // 0-1, derived from density
readonly radius: number; // meters, derived from feature area
readonly modifiers: ReadonlyMap<string, number>;
}
/** Feature-to-mechanic mapping rules */
const MAPPING_RULES: Record<string, GameMechanicType> = {
park: 'resource_zone',
garden: 'resource_zone',
commercial: 'trade_hub',
retail: 'trade_hub',
highway: 'movement_corridor',
railway: 'movement_corridor',
government: 'strategic_objective',
religious: 'strategic_objective',
industrial: 'fortification_point',
military: 'fortification_point',
} as const;
export function mapFeatureToMechanic(
feature: GeoFeature,
bbox: BBox,
): GameMechanic | null {
const landUse = classifyLandUse(feature);
const mechanicType = MAPPING_RULES[landUse];
if (!mechanicType) return null;
const density = calculateDensity(feature, bbox);
const area = feature.properties.area ?? 0;
return {
type: mechanicType,
origin: feature,
intensity: Math.min(density / 100, 1),
radius: Math.sqrt(area / Math.PI),
modifiers: new Map([
['elevation_bonus', feature.properties.elevation ?? 0],
['access_score', feature.properties.connectivity ?? 0.5],
]),
};
}
export function generateMechanicsForRegion(
features: readonly GeoFeature[],
bbox: BBox,
): GameMechanic[] {
return features
.map((f) => mapFeatureToMechanic(f, bbox))
.filter((m): m is GameMechanic => m !== null);
} Procedural Generation: Geography as Level Designer
The procedural generation engine is the system's core intellectual contribution. Rather than using random seeds or designer-placed content, TurfSynth derives game mechanics deterministically from geographic features. A park becomes a resource zone not because a designer placed one there but because the OpenStreetMap leisure=park tag triggers a classification rule that maps green space to resource production.[5] Shaker, Togelius, and Nelson taxonomize procedural generation approaches along axes of controllability, expressivity, and reliability — TurfSynth occupies an unusual position: highly controllable (deterministic rules from structured data), highly expressive (every neighborhood produces a unique game landscape), and highly reliable (the same geographic input always produces the same game output). This determinism is a deliberate choice. Two players standing in the same park should see the same resource zone, not different procedurally-random outcomes.[6] Togelius et al. distinguish "generate and test" from "constructive" procedural generation — TurfSynth is purely constructive, assembling game content through rule application without search or optimization, because the geographic data already encodes the design constraints that a search algorithm would need to discover.
| Geographic Feature | OSM Tags | Game Mechanic | Effect | Intensity Driver |
|---|---|---|---|---|
| Parks & Gardens | leisure=park | Resource Zone | Passive resource generation | Area (m²) |
| Commercial Strips | landuse=commercial | Trade Hub | Player-to-player exchange | Building density |
| Major Roads | highway=primary | Movement Corridor | Fast travel between zones | Road connectivity |
| Government Buildings | office=government | Strategic Objective | High-value capture target | Building prominence |
| Industrial Areas | landuse=industrial | Fortification Point | Defensive advantage | Perimeter ratio |
| Residential Blocks | landuse=residential | Contested Ground | Default claimable territory | Population density |
Territory Control: Claiming Real Boundaries
The territory system is where geographic fidelity produces its most distinctive gameplay. Players don't claim abstract hexagons on a grid — they claim zones bounded by real streets, real park edges, real property lines. A city block in Manhattan is a different shape, size, and strategic proposition than a suburban lot in Phoenix, and the game reflects this.[7] Salen and Zimmerman define a "game space" as a space of possibility structured by rules — in TurfSynth, the possibility space is literally the built environment, and the rules emerge from its physical constraints. Territory transitions follow a four-state lifecycle: unclaimed zones can be contested by any player who physically enters them, contested zones shift to controlled after a timed presence threshold, and controlled zones can be fortified through resource investment, granting defensive bonuses proportional to the zone's geographic defensibility (dead-end streets are easier to hold than intersections).[8] Huizinga's concept of the "magic circle" — the boundary between play and ordinary life — becomes paradoxically both more and less defined: the game is played in real physical space (dissolving the boundary) but only within zones whose boundaries are computationally enforced (reinforcing it).
Spatial Indexing and Performance
A location-based game that lags when you cross the street is unplayable. TurfSynth uses an R-tree spatial index to maintain sub-millisecond lookup times for "what game mechanics exist within 500 meters of the player's current position" — the fundamental query that fires on every GPS update. The geospatial core pre-computes bounding box hierarchies for all classified features within a region and caches them in a tile-based structure aligned to Web Mercator zoom level 16 (approximately 600m x 600m tiles at the equator).[4] Fowler's "identity map" pattern prevents duplicate feature loading when a player moves between adjacent tiles, and a "lazy load" strategy defers full feature hydration until the player is within rendering distance. The AR layer operates on a reduced feature set — only mechanics within a 100-meter radius are rendered — while the territory system tracks the full tile context for boundary calculations. This two-tier approach means the device does detailed rendering work only for nearby objects while maintaining strategic awareness of the broader zone, keeping frame rates stable even in feature-dense urban environments.[3]
import type { BBox, GeoFeature } from './types';
import type { GameMechanic } from '@turfsynth/procgen';
interface TileKey {
readonly x: number;
readonly y: number;
readonly zoom: 16;
}
interface SpatialTile {
readonly key: TileKey;
readonly features: readonly GeoFeature[];
readonly mechanics: readonly GameMechanic[];
readonly bbox: BBox;
loadedAt: number;
}
const TILE_CACHE = new Map<string, SpatialTile>();
const RENDER_RADIUS = 100; // meters — AR rendering
const STRATEGY_RADIUS = 500; // meters — territory awareness
const TILE_TTL = 5 * 60_000; // 5 minutes
function tileKeyToString(key: TileKey): string {
return `${key.zoom}/${key.x}/${key.y}`;
}
export function queryNearby(
lat: number,
lng: number,
radius: typeof RENDER_RADIUS | typeof STRATEGY_RADIUS,
): readonly GameMechanic[] {
const tiles = getTilesInRadius(lat, lng, radius);
const results: GameMechanic[] = [];
for (const tile of tiles) {
const cached = TILE_CACHE.get(tileKeyToString(tile));
if (!cached || Date.now() - cached.loadedAt > TILE_TTL) {
continue; // tile not loaded yet — async fetch in flight
}
for (const mechanic of cached.mechanics) {
const dist = haversine(lat, lng, mechanic.origin);
if (dist <= radius) {
results.push(mechanic);
}
}
}
return results;
}
export function getARFeatures(lat: number, lng: number) {
return queryNearby(lat, lng, RENDER_RADIUS);
}
export function getStrategyContext(lat: number, lng: number) {
return queryNearby(lat, lng, STRATEGY_RADIUS);
} Design Philosophy: The ORGAN-I Connection
TurfSynth is an ORGAN-III commercial product — pragmatic, shippable, revenue-aware — but its intellectual foundations run through ORGAN-I's theoretical framework. The core insight that geography structures experience rather than merely hosting it connects to Alexander's pattern language, where spatial configurations produce recurring human responses: a park bench facing a path creates a different social affordance than one facing a wall, and a game should recognize this.[9] Alexander's patterns describe "timeless" configurations of built space — arcades, courtyards, alcoves — that produce predictable human effects. TurfSynth's feature-to-mechanic mapping is a computational pattern language: recurring geographic configurations produce recurring gameplay configurations, not because a designer imposed them but because the structure of the built environment makes certain interactions natural. A dead-end street affords defense. A crossroads affords trade. A hilltop affords surveillance.[10] De Certeau distinguishes "strategies" (the imposition of spatial order by institutions) from "tactics" (the improvisational navigation of that order by individuals). TurfSynth respects this distinction: the game mechanics imposed by geographic features are strategies, but how players navigate, combine, and subvert them — claiming a park for its resources while using the adjacent corridor for escape — are tactics that the system enables but does not prescribe.
Gameplay Modes and Player Experience
TurfSynth supports four gameplay modes, each engaging differently with the geographic substrate. Conquest is the primary mode: players compete for territory control, with zone boundaries defined by real streets and defensibility scores derived from actual urban topology. Harvest is cooperative: players work together to maximize resource extraction from green spaces, with yield rates tied to actual park size and vegetation density. Courier treats the road network as a logistics puzzle: deliver resources between trade hubs along movement corridors, with route efficiency scored against the actual street graph. Siege is asymmetric: one player fortifies a zone while others attempt to breach it, with the defender's advantage calculated from real terrain elevation and building density.[7] Each mode uses a different subset of the game mechanic types, ensuring that the same geographic area produces meaningfully different strategic landscapes depending on what you are playing. Salen and Zimmerman's principle that "meaningful play" arises from the relationship between player action and system outcome is the design target: every action in TurfSynth is meaningful because its outcome depends on the specific geographic context in which it occurs.[1]
Tradeoffs and Lessons
The hardest tradeoff is geographic fidelity versus playability. Real-world geography is messy: irregular block shapes, inconsistent OSM tagging quality, areas with no tagged features at all. The classification engine includes fallback rules — when a region lacks tagged features, it infers from surrounding context (an untagged block surrounded by commercial zones is treated as contested ground with trade-hub adjacency bonuses) — but sparse data regions still produce thinner gameplay.[5] Shaker et al. identify "controllability" as a key challenge in procedural generation: the system must produce content that is both diverse and bounded, never generating unplayable configurations. TurfSynth addresses this with a minimum-density threshold — if a region's classified feature count falls below a threshold, the system supplements with procedurally-generated "ambient" mechanics that provide baseline gameplay. The honest admission is that rural areas with sparse OSM coverage will produce less interesting games than dense urban cores, and no amount of algorithmic cleverness fully bridges that gap. The design philosophy accepts this: the game should feel different in different places because those places are genuinely different, and pretending otherwise would undermine the core thesis.[6]
References
- Schell, Jesse. The Art of Game Design: A Book of Lenses. CRC Press, 2008.
- Bogost, Ian. Persuasive Games: The Expressive Power of Videogames. MIT Press, 2007.
- Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.
- Fowler, Martin. Patterns of Enterprise Application Architecture. Addison-Wesley, 2002.
- Shaker, Noor, Julian Togelius, and Mark J. Nelson. Procedural Content Generation in Games. Springer, 2016.
- Togelius, Julian, Georgios N. Yannakakis, Kenneth O. Stanley, and Cameron Browne. Search-Based Procedural Content Generation: A Taxonomy and Survey. IEEE Transactions on Computational Intelligence and AI in Games, 2011.
- Salen, Katie and Eric Zimmerman. Rules of Play: Game Design Fundamentals. MIT Press, 2003.
- Huizinga, Johan. Homo Ludens: A Study of the Play-Element in Culture. Beacon Press, 1938.
- Alexander, Christopher. A Pattern Language: Towns, Buildings, Construction. Oxford University Press, 1977.
- de Certeau, Michel. The Practice of Everyday Life. University of California Press, 1984.