The first commit, the core and all files is adding to repo
This commit is contained in:
112
shared/protocol.ts
Normal file
112
shared/protocol.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { z } from "zod";
|
||||
export const boundsSchema = z
|
||||
.object({
|
||||
minX: z.number().int(),
|
||||
minZ: z.number().int(),
|
||||
maxX: z.number().int(),
|
||||
maxZ: z.number().int(),
|
||||
})
|
||||
.refine(
|
||||
(b) =>
|
||||
b.maxX > b.minX &&
|
||||
b.maxZ > b.minZ &&
|
||||
b.maxX - b.minX <= 2048 &&
|
||||
b.maxZ - b.minZ <= 2048,
|
||||
"Bounds must be positive and at most 2048 blocks per side",
|
||||
);
|
||||
export const pointSchema = z.object({
|
||||
x: z.number().finite(),
|
||||
y: z.number().finite(),
|
||||
z: z.number().finite(),
|
||||
});
|
||||
export const faces = ["px", "nx", "py", "ny", "pz", "nz"] as const;
|
||||
export const datasetSchema = z
|
||||
.object({
|
||||
format: z.literal("minegeo-dataset/v1"),
|
||||
minecraftVersion: z.literal("1.21.11"),
|
||||
dimension: z.literal("minecraft:overworld"),
|
||||
source: z.enum(["plugin", "fabric"]),
|
||||
name: z.string().min(3).max(80),
|
||||
description: z.string().max(2000).default(""),
|
||||
serverName: z.string().min(2).max(80),
|
||||
serverAddress: z.string().max(255).optional(),
|
||||
bounds: boundsSchema,
|
||||
north: z.literal("-z"),
|
||||
viewpoints: z
|
||||
.array(
|
||||
pointSchema.extend({ id: z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/) }),
|
||||
)
|
||||
.min(5)
|
||||
.max(100),
|
||||
assets: z
|
||||
.array(
|
||||
z.object({
|
||||
path: z
|
||||
.string()
|
||||
.regex(/^[a-zA-Z0-9_./-]+$/)
|
||||
.refine((s) => !s.includes("..") && !s.startsWith("/")),
|
||||
sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
||||
bytes: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.max(50 * 1024 * 1024),
|
||||
}),
|
||||
)
|
||||
.max(4096),
|
||||
})
|
||||
.superRefine((d, ctx) => {
|
||||
if (new Set(d.viewpoints.map((p) => p.id)).size !== d.viewpoints.length)
|
||||
ctx.addIssue({ code: "custom", message: "Viewpoint IDs must be unique" });
|
||||
if (new Set(d.assets.map((a) => a.path)).size !== d.assets.length)
|
||||
ctx.addIssue({ code: "custom", message: "Asset paths must be unique" });
|
||||
for (const p of d.viewpoints)
|
||||
if (
|
||||
p.x < d.bounds.minX ||
|
||||
p.x > d.bounds.maxX ||
|
||||
p.z < d.bounds.minZ ||
|
||||
p.z > d.bounds.maxZ
|
||||
)
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: "Viewpoint outside map bounds",
|
||||
});
|
||||
});
|
||||
export type DatasetManifest = z.infer<typeof datasetSchema>;
|
||||
export type Bounds = z.infer<typeof boundsSchema>;
|
||||
export type Point = z.infer<typeof pointSchema>;
|
||||
export const difficulties = {
|
||||
easy: { seconds: 180, zoom: true, label: "Easy" },
|
||||
normal: { seconds: 90, zoom: true, label: "Normal" },
|
||||
hard: { seconds: 30, zoom: false, label: "Hard" },
|
||||
} as const;
|
||||
export type Difficulty = keyof typeof difficulties;
|
||||
export function scoreGuess(
|
||||
a: Pick<Point, "x" | "z">,
|
||||
b: Pick<Point, "x" | "z">,
|
||||
bounds: Bounds,
|
||||
) {
|
||||
const distance = Math.hypot(a.x - b.x, a.z - b.z);
|
||||
return {
|
||||
distance,
|
||||
score:
|
||||
distance <= 1
|
||||
? 5000
|
||||
: Math.round(
|
||||
5000 *
|
||||
Math.exp(
|
||||
(-5 * distance) /
|
||||
Math.hypot(
|
||||
bounds.maxX - bounds.minX,
|
||||
bounds.maxZ - bounds.minZ,
|
||||
),
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
export type TerrainBlock = [number, number, number, string, string?];
|
||||
export type TerrainSnapshot = {
|
||||
bounds: Bounds;
|
||||
blocks: TerrainBlock[];
|
||||
coverage: [number, number][];
|
||||
};
|
||||
Reference in New Issue
Block a user