•
27 min read

From Lidar to Pyramid

By: Carlos Granier
Rendered Maya plaza on a forested hilltop: a stepped oval pyramid with a stairway, a long range building, thatched houses, trees closing in behind.
Table of Contents

Case study, 10–11 November 2026. Built with Claude Code on two machines: axon, a Pixelbook running Omarchy, and vortex, a Windows box with an RTX 2080 Super. About eleven hours of wall clock, most of them while I was asleep.


I read an article, followed its footnote, and lost a night to it. This is how Claude and I went from “explain how lidar works” to locating a hidden Maya hilltop in public NASA data and rebuilding it in 3D, and what the whole exercise taught me.

Where we ended up: a Blender orbit around the civic centre, rebuilt on the real 1 m lidar terrain. Pyramid, plaza, range buildings and houses sit on the mound footprints detected in the data. Cycles, GPU, 10 s.

A footnote in someone else’s discovery

On 10 November, Pietro Miotti published “My AI Agents Found an Unmapped Maya Site”. It is a four-day diary: a thought while travelling in Peru, an overnight run of 48 agents looking at NASA G-LiHT lidar strips, a hilltop with a plaza, an oval pyramid and a raised courtyard under unbroken forest, and an official reply from INAH, Mexico’s National Institute of Anthropology and History, saying no registered site existed at that location. He kept the coordinates in a confidential annex and blacked out the state and municipality in the letters.

The article ends with a footnote: if you don’t know how lidar works, paste this into your favourite AI tool: “Build an HTML page that explains how lidar works, with beautiful visualizations.”

So I did, in a Claude Code session on my Pixelbook. Twenty minutes later I had an interactive explainer: a flight animation dropping pulses through a forest, a time-of-flight calculator with G-LiHT’s real altitude and pulse rate, the multiple-returns waveform, a point-cloud slice with vegetation and ground toggles, and a synthetic hilltop you can light, exaggerate and label. That was supposed to be the whole evening.

Then I asked the question that turned it into a project.

”How hard would it be to reverse-engineer the location?”

Claude’s answer was: not very, and that is the uncomfortable part. The author withheld the coordinates, but everything else was public. The dataset is open. The article names the instrument, the year and the region. It publishes a hillshade of the hilltop, a zoom of the civic centre with a scale bar, two terrain cross-sections, a 3D turntable and a satellite slider. A plaza with an oval mound on a cliff edge is a distinctive shape, and cliffs are rare in the lowlands. Confidentiality rested on nobody bothering, not on the information being hidden.

I wanted to know whether it could actually be done. Not to go there, not to tell anyone, just curiosity about how thin the veil really was. Before a single byte was downloaded, Claude set a condition and I agreed to it. It held for the whole project.

The rules. The coordinates go into one local file on one machine. They are never synced, committed, posted, put into a published page, or narrowed down in conversation beyond what the article itself says. Every image and model that leaves the machine is in local metres, with no coordinates, tile names or flight-line identifiers. Mexican archaeological sites are protected by federal law, INAH has the report, and the biggest threat to an unexcavated site is a pin on a public map.

This post follows the same rules. Nothing here narrows the location beyond “a hilltop on a cliff in the Maya region of Mexico, crossed by a 2013 NASA flight line”, which is what the article already says.

Eighty minutes from first download to a match

The plan Claude proposed was a template match: take the civic-centre crop from the article, bring it to the data’s scale, and slide it across every bare-earth terrain tile the campaign produced. Miotti’s agents had looked at pictures; we would let OpenCV do the looking. I created the NASA Earthdata token the article mentions while Claude checked the page for leaks and started the downloads.

01

Leak checkImage metadata, page source, embedded map extents. Nothing. The blacked-out letters were flattened pixels.

02

Fetch tilesBare-earth terrain tarballs for 15 G-LiHT flight sets of the 2013 campaign. Public, no login, about 4 GB at 1.6 MB/s.

03

CalibrateThe 100 m scale bar is 136 px, so the figure is 1.36 px per metre. Crop the label-free box: 168 × 116 m.

04

RenderEach 1 m tile rendered in the article’s own style. This step was wrong twice.

05

MatchNormalised cross-correlation, top five peaks per tile, positions written only to a local CSV. 520 tiles, about 2,600 windows.

06

VerifyHillshade crop and west–east and south–north profiles for the top candidates, compared with the article’s cross-sections.

15

flight sets downloaded, 2013 campaign, southern Mexico

5201 m bare-earth tiles scanned
0.74best match score; runner-up 0.40

80 min

first download to confirmed match, most of it download time

Why the first two passes found nothing

The first run rendered every tile as a conventional hillshade, lit from the north-west, and threw away any window containing no-data pixels. It ranked a flat interpolated strip edge first. Claude went back to the article’s pyramid crop, enlarged it four times and saw what the agents’ renderer had actually done: slope shading, dark on every steep edge regardless of direction, with the strip’s no-data painted white. And that white edge runs right through the civic-centre box, so the “reject any no-data” rule would have discarded the true window even with the right rendering.

Hillshade crop of a flat strip edge, the top false positive from the first pass

First pass, rank 1. A smooth interpolated edge between two strips. Wrong rendering, wrong no-data rule, and a score barely above the noise floor.

Slope-shaded bare-earth crop with plaza, oval pyramid, courtyard and platforms inside a drawn box

Third pass, rank 1. 500 × 400 m of bare earth around the best window, slope-shaded in the article’s style, north up, the 168 × 116 m template box drawn on it. Plaza, oval pyramid, raised courtyard, three platforms, the cliff to the west and the no-data edge clipping the south-west corner, all where the article’s figure puts them.

Two elevation profiles through the match, west to east and south to north

Verification. West–east and south–north terrain profiles through the box centre. The west–east one reproduces the article’s A–A′ cross-section: the cliff, the plaza shelf, the pyramid bump.

When the third pass finished, Claude told me it had found it, gave me the score and the path to the file, and nothing else. I opened the crop next to the article’s figure. Feature for feature.

  • Match the renderer, not the terrain. Template matching compares pictures. Until the tiles were rendered the way the article’s figure was rendered, the true window could not score.
  • Treat no-data as signal. The strip edge is part of the fingerprint. A high-pass filter on both sides made the plaza and pyramid edges drive the score instead of hillside tone.
  • Distrust clever shortcuts. A filter on flight heading, measured from the figure, would have excluded the correct tile: the tile’s principal axis did not represent the local strip direction. A slower rule-based terrain detector was started and abandoned. Brute force over 520 tiles took 15 minutes and was right.
  • No token was needed. The terrain tarballs turned out to be public. I created the Earthdata token and we never used it.

One ribbon of data, 450 m wide

My next question was whether adjacent tiles could be stitched into a larger map. They could, but only lengthwise. The 2013 survey was a forest-carbon sampling mission flown as long transects, never as area coverage. Within 37 km of the site there is exactly one flight line, about 450 m wide, in any Mesoamerican G-LiHT campaign. Everything we built sits on that ribbon, and everything beyond its edges in the renders is invented smooth ground.

Slope-shaded mosaic of the hilltop strip, 1.5 by 1.2 km

Merged tiles, 1 m, slope shading. A 1.5 × 1.2 km window. The article’s figure crops to the civic centre; the strip shows further platforms spreading 300–400 m along the hilltop and a long low ridge or wall line beyond.

Height-coloured mosaic of the same window with a turbo colour ramp

Same window, height colour. Roughly 150 m of drop from the hilltop to the plain on the north-west, which is where the noisy interpolated area at the top begins.

That led to a remark of mine that the find was sheer luck, and to a correction I keep thinking about. Miotti’s 1,200 km² of strips is well under 1% of the Maya lowlands, so for any one hilltop the odds of a transect crossing it are tiny. But the Classic Maya lowlands were one of the most densely settled pre-industrial landscapes anywhere, so a 1,200 km² transect through forest is almost guaranteed to cross hundreds of mounds. The lottery was never whether the laser would find ruins. It was whether the ruins under the beam were already on a map. Hold that thought until the end.

A 3D viewer with the real forest on top

Claude turned the merged terrain into a self-contained Three.js page: a 1 m heightmap packed into the HTML, orbit and pan, slope or height or clay textures, vertical exaggeration, a movable sun, and a canopy layer built from 2 million vegetation returns in the raw point cloud. Switch the canopy on and the ruins vanish under 10–17 m of trees, which is the article’s whole point. The page uses local metres only, so it carries no coordinates.

Clay-shaded 3D oblique view of the hilltop strip

Clay oblique, 1.5× exaggeration. The pyramid and courtyard on the cliff lip.

Same view with green vegetation points covering the terrain

Canopy on. The same view with the 2013 vegetation returns drawn as points.

Clay view along the cliff edge

Along the cliff. The escarpment the plaza sits on.

Painting a photograph onto real relief

The laptop has no image generation, so I offered vortex, my Windows box with an RTX 2080 Super. Claude’s idea was not to ask a model to imagine a Maya site, which produces generic temple art, but to force it to follow the actual terrain. The viewer gained a depth-map output and a normal-map output from any camera, with the strip’s no-data filled by a smooth membrane so there is ground everywhere in frame. That depth map became the control image for Stable Diffusion XL with a depth ControlNet, running in ComfyUI on vortex and driven over an ssh port forward from the laptop. My part was closing a few dozen browser tabs so the GPU had its memory back.

Clay render of the civic centre from a low close camera

Conditioning camera. Clay render of the civic centre from the close view.

Grey depth map of the same camera

Depth map. The same camera as true depth. This is what the ControlNet sees.

AI rendering of an excavated Maya plaza group on a cleared hilltop with forest behind

SDXL + RealVisXL V5 + xinsir depth ControlNet, 1536 × 1024, 47 s on the RTX 2080 Super. Raised plaza, pyramid, courtyard beside it, outlying platforms, all where the lidar puts them. This is the still

I posted on X

and later fed to Grok Imagine.

Second AI rendering, same layout, different seed

Seed 21. Same conditioning, a different seed.

AI rendering of the forested hilltop with a bare rock ring

The “today” prompt. Plausible forest and escarpment, but the model drew a bare rock ring instead of forested mounds. It has no idea the bumps are under trees.

What failed, so nobody repeats it

AI image where the whole hill became a giant thatched roof

“Thatched” in the prompt. The entire hilltop became one roof.

AI image with the whole terrain painted red

“Freshly plastered, inhabited.” Red plaster over the whole terrain, because the smooth filled ground reads as a surface to be painted.

  • Close cameras only. In wide views the civic centre is too small to matter and the model paints a generic landscape.
  • Fill the no-data first. Holes in the depth map become holes in the world.
  • A photoreal fine-tune over the base model, no “thatched”, and the camera above the exaggerated surface. Twice the camera ended up underground because of the 1.5× exaggeration.

A Blender scene from the heightmap

I asked what the plan for Blender would be, and the answer was a good one. AI stills are one-off paintings; nothing in them is a consistent object, so a video from them would flicker. The opposite route is a real scene: coherent, but only as good as what you build. A portable Blender 5.2.4 went onto both machines and one Python script builds the whole thing headless from three rasters in local metres: the heightmap, a canopy height model from the point cloud, and a mask of the mounds inside the civic-centre box. I said go and went to do something else.

  • Terrain mesh at 1 m, 1.8 million faces, with a 30 km skirt so there is a horizon. Procedural rock on slopes, soil, grass on cleared ground, lime plaster on the mounds.
  • Forest instanced by geometry nodes: density and scale from the 2013 canopy heights, so the trees are tall where the lidar says tall, and absent where we choose to clear.
  • Physical sky with the sun in it, distance haze mixed into every material, 1.3× vertical exaggeration.
  • Three modes from one script: today (the hilltop under its real forest), bare (the lidar surface as clay) and recon (terraced plastered mounds, cleared plaza, forest beyond).
Greyscale canopy height model with the strip edge as a diagonal line

Canopy height model. Tallest vegetation return per cell from the raw point cloud. The diagonal line is the strip edge; beyond it the forest is synthetic.

Blender render of the hilltop under forest Blender render of the reconstructed civic centre: plastered mounds and a cleared plaza
⇔
today recon
Compare today with:
Same camera, same terrain, four modes. Cycles on the GPU, 1080p, 128 samples, about a minute per still. Drag to compare.
Top-down render of unbroken forest canopy

Plan view, today. Nothing but canopy. This is what the satellite sees.

Top-down render with a cleared plastered plaza group in the forest

Plan view, recon. The same frame with the civic centre cleared and plastered.

Flight along the strip, today. 10 s, rendered on vortex.

Same flight, reconstruction. Cleared plaza, plastered mounds, forest closing in.

The first flythrough attempts on the laptop went black after a few frames: Eevee under a software OpenGL driver is not a renderer, it is a screenshot tool. Everything that moved was rendered on the GPU box, with the laptop keeping an ssh session open because Windows kills any process started over ssh the moment the session ends, whether through Start-Process or the task scheduler. The one real stall of the night was mine: my 1Password ssh agent sat waiting for an Allow click for about an hour while I was away from the laptop.

Buildings on the footprints

I saw the videos and asked for one with more detailed architecture, something closer to the AI stills. Claude laid out what it would build and I said: go with the pyramids. The recon mode read as white blobs rather than buildings, and the scene already knew where the mounds were, so a detector segmented them: mounds are what stands more than 1.2 m above an opened version of the surface, platforms are broader rises, and the pyramid is the component with a body more than 3 m tall, with an ellipse fitted to it. A second pass split the courtyard mound that the first detection had merged into the pyramid. The plaza floor is the minimum-area rectangle of the flat cells at plaza level, and the terrain is flattened underneath it.

Hillshade of the civic centre with detected structure footprints outlined in red and blue

Detected footprints. Mounds and platforms outlined on the slope map; the pyramid ellipse and the plaza rectangle in blue. Every building below is placed from this.

  • Pyramid: stepped oval, five terraces fitted to the mound body, red base band, stairway facing the plaza, two-room temple with a roof comb on top.
  • Plaza: plastered rectangular slab, stela and altar in front of the stairs.
  • Range buildings with three doorways on footprints over 20 m, including the courtyard the article reads as the ruler’s residence.
  • Pole-and-thatch houses with hipped roofs on the small platforms.
  • Six tree types: three broadleaf, two flat-crowned emergents and a palm, with the forest edge cleared around the group.
Rendered plaza with stepped pyramid, range buildings and thatched houses among trees

Plaza view. Cycles, 1080p, 128 samples. The pyramid’s stairway faces the plaza; the range building on the left sits on the courtyard platform.

Second plaza view from the north-east

From the other side. Stela and altar in front of the stairs.

Plan view of the group as a clearing in the canopy

Plan view. The whole group as a clearing in the canopy, the red base ring at the centre.

Orbit. 240 frames at 1080p, 48 samples, 22–29 s per frame on the RTX 2080 Super. The first attempt descended into the forest wall for its last third and was re-rendered at constant height.

Where it still falls short of the AI stills: surfaces are flat colour with light weathering, no carved detail, no people or smoke, and the trees are stylised. It reads as a museum reconstruction model, not a photograph. The next step up is real tree and texture assets, which is a different project. Or so I thought until the day after it went out.

A photograph of the model

The post went out, I asked Claude for five follow-up projects, and picked the first: make the orbit look like drone footage. The idea is a video diffusion model that repaints a control video instead of inventing one from scratch. We give it the Blender orbit as a depth map, not as colour: every frame is a grayscale image where near is white and far is black, so the model knows exactly where the pyramid, the plaza and the tree line are but is free to decide what they are made of. Coherent geometry from Blender, surfaces from the model. The model is Wan 2.1 VACE, run in the same ComfyUI on vortex that made the stills.

Three frames of the same camera position: the depth map from Blender, the 1.3B model's output, the 14B model's output

The same camera position three ways. Left: the depth map Blender renders for the orbit, the only thing the model sees. Centre: the 1.3B model’s version of it. Right: the 14B model’s. Nothing else changed between the last two.

The pipeline, in the order it settled:

  1. A depth pass in Blender. The scene builder got a depth mode: the camera distance mapped into a grayscale emission shader, 60 m white to 700 m black, rendered with Eevee at 832×480. 81 frames of the orbit take about a minute, against the hours the shaded orbit took.
  2. A keyframe to pin the look. The first depth frame went through the same SDXL and depth-ControlNet recipe as the earlier stills, giving one photographic frame of the plaza. VACE accepts a per-frame mask, so that image is pinned as frame 1 and every other frame is left for the model to generate. The same image goes in as the reference, so the model carries its palette and materials round the orbit.
  3. A prompt that argues with the model. Classic Maya civic centre around AD 700, white lime plaster, red base band, pole-and-thatch houses, late afternoon sun. The negatives matter as much: without them the first runs put up tents, farmhouses and a tiled villa, and the lighting pumped from frame to frame.
  4. The 1.3-billion-parameter model first. It fits the 8 GB card, 81 frames in 21 minutes, and it got geometry, light and forest to hold round the whole orbit. The pyramid, though, slumped into a rubble mound a quarter of the way in, and nothing in the prompt could stop it.
  5. Then the 14-billion-parameter one. It does not fit the card. A 4-bit quantised copy of the weights, 11.6 GB, loads through a community GGUF loader node: about 2.8 GB sits on the GPU and the other 8.4 GB streams in from system RAM every step. A 33-frame test took 30 minutes; the 81-frame orbit took 94. It was worth it.

The 14B pass over the Blender orbit. 81 frames at 832×480 in 94 minutes on the RTX 2080 Super, then upscaled and motion-interpolated to 32 fps. The terraces, the stairway and the roof comb survive the whole circuit; the thatched houses appear on their platforms as the prompt asks. The light drifts toward dusk as it goes round, which is the main thing I would still fix.

Same depth control, same keyframe, same prompt and seed. Left the 1.3B model, right the 14B. Watch the pyramid.

Two things went wrong on the way that are worth writing down. I noticed horizontal stripes running through every test, fixed in place while everything else moved. They turned out to be the depth map: an 8-bit PNG has 256 shades, and a gentle slope rendered into 256 steps becomes a staircase of contour bands. The model took each band edge for a wall and built one. Rendering the depth at 16 bits, blurring it slightly and never dithering it removed every stripe. The second was the prompt: it described a “limestone escarpment beyond” the site, and the big model took it literally, painting a cliff face across the top of the frame wherever the camera tilted up and the sky should have been. Changing one clause to a flat horizon and an evening sky fixed it, and may be why the light now heads for dusk.

An early test frame with horizontal bands of masonry running across the plaza

An early test. The horizontal bands across the plaza are 8-bit depth contours that the model turned into masonry.

One more thing about the small model: pinning both the first and the last frame, which seemed like the obvious way to stop drift, made it give up and copy the depth map straight through as a grey video. One anchor is the limit. The whole recipe, every failed run and the timings are in a README next to the outputs so the next attempt starts from the end of this one.

Installs, disk and time

Nothing was installed into either operating system. Every tool is a portable unpack, a project virtualenv or a mise-managed binary, and deleting two folders removes all of it. I asked for the plan before each install and approved it; Claude did the rest.

MachineWhatWhereSize
axonPython venv: numpy, rasterio, OpenCV, scipy, laspy, Pillowtools/.venvsmall
axonNode 26.13 and uv 0.13 (side effect of a syntax check and the venv)mise storesmall
axonBlender 5.2.4 portable, Linuxtools/blender/1.6 GB
axonG-LiHT tarballs, extracted terrain tiles and point-cloud tiles (deleted after)data/gliht/33 GB
axonMosaics, viewer, renders, scene inputs and outputsdata/mosaic/357 MB
vortexComfyUI portable + SDXL base, RealVisXL V5, xinsir depth ControlNet, VAED:\lidar\≈ 21 GB
vortexBlender 5.2.4 portable, Windows, plus the scene package and rendersD:\lidar\≈ 5 GB
vortexWan 2.1 VACE 1.3B and 14B (4-bit GGUF), umT5 text encoder, Wan VAE, GGUF loader nodeD:\lidar\≈ 23 GB

Scripts, in the order they were written

ScriptDoes
fetch.sh / process.shDownload the public terrain tarballs per flight set, extract, hand tiles to the matcher as they land.
match.pyTemplate from the article figure at 1.36 px/m; render tiles as slope shading with white no-data; high-pass; normalised cross-correlation; peaks to a local CSV. Prints scores only.
verify.pyHillshade crop and west–east / south–north profiles for the top candidates.
heading.py, detector.pyThe strip-heading filter and rule-based terrain detector. Both abandoned; both would have misled.
mosaic.pyMerge every tile within range of the site; slope, hillshade and height renders at 1 m and a 5 m strip overview.
canopy.pyVegetation returns from the compressed point-cloud tiles.
export3d.py + viewer.tpl.htmlSelf-contained Three.js viewer with depth and normal render modes and membrane no-data fill.
comfy_render.pySubmits SDXL + depth-ControlNet jobs to ComfyUI’s API through an ssh tunnel.
blender_inputs.pyHeightmaps, canopy, masks and the structure footprints as PNGs and JSON in local metres.
build_scene.py + arch.pyHeadless Blender scene builder: terrain, forest, sky, four modes, eight cameras, flythroughs and orbit; the Classic-Maya massing. Later: a 16-bit depth pass for the orbit.
comfy_vace.pySubmits Wan VACE video-to-video jobs to ComfyUI: depth control video, frame mask, reference image, prompt; pulls the frames back and assembles the clip.

Timeline, local time

Tuesday 10 November

17:09Lidar explainer page published, twenty minutes after I pasted the footnote.

17:15I ask how hard it would be. Answer: an afternoon for someone who scripts it.

17:32I say let’s do it. Leak check of the article: nothing. Downloads begin, matching starts as each set lands.

18:23First two passes miss. The article’s images are recognised as slope-shaded; renderer rewritten.

18:40Match confirmed, 0.74 against 0.40. Coordinates written to one file, permissions 600.

20:05Mosaic built. Only one strip exists; nearest other flight line 37 km away.

20:21Interactive 3D viewer with the real canopy.

20:28I approve the vortex plan; ComfyUI and 16 GB of models land on the D drive. I close my browsers to free the GPU.

21:12AI renderings done. Vortex idle.

21:21I approve the Blender plan and leave. Scene builder written and iterated on the laptop.

22:08Blocked for an hour on my 1Password ssh agent waiting for an Allow click.

23:55GPU stills and two flythroughs back from vortex.

Wednesday 11 November

00:00”Go with the pyramids.” Architecture mode built: footprints, massing, better trees.

02:37First orbit video reviewed: camera sinks into the forest. Re-rendered at constant height.

04:21Final orbit video copied back. Everything idle. I was asleep.

14:41”Let’s start with 1.” Wan VACE 1.3B lands on vortex; first depth-controlled test.

16:13Keyframe pinned as frame 1. Drift stops; I notice the stripes.

17:10Depth re-rendered at 16 bits. Stripes gone.

18:02Two clean 1.3B orbits. The pyramid still melts.

21:06”Let’s try that 14B version.” 11.6 GB download; I run the loader install myself because the agent’s sandbox refuses to pull third-party code.

21:56First 14B test: pyramid holds, sky is a cliff. Prompt fixed, second test clean.

22:58Full 81-frame 14B orbit queued.

Thursday 12 November

00:32Orbit done, 94 minutes. Upscaled and compared while I slept, again.

Ten things worth keeping

  • Withholding coordinates is not protection when the dataset is public. A distinctive shape in a published figure plus an open archive is a solved problem. Anyone publishing a find should assume the figure is the location.
  • The hard part was looking at the pictures correctly. Three passes, and the only thing that changed between failure and a 0.74 match was rendering the data the way the target image had been rendered.
  • Brute force beat cleverness every time. The heading filter and the terrain-rule detector were both elegant and both wrong. Sliding a template over 520 tiles took 15 minutes.
  • Sampling lidar finds things because the ground is crowded, not because the plane was lucky. The lottery is whether what it finds is already registered.
  • Depth-conditioned image models are a terrain-faithful rendering tool, as long as the camera is close, the depth is hole-free and the prompt avoids words the model over-reads.
  • A real scene beats a pretty painting the moment you need a second frame. Coherence costs modelling time; the AI stills cost 47 seconds each and could never be animated.
  • Portable everything. No system installs on either machine, and the clean-up is two folders.
  • Eight bits is not depth. A video model reads a depth map as geometry, so the quantisation steps of an 8-bit render become walls. Render depth at 16 bits, blur it a little, never dither it.
  • Windows over ssh forgets you the moment you hang up. Keep the session open from the other end; nothing else survived.
  • An agent can run a two-machine pipeline while I sleep, but it needs a stated boundary before it starts. The privacy rule was set once, at the beginning, and never had to be renegotiated. My job was mostly saying “go”, closing browser tabs, and clicking Allow.

What happened when it went out

I posted the AI still of the reconstruction as a reply to the article’s author on X. It reached about 14,000 views, 150 likes and 5 reposts, and brought in more followers in a day than the previous month had. I then gave the same still to Grok Imagine, which produced a fly-by video from the single image that almost looks real.

A community note on the post said the hilltop had been found before. On 11 November, Miotti confirmed it: the site appears in a 2025 paper by Estrada-Belli, Canuto, Šprajc and Fernandez-Diaz, “New regional-scale Classic Maya population density estimates and settlement distribution models through airborne lidar scanning”, on page 14. That is the same paper his article had cited for its population estimates. The site was in the literature all along, just not in INAH’s register, and INAH’s reply was accurate about the register.

I read the paper. It is open access, and it did something the article did not expect: the authors took 500 km² of the same 2013 environmental lidar, reprocessed it for structure visibility, and digitised 16,206 structures in it. Page 14 is their Figure 13, the four largest “minor ceremonial centres” in the whole G-LiHT dataset, with plaza areas between 8,000 and 13,000 m². One of the four panels is our hilltop, with the plaza platform, the structures and the hollow already drawn as a reservoir, and a dot on an inset map of the transects. The agents found the most prominent thing in the data. So had the archaeologists, two years earlier, and they had ranked it.

Remember the lottery. The laser was always going to find ruins; the only question was whether someone had already written them down. Someone had. That changes the article’s headline and none of the method, and it changes nothing about how easy the location was to recover from a public figure. If anything it makes the lesson sharper: the first check after “is this in the register?” should have been “is this in the papers I am citing?”, down to the figures, and that check is one an agent can run before the report goes out. The paper’s figure is not reproduced here for the same reason the article’s are not.

What this post deliberately leaves out. No coordinates, no tile names, no flight-line identifiers, no state or municipality. Every render is in local metres. The location file, the georeferenced mosaic and the results stay on one laptop and nowhere else; the 33 GB of tiles have already been deleted. The article’s own figures are not reproduced here; go read it.

ComfyUI and Blender stay on the GPU box. The scripts stay. The next time someone publishes a hilltop, the pipeline is an afternoon away, and so, apparently, is the paper that already describes it.


Sources. Pietro Miotti, “My AI Agents Found an Unmapped Maya Site”. Lidar data: NASA Goddard’s G-LiHT programme, 2013 campaigns, public bare-earth terrain and point-cloud products. Prior documentation: Estrada-Belli, Canuto, Šprajc and Fernandez-Diaz, Journal of Archaeological Science: Reports 66 (2025) 105288. Rendering: Three.js, ComfyUI with Stable Diffusion XL and a depth ControlNet, Blender 5.2.4 Cycles and Eevee, Wan 2.1 VACE 1.3B and 14B (the latter as a 4-bit GGUF through the ComfyUI-GGUF loader). All images in this post were produced during the project; none carry georeferencing.

New posts by email

New posts and case studies, sent when they're published. Unsubscribe anytime.

About the Author

Carlos Granier is a Tech Founder, CTO, and AI Strategist with 25 years of experience building at the intersection of technology and business. He co-founded Pongalo, one of the first US Hispanic OTT platforms, and built a YouTube MCN to 200M+ monthly views. He now helps founders and executives implement AI as practical infrastructure. Based in Miami, Florida.

Connect on LinkedIn • Book a call

Let's Connect