Add the Horizon logo, banner and in-game screenshots to the README
An original logo (a sun setting behind a blocky horizon), a banner and a social preview card, and six screenshots captured in the Rust client. The README opens with badges, a short pitch and a gallery; a test keeps the parity badge in step with the subsystem table. Packaged builds use the logo as their menu icon. Co-Authored-By: Claude Opus 5.5 <[email protected]>
No files matched your search
@@ -1,3 +1,23 @@
|
||||
<p align="center">
|
||||
<img src="docs/media/banner.jpg" alt="Horizon: Minecraft Java Edition 26.3, rewritten from scratch in Rust" width="100%">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/ludovic111/horizon/actions/workflows/ci.yml"><img src="https://github.com/ludovic111/horizon/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
|
||||
<img src="https://img.shields.io/badge/Minecraft-Java%2026.3-5D9C3F" alt="Minecraft Java 26.3">
|
||||
<img src="https://img.shields.io/badge/Rust-stable-B7410E?logo=rust&logoColor=white" alt="Rust">
|
||||
<img src="https://img.shields.io/badge/vanilla%20parity-80%25-F0795A" alt="Vanilla parity 80%">
|
||||
<img src="https://img.shields.io/badge/platform-Linux-2A2470?logo=linux&logoColor=white" alt="Linux">
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-3B2F7E" alt="MIT license"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="#getting-it"><b>Get started</b></a> ·
|
||||
<a href="#screenshots"><b>Screenshots</b></a> ·
|
||||
<a href="#project-status"><b>Parity status</b></a> ·
|
||||
<a href="docs/native-client.md"><b>Native client guide</b></a>
|
||||
</p>
|
||||
|
||||
# Horizon
|
||||
|
||||
A Rust rewrite of Minecraft: Java Edition 26.3 that aims for one-to-one vanilla parity, checked against the game itself with differential tests.
|
||||
@@ -6,6 +26,30 @@ A Rust rewrite of Minecraft: Java Edition 26.3 that aims for one-to-one vanilla
|
||||
|
||||
> Not an official Minecraft product. Not approved by or associated with Mojang or Microsoft. Horizon contains no Minecraft code, assets or data: you need your own legally obtained copy of Minecraft: Java Edition 26.3.
|
||||
|
||||
## Screenshots
|
||||
|
||||
Captured in the Rust client itself, with vanilla assets and no mods or shaders.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td colspan="2"><img src="docs/media/sunset.jpg" alt="Sunset over a savanna river valley and a plains village"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%"><img src="docs/media/village.jpg" alt="A plains village with farms, an iron golem and animals"></td>
|
||||
<td width="50%"><img src="docs/media/survival.jpg" alt="Survival at dusk: a zombie attacks near the village"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%"><img src="docs/media/nether.jpg" alt="A crimson forest in the Nether with a zombified piglin"></td>
|
||||
<td width="50%"><img src="docs/media/night.jpg" alt="Moonrise over a torch-lit village and river"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%"><img src="docs/media/underwater.jpg" alt="Salmon swimming in a river"></td>
|
||||
<td width="50%" valign="middle">
|
||||
Vanilla world generation ported to Rust (terrain, aquifers, carvers, features, villages and the Nether), every block model with smooth lighting and biome tints, the vanilla sky and fog, mobs, the HUD, chat and advancements: everything in these frames is drawn by Horizon.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## What it does
|
||||
|
||||
Horizon reimplements the Minecraft Java client and its game logic in Rust, one subsystem at a time, and compares each piece with the vanilla game. Each `tools/test_*_parity.py` harness drives the same inputs through Horizon and through the vanilla client and server bytecode from your own copy of the game, then checks that the outputs are identical. It is for players and developers interested in game reimplementation, and for anyone who wants to see how far a behaviour-exact native rewrite can go.
|
||||
|
||||
|
After Width: | Height: | Size: 320 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 153 KiB |
@@ -0,0 +1,62 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
||||
<title>Horizon</title>
|
||||
<defs>
|
||||
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#0A0F2C"/>
|
||||
<stop offset="0.38" stop-color="#26206A"/>
|
||||
<stop offset="0.62" stop-color="#7C3A88"/>
|
||||
<stop offset="0.78" stop-color="#EE6E5A"/>
|
||||
<stop offset="0.9" stop-color="#FFB25E"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="sun" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#FFF6CF"/>
|
||||
<stop offset="0.5" stop-color="#FFD272"/>
|
||||
<stop offset="1" stop-color="#FF9A44"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="glow" cx="0.5" cy="0.6" r="0.55">
|
||||
<stop offset="0" stop-color="#FFC874" stop-opacity="0.6"/>
|
||||
<stop offset="0.6" stop-color="#FF8A5C" stop-opacity="0.12"/>
|
||||
<stop offset="1" stop-color="#FF8A5C" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="far" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#4A2F7E"/>
|
||||
<stop offset="1" stop-color="#2B1F5E"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="near" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#151A45"/>
|
||||
<stop offset="1" stop-color="#080B22"/>
|
||||
</linearGradient>
|
||||
<clipPath id="tile"><rect width="512" height="512" rx="112"/></clipPath>
|
||||
<clipPath id="sunCut"><rect x="0" y="0" width="512" height="352"/></clipPath>
|
||||
<clipPath id="sunDisc"><circle cx="256" cy="300" r="136"/></clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#tile)">
|
||||
<rect width="512" height="512" fill="url(#sky)"/>
|
||||
<ellipse cx="256" cy="320" rx="300" ry="230" fill="url(#glow)"/>
|
||||
<g clip-path="url(#sunCut)">
|
||||
<circle cx="256" cy="300" r="136" fill="url(#sun)"/>
|
||||
<g clip-path="url(#sunDisc)">
|
||||
<rect x="100" y="262" width="312" height="8" fill="#EE6E5A" opacity="0.5"/>
|
||||
<rect x="100" y="284" width="312" height="11" fill="#EE6E5A" opacity="0.62"/>
|
||||
<rect x="100" y="310" width="312" height="14" fill="#EE6E5A" opacity="0.74"/>
|
||||
</g>
|
||||
</g>
|
||||
<path fill="url(#far)" d="M0 320H64V288H128V320H352V288H416V256H512V512H0Z"/>
|
||||
<path fill="url(#near)" d="M0 384H64V352H192V384H320V352H384V320H448V352H512V512H0Z"/>
|
||||
<g fill="#FFFFFF" opacity="0.07">
|
||||
<rect x="64" y="352" width="4" height="160"/>
|
||||
<rect x="192" y="352" width="4" height="160"/>
|
||||
<rect x="320" y="352" width="4" height="160"/>
|
||||
<rect x="384" y="320" width="4" height="192"/>
|
||||
<rect x="448" y="320" width="4" height="192"/>
|
||||
<rect x="0" y="448" width="512" height="4"/>
|
||||
</g>
|
||||
<rect x="0" y="384" width="64" height="4" fill="#FFB25E" opacity="0.25"/>
|
||||
<rect x="64" y="352" width="128" height="4" fill="#FFB25E" opacity="0.35"/>
|
||||
<rect x="192" y="384" width="128" height="4" fill="#FFB25E" opacity="0.25"/>
|
||||
<rect x="320" y="352" width="64" height="4" fill="#FFB25E" opacity="0.35"/>
|
||||
<rect x="384" y="320" width="64" height="4" fill="#FFB25E" opacity="0.45"/>
|
||||
<rect x="448" y="352" width="64" height="4" fill="#FFB25E" opacity="0.35"/>
|
||||
</g>
|
||||
<rect x="2" y="2" width="508" height="508" rx="110" fill="none" stroke="#FFFFFF" stroke-opacity="0.1" stroke-width="4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 155 KiB |
|
After Width: | Height: | Size: 265 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 291 KiB |
@@ -14,6 +14,9 @@ class ParityTests(unittest.TestCase):
|
||||
scores = [float(score) for score in re.findall(r'^\| [^|]+ \| (0|0\.25|0\.5|0\.75|1) \| [^|]+ \|$', table, re.MULTILINE)]
|
||||
self.assertEqual(len(scores), table.count('\n|') - 1, 'Every row needs a score of 0, 0.25, 0.5, 0.75 or 1')
|
||||
self.assertEqual(int(heading.group(1)), round(sum(scores) * 100 / len(scores)))
|
||||
badge = re.search(r'badge/vanilla%20parity-(\d+)%25-', text)
|
||||
self.assertIsNotNone(badge, 'README parity badge is missing')
|
||||
self.assertEqual(badge.group(1), heading.group(1), 'The parity badge must match the table')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -33,7 +33,7 @@ Type=Application
|
||||
Name=Horizon
|
||||
Comment=Minecraft Java Edition 26.3 in Rust
|
||||
Exec="$here/horizon.sh"
|
||||
Icon=$here/resources/assets/minecraft/textures/block/grass_block_side.png
|
||||
Icon=$here/horizon.png
|
||||
Terminal=false
|
||||
Categories=Game;
|
||||
EOF
|
||||
@@ -103,6 +103,7 @@ def main():
|
||||
with tarfile.open(archive, 'w:gz', compresslevel=1) as tar:
|
||||
tar.add(args.binary, 'horizon/horizon-client')
|
||||
add_bytes(tar, 'horizon/horizon.sh', LAUNCHER.encode(), 0o755)
|
||||
tar.add(ROOT / 'docs/media/icon-256.png', 'horizon/horizon.png')
|
||||
add_bytes(tar, 'horizon/README.txt', README.format(version=version, commit=commit, platform=plat).encode())
|
||||
for path in sorted(args.resources.iterdir()):
|
||||
if path.name not in SKIP:
|
||||
|
||||