Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 12 additions & 29 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ scripts/quality-check.sh
```


On Windows, run every `scripts/` command in Git Bash. PowerShell and Command Prompt cannot run bash scripts. See [onboarding.md](docs/onboarding.md) section 1 step 2.

Most formatting check failures can be enforced with:

```bash
Expand All @@ -34,15 +36,11 @@ To run more specific tests, refer to the following commands.
| `scripts/web-check.sh` | The visualizer in `web/`: lint, tests, and build. |


Testing on the Python side is done with pytest, and on the web side with vitest. We use pytest markers to categorize tests into three groups: `quick`, `slow`, and `gpu`.
`slow` and `gpu` tests are explicitly marked using `@pytest.mark.<slow|gpu>`, a test is categorized as `quick` otherwise.
You can use `test-check.sh` to run tests locally. Refer to the table above on how to invoke specific groups.
Testing on the Python side is done with pytest, and on the web side with vitest. We use pytest markers to categorize tests into three groups: `quick`, `slow`, and `gpu`. `slow` and `gpu` tests are explicitly marked using `@pytest.mark.<slow|gpu>`, a test is categorized as `quick` otherwise. You can use `test-check.sh` to run tests locally. Refer to the table above on how to invoke specific groups.

Anything after the group are treated as pytest args, so
`scripts/test-check.sh python -k sampler` passes the `-k sample` argument to pytest.
Anything after the group are treated as pytest args, so `scripts/test-check.sh python -k sampler` passes the `-k sample` argument to pytest.

You can also run pytest directly, as long as you go through uv so that the
project's environment is used:
You can also run pytest directly, as long as you go through uv so that the project's environment is used:

```bash
uv run pytest
Expand Down Expand Up @@ -80,22 +78,17 @@ For the frontend, run `pnpm add <package>` inside `web/` and commit the change t
7. Merge.


One thing to expect: pushing a new commit also removes any approval the pull
request already had, so it needs approving again. That is deliberate, so that
what gets approved is what gets merged.
One thing to expect: pushing a new commit also removes any approval the pull request already had, so it needs approving again. That is deliberate, so that what gets approved is what gets merged.

The GitHub settings that make these checks compulsory are not stored in the repository. They are listed in [docs/branch-protection.md](docs/branch-protection.md).

## Testing

Every PR is expected to show some proof of testing for their work. This can be with unit tests or with new pytests added to one of the three groups below.

**Quick tests** have no marker on them. They run on every push and
decide whether a pull request is allowed to merge. Most tests should be in this
group. Write them so they stay fast.
**Quick tests** have no marker on them. They run on every push and decide whether a pull request is allowed to merge. Most tests should be in this group. Write them so they stay fast.

**Slow tests** are marked `@pytest.mark.slow`. They are left out when you push,
so they never hold up ordinary work. Run them on request, as described below.
**Slow tests** are marked `@pytest.mark.slow`. They are left out when you push, so they never hold up ordinary work. Run them on request, as described below.

**GPU tests** are marked `@pytest.mark.gpu`. These will only run on the project's own GPU machine, on request.

Expand All @@ -108,11 +101,7 @@ def test_core_model_runs():
...
```

The marker is what lets the GPU machine pick the test out and run it. The
`skipif` is what lets the test sit harmlessly in the suite everywhere else: on
a machine with no GPU it is reported as skipped instead of failing. Without
`skipif`, running the whole suite would fail for a reason that is not a real
problem with the code.
The marker is what lets the GPU machine pick the test out and run it. The `skipif` is what lets the test sit harmlessly in the suite everywhere else: on a machine with no GPU it is reported as skipped instead of failing. Without `skipif`, running the whole suite would fail for a reason that is not a real problem with the code.

## Running extra tests on a pull request

Expand All @@ -130,14 +119,8 @@ request:

Each comment runs its own job on the latest commit pushed to the branch, re-running a command while a job of the same type is running will cancel the previous run and start a new one on the latest commit.

Only people with access to this repository can use them. A short while after
commenting you should see a 👀 reaction on your comment, and then a reply with
the result.
Only people with access to this repository can use them. A short while after commenting you should see a 👀 reaction on your comment, and then a reply with the result.

The reply names the exact commit it tested. That matters: if you push more
commits afterwards, the old result no longer applies to the new code, and a
reviewer is meant to notice this and ask for the tests to be run again.
The reply names the exact commit it tested. That matters: if you push more commits afterwards, the old result no longer applies to the new code, and a reviewer is meant to notice this and ask for the tests to be run again.

These commands do not block merging on their own. Only the automatic checks do
that. Whether the extra tests were run is something the reviewer checks, using
the checklist in the pull request template.
These commands do not block merging on their own. Only the automatic checks do that. Whether the extra tests were run is something the reviewer checks, using the checklist in the pull request template.
27 changes: 18 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@

A reproduction of the [InfiniteDiffusion paper](https://arxiv.org/abs/2512.08309).

A game world like Minecraft lets a player walk in any direction forever, but the terrain looks blocky and artificial.
An AI image generator can paint a realistic landscape, but only one fixed size image, not an endless world.
A game world like Minecraft lets a player walk in any direction forever, but the terrain looks blocky and artificial. An AI image generator can paint a realistic landscape, but only one fixed size image, not an endless world.

This project produces a realistic, endless landscape from a seed.
A user chooses a seed, which is a number that fully determines the world, and explores the terrain in a 3D view.
The land has sharp ridges, river valleys, and coastlines, and it continues in every direction without end.
This project produces a realistic, endless landscape from a seed. A user chooses a seed, which is a number that fully determines the world, and explores the terrain in a 3D view. The land has sharp ridges, river valleys, and coastlines, and it continues in every direction without end.

Two properties define it:

Expand All @@ -18,24 +15,26 @@ The same seed always recreates the same world.

The models are already trained by the paper's authors. Training terrain models of this kind costs thousands of dollars and takes months, so this project does not train them. It uses the released weights and rebuilds the algorithm that runs on top of them. Running a model needs about 2.2 GB of GPU memory, so a free cloud GPU or a gaming laptop is enough.

The project has its own machine, an RTX 2060 with 6 GB, confirmed able to serve the backend publicly through an outbound tunnel. What that machine can and cannot do is in [docs/machine-findings.md](docs/machine-findings.md), and it constrains a few things: latency will be several times the paper's figures, and there is about 3 GB of GPU memory spare rather than the whole card.

## What we are building first

The MVP:

- As a user, I can give a seed and a region size and receive a height map image.
- As a user, I can give a seed and a region size of more than one tile and get terrain whose tiles meet with no visible seams.
- As a user, I can give the same seed twice and get exactly the same terrain both times.
- As a user, I can give the same seed twice and get exactly the same terrain both times. Exactly means bit for bit on the same machine at the same precision. Across different machines, or between full and half precision, terrain matches within a small tolerance instead.
- As a user, I can give a seed and a region and view the result as a 3D surface.

## How the system fits together

The system answers one question: what does the world look like at a given seed and coordinate.
It turns the answer into images and an interactive 3D view.
The system answers one question: what does the world look like at a given seed and coordinate. It turns the answer into images and an interactive 3D view.

```mermaid
graph TD
CLI["Output and CLI"] --> ORCH["Generation Orchestration"]
VIZ["3D Visualizer"] --> ORCH
VIZ["3D Visualizer"] -->|HTTP| API["Generation Service"]
API --> ORCH
ORCH --> SAMP["Windowed Blending Sampler"]
SAMP --> PIPE["Model Pipeline"]
SAMP --> STORE["Terrain Store"]
Expand All @@ -55,6 +54,7 @@ Which features live where:
- The Windowed Blending Sampler holds the core generation math.
- The Terrain Store holds memory behaviour, from bounded to lazy to infinite.
- Generation Orchestration drives a region, and in the build-out coordinates the multi scale hierarchy.
- The Generation Service puts Orchestration behind HTTP, because the visualizer runs in a browser and cannot call Python directly.
- Output and CLI and the 3D Visualizer hold user facing output.
- Benchmarking and Evaluation holds paper comparison.

Expand All @@ -70,6 +70,7 @@ Each component has one module. None of them are implemented yet, they hold the p
| Windowed Blending Sampler | `src/terrain_diffusion/sampler.py` |
| Terrain Store | `src/terrain_diffusion/store.py` |
| Generation Orchestration | `src/terrain_diffusion/orchestration.py` |
| Generation Service | `src/terrain_diffusion/service.py` |
| Output and CLI | `src/terrain_diffusion/cli.py` |
| Benchmarking and Evaluation | `src/terrain_diffusion/benchmark.py` |
| 3D Visualizer | `web/` |
Expand All @@ -86,12 +87,20 @@ Install [uv](https://docs.astral.sh/uv/getting-started/installation/), the tool
curl -LsSf https://astral.sh/uv/install.sh | sh
```

On Windows, in PowerShell:

```powershell
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
```

Then install the project's packages:

```bash
scripts/install-dependencies.sh
```

> On Windows, run that in Git Bash. PowerShell cannot run bash scripts. [onboarding.md](docs/onboarding.md) section 1 has the full setup for all three systems.

> You do not need to install python, uv handles and maintains the correct python version.

Check it worked:
Expand Down
20 changes: 5 additions & 15 deletions docs/branch-protection.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ test
web
```

They may instead appear in the list as `CI / format`, `CI / test`, and
`CI / web`. Choose whichever version GitHub shows you. `web` passes until the
`web/` folder exists, and starts checking it once it does.
They may instead appear in the list as `CI / format`, `CI / test`, and `CI / web`. Choose whichever version GitHub shows you. `web` passes until the `web/` folder exists, and starts checking it once it does.

Leave switched off:

Expand Down Expand Up @@ -73,22 +71,14 @@ Leave switched off:
- Workflow permissions: Read repository contents permission. Each workflow asks
for anything more at the top of its own file.

If an organisation owns this repository, these settings also exist at
organisation level, and the stricter of the two applies.
If an organisation owns this repository, these settings also exist at organisation level, and the stricter of the two applies.

## 5. Add the GPU Machine

`Settings -> Actions -> Runners -> New self-hosted runner`

Follow the instructions on that page. Add the label `gpu`. `self-hosted` is
applied automatically, and the workflow asks for both.
Follow the instructions on that page. Add the label `gpu`. `self-hosted` is applied automatically, and the workflow asks for both.

Then add `gpu` back to the `all` group in `.github/scripts/parse-command.sh`,
where a TODO marks the line. It is left out until the machine exists so that
`/test all` cannot wait on a runner that is not there.
Then add `gpu` back to the `all` group in `.github/scripts/parse-command.sh`, where a TODO marks the line. It is left out until the machine exists so that `/test all` cannot wait on a runner that is not there.

Until this is done, do not use `/test gpu`. GitHub allows a job to sit in the
queue waiting for a self-hosted runner for 24 hours before giving up, and the
hour long timeout on the job does not apply, because that limits how long a job
may run rather than how long it may wait. No result comment is posted until the
queue time runs out.
Until this is done, do not use `/test gpu`. GitHub allows a job to sit in the queue waiting for a self-hosted runner for 24 hours before giving up, and the hour long timeout on the job does not apply, because that limits how long a job may run rather than how long it may wait. No result comment is posted until the queue time runs out.
2 changes: 1 addition & 1 deletion scripts/test-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ fi
echo "==> Running the '$group' tests (pytest)"

status=0
uv run pytest "${marker_args[@]}" "${report_args[@]+"${report_args[@]}"}" "$@" || status=$?
uv run pytest "${marker_args[@]+"${marker_args[@]}"}" "${report_args[@]+"${report_args[@]}"}" "$@" || status=$?

if [[ $status -eq 5 ]]; then
if [[ "$group" == "python" ]]; then
Expand Down
1 change: 1 addition & 0 deletions src/terrain_diffusion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
sampler Windowed Blending Sampler
store Terrain Store
orchestration Generation Orchestration
service Generation Service
cli Output and CLI
benchmark Benchmarking and Evaluation
"""
28 changes: 28 additions & 0 deletions src/terrain_diffusion/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Generation Service

Overview

Puts Generation Orchestration behind HTTP so the 3D Visualizer can reach it.

The visualizer runs in a browser and cannot import Python, and Orchestration
runs on the machine holding the GPU. This component is the boundary between
them. It accepts a seed and a tile coordinate, asks Orchestration for the
region, and returns the height grid.

Output and CLI does not go through here. It calls Orchestration in process.

Neighbours and communication

- Receives requests over HTTP from the 3D Visualizer.
- Asks Generation Orchestration for a region and returns the height grid.

Notes for whoever builds this

- The project machine sits behind carrier-grade NAT and reaches the outside
through an outbound tunnel, so the client address arrives in the
`Cf-Connecting-Ip` and `X-Forwarded-For` headers. The socket always reports
`127.0.0.1`. Anything that logs or limits by address has to read the header.
- Clients may be IPv6. The service itself can stay IPv4 only.
- There is one GPU with about 3 GB spare, so concurrent requests can exhaust
it. Serialising generation is the simplest answer.
"""
35 changes: 35 additions & 0 deletions tests/test_introduction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Introductory file to get users familiar with tests and pull requests.

Perform the following steps:
- Add your username to the user_list
- Add a test to the file below and name it test_<username>
- Assert something trivial.
- Run the bashs script ./scripts/test-check.sh
- Fix any and all errors that occur, rerun untill all are passing
- Create a pull request to add your changes.
"""

import pytest


class TestIntroductory:
@pytest.fixture
def user_list(self):
return [
# add your usernames here
"kurbydoo",
]

def test_kurbydoo(self, user_list):
assert 1 + 1 == 2, "Somthing went really wrong"

n = 10
counter = 0
for i in range(1, n):
counter += i

assert counter == n * (n - 1) // 2, "Check arithmetic sum"

assert len(user_list) == 1

### Add your tests below, follow the format above
1 change: 1 addition & 0 deletions tests/test_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"terrain_diffusion.sampler",
"terrain_diffusion.store",
"terrain_diffusion.orchestration",
"terrain_diffusion.service",
"terrain_diffusion.cli",
"terrain_diffusion.benchmark",
]
Expand Down