From cae06eb97ee7d32baa2427de2a7374b50a4e435b Mon Sep 17 00:00:00 2001 From: Master290 Date: Tue, 21 Jul 2026 09:21:44 +0300 Subject: [PATCH] Implement multiplayer persistence and vanilla lighting --- .gitignore | 1 + Makefile | 10 + README.md | 57 +- cmd/regionio/main.go | 2 +- internal/network/configuration.go | 8 - internal/network/conn.go | 8 +- internal/network/entity_packets_test.go | 276 +++++++++ internal/network/handler.go | 13 +- internal/network/listener.go | 37 +- internal/network/multiplayer_test.go | 321 +++++++++++ internal/network/play.go | 360 +++++++++--- internal/network/streamer.go | 38 +- internal/protocol/buffer.go | 15 +- internal/protocol/ids.go | 60 +- internal/protocol/ids_test.go | 117 ++++ internal/registry/entity_test.go | 25 + internal/registry/registry.go | 39 +- internal/server/server.go | 283 +++++++++- internal/server/server_test.go | 130 +++++ internal/server/spawner.go | 147 ++--- internal/world/cache.go | 251 ++++++--- internal/world/cache_light.go | 174 ++++++ internal/world/cache_test.go | 75 +++ internal/world/chunk.go | 8 +- internal/world/compress.go | 12 +- internal/world/encode.go | 123 +++- internal/world/entity.go | 49 +- internal/world/features.go | 2 +- internal/world/golden_test.go | 98 +++- internal/world/light.go | 525 +++++++++++++++--- internal/world/light_data.go | 5 - internal/world/light_properties.bin | Bin 0 -> 204677 bytes internal/world/light_properties.go | 113 ++++ internal/world/light_test.go | 207 +++++++ internal/world/regionfile.go | 21 +- internal/world/state_names.go | 25 +- internal/world/store.go | 146 ++++- internal/world/store_test.go | 199 ++++++- internal/world/terrain.go | 2 +- .../vanilla_glowstone_block_light.bin | Bin 0 -> 29791 bytes internal/world/vanilla.go | 8 +- tools/VanillaLightDump.java | 111 ++++ tools/vanilla_light_fixture.go | 148 +++++ world/region/r.-1.-1.mca | Bin 540672 -> 552960 bytes world/region/r.-1.0.mca | Bin 1150976 -> 1171456 bytes world/region/r.0.-1.mca | Bin 212992 -> 237568 bytes world/region/r.0.0.mca | Bin 225280 -> 253952 bytes 47 files changed, 3784 insertions(+), 465 deletions(-) create mode 100644 Makefile create mode 100644 internal/network/entity_packets_test.go create mode 100644 internal/network/multiplayer_test.go create mode 100644 internal/protocol/ids_test.go create mode 100644 internal/registry/entity_test.go create mode 100644 internal/server/server_test.go create mode 100644 internal/world/cache_light.go delete mode 100644 internal/world/light_data.go create mode 100644 internal/world/light_properties.bin create mode 100644 internal/world/light_properties.go create mode 100644 internal/world/light_test.go create mode 100644 internal/world/testdata/vanilla_glowstone_block_light.bin create mode 100644 tools/VanillaLightDump.java create mode 100644 tools/vanilla_light_fixture.go diff --git a/.gitignore b/.gitignore index 4a24f3e..ceb53e7 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ # Logs and runtime artefacts /logs/ *.log +/world/regionio-world.json # Editor / tool local config /.claude/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5106361 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +.PHONY: test test-race verify + +test: + go test ./... + +test-race: + go test -race ./internal/network ./internal/server ./internal/world \ + -run 'Test(Integration|BoundaryEdit|PlayerInfo|PlayerRegistry|Concurrent|Incremental|EncodeLight|Cache|Store|Eviction|Region)' + +verify: test test-race diff --git a/README.md b/README.md index bec2b3b..a2338e4 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ A Minecraft Java Edition server core written in Go, targeting version **26.1.2** (protocol **775**). RegionIO implements the connection lifecycle -(status → login → configuration → play), chunk streaming, block editing, and a -vanilla-faithful overworld generator built on the real `noise_router` -`final_density` tree. +(status → login → configuration → play), multiplayer chunk streaming, shared +block editing, persistent worlds, and an overworld generator built on the real +`noise_router` `final_density` tree. ## Status @@ -12,14 +12,22 @@ vanilla-faithful overworld generator built on the real `noise_router` state machine with zlib compression, keep-alive, and chunk streaming. - **Registries**: 28 synchronized registries + tags, captured verbatim from the 26.1.2 vanilla server and sent during configuration. -- **World**: in-memory chunk cache with memoized, compression-ready - `level_chunk_with_light` frames; paletted block containers; heightmaps. -- **Generation**: bit-faithful overworld terrain from the embedded datapack +- **World**: revisioned, concurrency-safe chunk snapshots; memoized + `level_chunk_with_light` frames; bounded LRU cache; Anvil `.mca` persistence + with autosave and seed metadata. +- **Generation**: vanilla-derived overworld terrain from the embedded datapack (`ImprovedNoise`/`PerlinNoise`/`BlendedNoise`/`NormalNoise` + the density - function interpreter), plus multi-noise **biomes** (per-chunk, surface layer) - via the vanilla `Climate` finder over the official biome parameter table. -- **Gameplay**: creative block place/break, hotbar item→block mapping, chat, - teleport ack, and a randomized bedrock floor / beach & gravel surface pass. + function interpreter), 3D multi-noise biomes, surface-rule interpretation, + deterministic decoration, and basic template structures. +- **Gameplay**: four-player session registry; player join/leave and movement + synchronization; chunk-scoped visibility for players and mobs; shared + creative block place/break; broadcast chat; and hotbar item→block mapping. +- **Lighting**: stored vanilla nibble arrays for sky and block light; horizontal + and cross-chunk propagation; incremental updates after edits; persisted + `SkyLight`/`BlockLight`; and chunk-scoped `light_update` broadcasts. +- **Safety**: duplicate chunk generation is coalesced; corrupt stored chunks are + not silently regenerated or overwritten; a world cannot reopen with another + seed. ## Build & run @@ -30,16 +38,39 @@ go run ./cmd/regionio -seed 12345 The world seed defaults to `0`; override it with the `-seed` flag or the `REGIONIO_SEED` environment variable. The server listens on `0.0.0.0:25565`. +Changing the seed for an existing world directory is rejected. ## Testing ``` go test ./... +go test -race ./internal/network ./internal/server ./internal/world \ + -run 'Test(Integration|BoundaryEdit|PlayerInfo|PlayerRegistry|Concurrent|Incremental|EncodeLight|Cache|Store|Eviction|Region)' +# or run both gates: +make verify ``` -Parity tests (`internal/world/vanilla_parity_test.go`) compare generated surface -heights against captures from the official server and skip when no capture is -present. +The integration suite exercises four clients across two visibility regions: +join, movement, leaving, mob visibility, and local block/light updates. A +two-client scenario separately covers shared block edits and chat. Concurrency +tests cover simultaneous frame encoding, editing, autosave, cache misses, and +session movement/broadcasts. Lighting tests compare the initial flat chunk and a +31x31x31 glowstone propagation volume against fixtures captured from the +official vanilla 26.1.2 server. Optional terrain parity diagnostics compare +surface heights against `/tmp/vanilla_ground.json` when that capture is present. + +## v0.3 scope + +RegionIO v0.3 is a small creative multiplayer server core, not a complete +vanilla gameplay implementation. Player and mob visibility is chunk-scoped, +but there is no interest prioritization or delta-movement compression yet. +Lighting matches vanilla's block-state dampening, emission, and face-occlusion +properties and propagates across loaded chunk boundaries. Chunks outside the +live cache are recalculated exactly when loaded rather than retained as active +light-engine state. Structures, placed features, mob AI, authentication, +inventory, and survival mechanics remain intentionally partial. The density +router is vanilla-derived, while biome/surface/decoration layers still contain +approximations and require stricter parity fixtures. ## Project layout diff --git a/cmd/regionio/main.go b/cmd/regionio/main.go index 306a520..992ee41 100644 --- a/cmd/regionio/main.go +++ b/cmd/regionio/main.go @@ -46,7 +46,7 @@ func main() { saveCtx, saveStop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) autosaveDone := srv.Chunks().StartAutosave(saveCtx, log, 30*time.Second) - srv.StartSpawning() + srv.StartSpawning(saveCtx) ln := network.NewListener(srv, log) diff --git a/internal/network/configuration.go b/internal/network/configuration.go index bf18423..d078a65 100644 --- a/internal/network/configuration.go +++ b/internal/network/configuration.go @@ -7,14 +7,6 @@ import ( "regionio/internal/registry" ) -// ClientSettings holds the subset of client_information we currently track. -type ClientSettings struct { - Locale string - ViewDistance int8 - ChatMode int32 - MainHand int32 -} - // beginConfiguration is called on entering the configuration phase. It mirrors // the vanilla opening sequence: server brand, enabled feature flags, then the // known-packs negotiation. The client's known-packs reply triggers the registry diff --git a/internal/network/conn.go b/internal/network/conn.go index 29fb567..b0a3f81 100644 --- a/internal/network/conn.go +++ b/internal/network/conn.go @@ -6,6 +6,7 @@ import ( "bufio" "net" "sync" + "time" "regionio/internal/protocol" "regionio/internal/server" @@ -56,7 +57,12 @@ func (c *Conn) RemoteAddr() net.Addr { return c.raw.RemoteAddr() } // ReadPacket reads the next frame using the current compression settings. func (c *Conn) ReadPacket() (protocol.Packet, error) { - return protocol.ReadPacket(c.br, c.compressionThreshold) + if err := c.raw.SetReadDeadline(time.Now().Add(30 * time.Second)); err != nil { + return protocol.Packet{}, err + } + pkt, err := protocol.ReadPacket(c.br, c.compressionThreshold) + _ = c.raw.SetReadDeadline(time.Time{}) + return pkt, err } // Send writes a packet with the given ID and pre-encoded body. Safe for diff --git a/internal/network/entity_packets_test.go b/internal/network/entity_packets_test.go new file mode 100644 index 0000000..a140516 --- /dev/null +++ b/internal/network/entity_packets_test.go @@ -0,0 +1,276 @@ +package network + +import ( + "bufio" + "bytes" + "net" + "testing" + + "regionio/internal/protocol" + "regionio/internal/server" + "regionio/internal/world" +) + +func readServerPacket(t *testing.T, c net.Conn) protocol.Packet { + t.Helper() + // net.Pipe has no buffering; read the frame from a goroutine-driven write. + br := make([]byte, 4096) + n, err := c.Read(br) + if err != nil { + t.Fatalf("reading packet: %v", err) + } + pkt, err := protocol.ReadPacket(bufio.NewReader(bytes.NewReader(br[:n])), -1) + if err != nil { + t.Fatalf("decoding packet: %v", err) + } + return pkt +} + +func TestSendEntityTeleportUsesPositionMoveRotation(t *testing.T) { + serverSide, clientSide := net.Pipe() + defer serverSide.Close() + defer clientSide.Close() + + h := &handler{conn: NewConn(serverSide)} + ent := &world.Entity{ + ID: 42, + X: 1.25, + Y: 65.5, + Z: -3.75, + Yaw: 90, + Pitch: 15, + VelocityX: 80, + VelocityY: -160, + VelocityZ: 240, + } + + errc := make(chan error, 1) + go func() { errc <- h.sendEntityTeleport(ent) }() + + pkt := readServerPacket(t, clientSide) + if err := <-errc; err != nil { + t.Fatalf("sendEntityTeleport: %v", err) + } + if pkt.ID != protocol.PlayTeleportEntity { + t.Fatalf("packet id = %#x, want %#x", pkt.ID, protocol.PlayTeleportEntity) + } + + r := pkt.Body() + if id, err := r.VarInt(); err != nil || id != ent.ID { + t.Fatalf("entity id = %d, %v; want %d", id, err, ent.ID) + } + coords := []struct { + name string + want float64 + }{ + {"x", ent.X}, + {"y", ent.Y}, + {"z", ent.Z}, + } + for _, tc := range coords { + got, err := r.Float64() + if err != nil || got != tc.want { + t.Fatalf("%s = %v, %v; want %v", tc.name, got, err, tc.want) + } + } + velocities := []struct { + name string + want float64 + }{ + {"vx", float64(ent.VelocityX) / 8000.0}, + {"vy", float64(ent.VelocityY) / 8000.0}, + {"vz", float64(ent.VelocityZ) / 8000.0}, + } + for _, tc := range velocities { + got, err := r.Float64() + if err != nil || got != tc.want { + t.Fatalf("%s = %v, %v; want %v", tc.name, got, err, tc.want) + } + } + if yaw, err := r.Float32(); err != nil || yaw != ent.Yaw { + t.Fatalf("yaw = %v, %v; want %v", yaw, err, ent.Yaw) + } + if pitch, err := r.Float32(); err != nil || pitch != ent.Pitch { + t.Fatalf("pitch = %v, %v; want %v", pitch, err, ent.Pitch) + } + if flags, err := r.Int32(); err != nil || flags != 0 { + t.Fatalf("relative flags = %d, %v; want 0", flags, err) + } + if onGround, err := r.Bool(); err != nil || !onGround { + t.Fatalf("onGround = %v, %v; want true", onGround, err) + } + if rem := r.Remaining(); rem != 0 { + t.Fatalf("remaining bytes = %d, want 0", rem) + } +} + +func TestSendAddEntityLayout(t *testing.T) { + serverSide, clientSide := net.Pipe() + defer serverSide.Close() + defer clientSide.Close() + + uuid := [16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10} + h := &handler{conn: NewConn(serverSide)} + ent := &world.Entity{ + ID: 43, + UUID: uuid, + TypeID: 77, + X: 10.5, + Y: 66.25, + Z: -20.75, + Pitch: 45, + Yaw: 180, + HeadYaw: 90, + VelocityX: 123, + VelocityY: -456, + VelocityZ: 789, + } + + errc := make(chan error, 1) + go func() { errc <- h.sendAddEntity(ent) }() + + pkt := readServerPacket(t, clientSide) + if err := <-errc; err != nil { + t.Fatalf("sendAddEntity: %v", err) + } + if pkt.ID != protocol.PlayAddEntity { + t.Fatalf("packet id = %#x, want %#x", pkt.ID, protocol.PlayAddEntity) + } + + r := pkt.Body() + if id, err := r.VarInt(); err != nil || id != ent.ID { + t.Fatalf("entity id = %d, %v; want %d", id, err, ent.ID) + } + if got, err := r.UUID(); err != nil || got != uuid { + t.Fatalf("uuid = %x, %v; want %x", got, err, uuid) + } + if typ, err := r.VarInt(); err != nil || typ != int32(ent.TypeID) { + t.Fatalf("type id = %d, %v; want %d", typ, err, ent.TypeID) + } + coords := []struct { + name string + want float64 + }{ + {"x", ent.X}, + {"y", ent.Y}, + {"z", ent.Z}, + } + for _, tc := range coords { + got, err := r.Float64() + if err != nil || got != tc.want { + t.Fatalf("%s = %v, %v; want %v", tc.name, got, err, tc.want) + } + } + angles := []struct { + name string + want byte + }{ + {"pitch", byte(ent.Pitch * 256.0 / 360.0)}, + {"yaw", byte(ent.Yaw * 256.0 / 360.0)}, + {"headYaw", byte(ent.HeadYaw * 256.0 / 360.0)}, + } + for _, tc := range angles { + got, err := r.ReadByte() + if err != nil || got != tc.want { + t.Fatalf("%s = %d, %v; want %d", tc.name, got, err, tc.want) + } + } + if data, err := r.VarInt(); err != nil || data != 0 { + t.Fatalf("data = %d, %v; want 0", data, err) + } + encodedVelocities := []struct { + name string + want uint16 + }{ + {"velocityX", uint16(ent.VelocityX)}, + {"velocityY", uint16(ent.VelocityY)}, + {"velocityZ", uint16(ent.VelocityZ)}, + } + for _, tc := range encodedVelocities { + got, err := r.Uint16() + if err != nil || got != tc.want { + t.Fatalf("%s = %d, %v; want %d", tc.name, got, err, tc.want) + } + } + if rem := r.Remaining(); rem != 0 { + t.Fatalf("remaining bytes = %d, want 0", rem) + } +} + +func TestPlayerInfoPacketLayouts(t *testing.T) { + serverSide, clientSide := net.Pipe() + defer serverSide.Close() + defer clientSide.Close() + + profile := server.Profile{Name: "Alice", UUID: server.OfflineUUID("Alice")} + h := &handler{conn: NewConn(serverSide)} + + errCh := make(chan error, 1) + go func() { errCh <- h.sendPlayerInfoAdd(profile) }() + pkt := readServerPacket(t, clientSide) + if err := <-errCh; err != nil { + t.Fatal(err) + } + if pkt.ID != protocol.PlayPlayerInfoUpdate { + t.Fatalf("player info update id = %#x, want %#x", pkt.ID, protocol.PlayPlayerInfoUpdate) + } + r := pkt.Body() + if actions, err := r.ReadByte(); err != nil || actions != 0xff { + t.Fatalf("actions = %#x, %v; want 0xff", actions, err) + } + if count, err := r.VarInt(); err != nil || count != 1 { + t.Fatalf("entry count = %d, %v; want 1", count, err) + } + if uuid, err := r.UUID(); err != nil || uuid != profile.UUID { + t.Fatalf("profile UUID = %x, %v; want %x", uuid, err, profile.UUID) + } + if name, err := r.String(); err != nil || name != profile.Name { + t.Fatalf("profile name = %q, %v; want %q", name, err, profile.Name) + } + if properties, err := r.VarInt(); err != nil || properties != 0 { + t.Fatalf("properties = %d, %v; want 0", properties, err) + } + if chatSession, err := r.Bool(); err != nil || chatSession { + t.Fatalf("chat session = %v, %v; want false", chatSession, err) + } + if gameMode, err := r.VarInt(); err != nil || gameMode != 1 { + t.Fatalf("game mode = %d, %v; want 1", gameMode, err) + } + if listed, err := r.Bool(); err != nil || !listed { + t.Fatalf("listed = %v, %v; want true", listed, err) + } + if latency, err := r.VarInt(); err != nil || latency != 0 { + t.Fatalf("latency = %d, %v; want 0", latency, err) + } + if displayName, err := r.Bool(); err != nil || displayName { + t.Fatalf("display name = %v, %v; want absent", displayName, err) + } + if order, err := r.VarInt(); err != nil || order != 0 { + t.Fatalf("list order = %d, %v; want 0", order, err) + } + if showHat, err := r.Bool(); err != nil || !showHat { + t.Fatalf("show hat = %v, %v; want true", showHat, err) + } + if r.Remaining() != 0 { + t.Fatalf("player info update trailing bytes = %d", r.Remaining()) + } + + go func() { errCh <- h.sendPlayerInfoRemove(profile.UUID) }() + pkt = readServerPacket(t, clientSide) + if err := <-errCh; err != nil { + t.Fatal(err) + } + if pkt.ID != protocol.PlayPlayerInfoRemove { + t.Fatalf("player info remove id = %#x, want %#x", pkt.ID, protocol.PlayPlayerInfoRemove) + } + r = pkt.Body() + if count, err := r.VarInt(); err != nil || count != 1 { + t.Fatalf("remove count = %d, %v; want 1", count, err) + } + if uuid, err := r.UUID(); err != nil || uuid != profile.UUID { + t.Fatalf("removed UUID = %x, %v; want %x", uuid, err, profile.UUID) + } + if r.Remaining() != 0 { + t.Fatalf("player info remove trailing bytes = %d", r.Remaining()) + } +} diff --git a/internal/network/handler.go b/internal/network/handler.go index 28a45d6..6073b3f 100644 --- a/internal/network/handler.go +++ b/internal/network/handler.go @@ -27,11 +27,14 @@ type handler struct { streamer *streamer // viewDistance is the client's requested view distance (from // client_information), clamped; used to size the streamer. - viewDistance int + viewDistance int + session *server.PlayerSession + knownPlayers map[[16]byte]bool + knownEntities map[int32]visibleEntity // Creative inventory state for block placement. - heldSlot int32 // selected hotbar index (0-8) - hotbar [9]int32 // item network IDs per hotbar slot (-1 = empty) + heldSlot int32 // selected hotbar index (0-8) + hotbar [9]int32 // item network IDs per hotbar slot (-1 = empty) } // serve runs the read/dispatch loop for a single connection. It owns the @@ -44,7 +47,7 @@ func (h *handler) serve() { defer cancel() // stops the streamer when the read loop ends h.ctx = ctx - defer h.srv.RemovePlayerPosition(h.conn.Profile.Name) + defer func() { h.srv.UnregisterPlayer(h.session) }() for { pkt, err := h.conn.ReadPacket() @@ -123,7 +126,7 @@ func (h *handler) handleHandshake(pkt protocol.Packet) error { func (h *handler) handleStatus(pkt protocol.Packet) error { switch pkt.ID { case protocol.StatusRequestID: - jsonBytes, err := h.srv.StatusJSON(0) + jsonBytes, err := h.srv.StatusJSON(h.srv.PlayerCount()) if err != nil { return err } diff --git a/internal/network/listener.go b/internal/network/listener.go index f52a823..d6b8136 100644 --- a/internal/network/listener.go +++ b/internal/network/listener.go @@ -5,19 +5,23 @@ import ( "fmt" "log/slog" "net" + "sync" "regionio/internal/server" ) // Listener accepts TCP connections and serves each in its own goroutine. type Listener struct { - srv *server.Server - log *slog.Logger + srv *server.Server + log *slog.Logger + mu sync.Mutex + conns map[net.Conn]struct{} + wg sync.WaitGroup } // NewListener constructs a Listener bound to srv. func NewListener(srv *server.Server, log *slog.Logger) *Listener { - return &Listener{srv: srv, log: log} + return &Listener{srv: srv, log: log, conns: make(map[net.Conn]struct{})} } // ListenAndServe binds the configured address and accepts connections until @@ -31,6 +35,11 @@ func (l *Listener) ListenAndServe(ctx context.Context) error { if err != nil { return fmt.Errorf("listen on %s: %w", addr, err) } + defer func() { + _ = ln.Close() + l.closeConnections() + l.wg.Wait() + }() l.log.Info("RegionIO listening", "addr", addr, "version", "26.1.2") // Close the listener when the context is cancelled to unblock Accept. @@ -48,12 +57,34 @@ func (l *Listener) ListenAndServe(ctx context.Context) error { l.log.Warn("accept failed", "err", err) continue } + l.mu.Lock() + l.conns[raw] = struct{}{} + l.wg.Add(1) + l.mu.Unlock() go l.serveConn(raw) } } +func (l *Listener) closeConnections() { + l.mu.Lock() + conns := make([]net.Conn, 0, len(l.conns)) + for conn := range l.conns { + conns = append(conns, conn) + } + l.mu.Unlock() + for _, conn := range conns { + _ = conn.Close() + } +} + // serveConn wraps a raw connection and runs its state-machine handler. func (l *Listener) serveConn(raw net.Conn) { + defer func() { + l.mu.Lock() + delete(l.conns, raw) + l.mu.Unlock() + l.wg.Done() + }() conn := NewConn(raw) h := &handler{ conn: conn, diff --git a/internal/network/multiplayer_test.go b/internal/network/multiplayer_test.go new file mode 100644 index 0000000..13bbae9 --- /dev/null +++ b/internal/network/multiplayer_test.go @@ -0,0 +1,321 @@ +package network + +import ( + "bufio" + "bytes" + "io" + "log/slog" + "net" + "sync" + "testing" + "time" + + "regionio/internal/protocol" + "regionio/internal/server" + "regionio/internal/world" +) + +type recordingConn struct { + mu sync.Mutex + buf bytes.Buffer +} + +func (c *recordingConn) Read([]byte) (int, error) { return 0, io.EOF } +func (c *recordingConn) Close() error { return nil } +func (c *recordingConn) LocalAddr() net.Addr { return testAddr("local") } +func (c *recordingConn) RemoteAddr() net.Addr { return testAddr("remote") } +func (c *recordingConn) SetDeadline(time.Time) error { return nil } +func (c *recordingConn) SetReadDeadline(time.Time) error { return nil } +func (c *recordingConn) SetWriteDeadline(time.Time) error { return nil } +func (c *recordingConn) Write(p []byte) (int, error) { + c.mu.Lock() + defer c.mu.Unlock() + return c.buf.Write(p) +} + +func (c *recordingConn) take(t *testing.T) []protocol.Packet { + t.Helper() + c.mu.Lock() + raw := append([]byte(nil), c.buf.Bytes()...) + c.buf.Reset() + c.mu.Unlock() + + reader := bytes.NewReader(raw) + br := bufio.NewReader(reader) + var packets []protocol.Packet + for br.Buffered() > 0 || reader.Len() > 0 { + pkt, err := protocol.ReadPacket(br, -1) + if err != nil { + t.Fatalf("decode recorded packet: %v", err) + } + packets = append(packets, pkt) + } + return packets +} + +type testAddr string + +func (a testAddr) Network() string { return "test" } +func (a testAddr) String() string { return string(a) } + +func TestIntegrationTwoPlayersShareBlockAndChatUpdates(t *testing.T) { + cfg := server.DefaultConfig() + cfg.WorldDir = "" + cfg.MaxPlayers = 4 + cache := world.NewCache(-1, world.GenerateFlat) + srv, err := server.NewWithCache(cfg, cache) + if err != nil { + t.Fatal(err) + } + + raw1, raw2 := &recordingConn{}, &recordingConn{} + h1 := &handler{conn: NewConn(raw1), srv: srv, log: slog.Default()} + h2 := &handler{conn: NewConn(raw2), srv: srv, log: slog.Default()} + h1.conn.Profile = server.Profile{Name: "Alice", UUID: server.OfflineUUID("Alice")} + h2.conn.Profile = server.Profile{Name: "Bob", UUID: server.OfflineUUID("Bob")} + h1.session, err = srv.RegisterPlayer(h1.conn.Profile, h1.conn.Send) + if err != nil { + t.Fatal(err) + } + defer srv.UnregisterPlayer(h1.session) + h2.session, err = srv.RegisterPlayer(h2.conn.Profile, h2.conn.Send) + if err != nil { + t.Fatal(err) + } + defer srv.UnregisterPlayer(h2.session) + + action := protocol.NewWriter(24) + action.VarInt(0).Position(2, world.FlatSurfaceY, 3).Byte(1).VarInt(17) + if err := h1.handlePlayerAction(protocol.Packet{ID: protocol.PlayPlayerAction, Data: action.Bytes()}); err != nil { + t.Fatal(err) + } + if got := cache.GetBlock(2, world.FlatSurfaceY, 3); got != world.StateAir { + t.Fatalf("shared block state = %d, want air", got) + } + assertPacketIDs(t, raw1.take(t), protocol.PlayBlockUpdate, protocol.PlayLightUpdate, protocol.PlayBlockChangedAck) + assertPacketIDs(t, raw2.take(t), protocol.PlayBlockUpdate, protocol.PlayLightUpdate) + + chat := protocol.NewWriter(16).String("hello") + if err := h1.handleChat(protocol.Packet{ID: protocol.PlayChatMessage, Data: chat.Bytes()}); err != nil { + t.Fatal(err) + } + assertPacketIDs(t, raw1.take(t), protocol.PlaySystemChat) + assertPacketIDs(t, raw2.take(t), protocol.PlaySystemChat) +} + +func TestBoundaryEditBroadcastsEveryChangedLightChunk(t *testing.T) { + cfg := server.DefaultConfig() + cfg.WorldDir = "" + cache := world.NewCache(-1, func(cx, cz int32) *world.Chunk { + return world.NewChunk(cx, cz, world.BiomePlains) + }) + if _, err := cache.FrameErr(0, 0); err != nil { + t.Fatal(err) + } + if _, err := cache.FrameErr(1, 0); err != nil { + t.Fatal(err) + } + srv, err := server.NewWithCache(cfg, cache) + if err != nil { + t.Fatal(err) + } + recorder := &recordingConn{} + h := &handler{conn: NewConn(recorder), srv: srv, log: slog.Default()} + h.conn.Profile = server.Profile{Name: "Alice", UUID: server.OfflineUUID("Alice")} + h.session, err = srv.RegisterPlayer(h.conn.Profile, h.conn.Send) + if err != nil { + t.Fatal(err) + } + defer srv.UnregisterPlayer(h.session) + srv.SetPlayerTransform(h.session, 15, 100, 8, 0, 0, true) + srv.SetPlayerViewDistance(h.session, 2) + + valid, lightChunks := cache.SetBlockWithLight(15, 100, 8, world.StateGlowstone) + if !valid || len(lightChunks) != 2 { + t.Fatalf("boundary edit valid=%v light chunks=%v; want two", valid, lightChunks) + } + h.broadcastBlockUpdate(15, 100, 8, world.StateGlowstone, lightChunks) + assertPacketIDs(t, recorder.take(t), protocol.PlayBlockUpdate, protocol.PlayLightUpdate, protocol.PlayLightUpdate) +} + +func TestIntegrationFourClientsVisibilityMovementLeaveAndLight(t *testing.T) { + cfg := server.DefaultConfig() + cfg.WorldDir = "" + cfg.MaxPlayers = 4 + cache := world.NewCache(-1, world.GenerateFlat) + srv, err := server.NewWithCache(cfg, cache) + if err != nil { + t.Fatal(err) + } + + names := []string{"Alice", "Bob", "Carol", "Dave"} + positions := [][3]float64{{0, 80, 0}, {16, 80, 0}, {160, 80, 0}, {176, 80, 0}} + handlers := make([]*handler, len(names)) + recorders := make([]*recordingConn, len(names)) + for i, name := range names { + recorders[i] = &recordingConn{} + h := &handler{conn: NewConn(recorders[i]), srv: srv, log: slog.Default(), viewDistance: 2} + h.conn.Profile = server.Profile{Name: name, UUID: server.OfflineUUID(name)} + h.session, err = srv.RegisterPlayer(h.conn.Profile, h.conn.Send) + if err != nil { + t.Fatalf("register %s: %v", name, err) + } + srv.SetPlayerTransform(h.session, positions[i][0], positions[i][1], positions[i][2], 0, 0, true) + srv.SetPlayerViewDistance(h.session, h.viewDistance) + handlers[i] = h + } + defer func() { + for _, h := range handlers { + srv.UnregisterPlayer(h.session) + } + }() + + mobID := srv.Entities().Add(&world.Entity{ + TypeID: 100, TypeName: "minecraft:pig", X: 8, Y: 80, Z: 8, + }) + for _, h := range handlers { + if err := h.syncVisibleEntities(); err != nil { + t.Fatal(err) + } + } + + for i, recorder := range recorders { + packets := recorder.take(t) + if got := countPackets(packets, protocol.PlayPlayerInfoUpdate); got != 4 { + t.Fatalf("client %s player-info count = %d, want 4", names[i], got) + } + wantAdds := 1 + if i < 2 { + wantAdds = 2 // nearby player plus the pig + } + if got := countPackets(packets, protocol.PlayAddEntity); got != wantAdds { + t.Fatalf("client %s add-entity count = %d, want %d", names[i], got, wantAdds) + } + } + + move := protocol.NewWriter(40) + move.Float64(32).Float64(80).Float64(0).Float32(90).Float32(15).Byte(1) + if err := handlers[1].handlePlay(protocol.Packet{ID: protocol.PlayMovePosRot, Data: move.Bytes()}); err != nil { + t.Fatal(err) + } + if err := handlers[0].syncVisibleEntities(); err != nil { + t.Fatal(err) + } + packets := recorders[0].take(t) + if got := countPackets(packets, protocol.PlayTeleportEntity); got != 1 { + t.Fatalf("nearby movement teleports = %d, want 1", got) + } + assertTeleportTransform(t, firstPacket(t, packets, protocol.PlayTeleportEntity), handlers[1].session.EntityID, 32, 80, 0, 90, 15, true) + + move = protocol.NewWriter(32) + move.Float64(64).Float64(80).Float64(0).Byte(1) + if err := handlers[1].handlePlay(protocol.Packet{ID: protocol.PlayMovePos, Data: move.Bytes()}); err != nil { + t.Fatal(err) + } + if err := handlers[0].syncVisibleEntities(); err != nil { + t.Fatal(err) + } + assertPacketIDs(t, recorders[0].take(t), protocol.PlayRemoveEntities) + + move = protocol.NewWriter(32) + move.Float64(16).Float64(80).Float64(0).Byte(1) + if err := handlers[1].handlePlay(protocol.Packet{ID: protocol.PlayMovePos, Data: move.Bytes()}); err != nil { + t.Fatal(err) + } + if err := handlers[0].syncVisibleEntities(); err != nil { + t.Fatal(err) + } + assertPacketIDs(t, recorders[0].take(t), protocol.PlayAddEntity) + + srv.UnregisterPlayer(handlers[1].session) + if err := handlers[0].syncVisibleEntities(); err != nil { + t.Fatal(err) + } + assertPacketIDs(t, recorders[0].take(t), protocol.PlayRemoveEntities, protocol.PlayPlayerInfoRemove) + + action := protocol.NewWriter(24) + action.VarInt(0).Position(2, world.FlatSurfaceY, 3).Byte(1).VarInt(91) + if err := handlers[0].handlePlayerAction(protocol.Packet{ID: protocol.PlayPlayerAction, Data: action.Bytes()}); err != nil { + t.Fatal(err) + } + assertPacketIDs(t, recorders[0].take(t), protocol.PlayBlockUpdate, protocol.PlayLightUpdate, protocol.PlayBlockChangedAck) + if packets := recorders[2].take(t); len(packets) != 0 { + t.Fatalf("far client Carol received %d block/light packets", len(packets)) + } + if packets := recorders[3].take(t); len(packets) != 0 { + t.Fatalf("far client Dave received %d block/light packets", len(packets)) + } + + srv.Entities().Remove(mobID) +} + +func countPackets(packets []protocol.Packet, id int32) int { + count := 0 + for _, packet := range packets { + if packet.ID == id { + count++ + } + } + return count +} + +func firstPacket(t *testing.T, packets []protocol.Packet, id int32) protocol.Packet { + t.Helper() + for _, packet := range packets { + if packet.ID == id { + return packet + } + } + t.Fatalf("packet %#x not found", id) + return protocol.Packet{} +} + +func assertTeleportTransform(t *testing.T, packet protocol.Packet, entityID int32, x, y, z float64, yaw, pitch float32, onGround bool) { + t.Helper() + r := packet.Body() + if got, err := r.VarInt(); err != nil || got != entityID { + t.Fatalf("teleport entity = %d, %v; want %d", got, err, entityID) + } + for _, field := range []struct { + name string + want float64 + }{{"x", x}, {"y", y}, {"z", z}} { + got, err := r.Float64() + if err != nil || got != field.want { + t.Fatalf("teleport %s = %v, %v; want %v", field.name, got, err, field.want) + } + } + for i := 0; i < 3; i++ { + if got, err := r.Float64(); err != nil || got != 0 { + t.Fatalf("teleport velocity[%d] = %v, %v; want 0", i, got, err) + } + } + if got, err := r.Float32(); err != nil || got != yaw { + t.Fatalf("teleport yaw = %v, %v; want %v", got, err, yaw) + } + if got, err := r.Float32(); err != nil || got != pitch { + t.Fatalf("teleport pitch = %v, %v; want %v", got, err, pitch) + } + if _, err := r.Int32(); err != nil { + t.Fatal(err) + } + if got, err := r.Bool(); err != nil || got != onGround { + t.Fatalf("teleport onGround = %v, %v; want %v", got, err, onGround) + } +} + +func assertPacketIDs(t *testing.T, packets []protocol.Packet, want ...int32) { + t.Helper() + if len(packets) != len(want) { + ids := make([]int32, len(packets)) + for i := range packets { + ids[i] = packets[i].ID + } + t.Fatalf("packet IDs = %v (count %d), want %v (count %d)", ids, len(packets), want, len(want)) + } + for i := range want { + if packets[i].ID != want[i] { + t.Fatalf("packet[%d] id = %#x, want %#x", i, packets[i].ID, want[i]) + } + } +} diff --git a/internal/network/play.go b/internal/network/play.go index b82660b..5a87065 100644 --- a/internal/network/play.go +++ b/internal/network/play.go @@ -1,12 +1,14 @@ package network import ( + "errors" "math" "time" "regionio/internal/nbt" "regionio/internal/protocol" "regionio/internal/registry" + "regionio/internal/server" "regionio/internal/world" ) @@ -22,6 +24,13 @@ const ( // hands chunk streaming off to the background streamer. The streamer stops when // h.ctx (the connection lifetime context) is cancelled. func (h *handler) beginPlay() error { + session, err := h.srv.RegisterPlayer(h.conn.Profile, h.conn.Send) + if err != nil { + return err + } + h.session = session + h.srv.SetPlayerTransform(session, spawnX, spawnY, spawnZ, 0, 0, true) + h.srv.SetPlayerViewDistance(session, h.visibilityRadius()) for i := range h.hotbar { h.hotbar[i] = -1 // empty } @@ -36,6 +45,15 @@ func (h *handler) beginPlay() error { if err := h.sendPlayerPosition(1); err != nil { return err } + if err := h.sendChunkCacheCenter(0, 0); err != nil { + return err + } + if err := h.sendDefaultSpawnPosition(); err != nil { + return err + } + if err := h.sendPlayerAbilities(); err != nil { + return err + } // Launch the background chunk streamer. It owns generation + sending so the // read loop stays free; requestRecenter is a non-blocking push. h.streamer = newStreamer(h.srv.Chunks(), h.conn, h.log, h.viewDistance) @@ -46,10 +64,38 @@ func (h *handler) beginPlay() error { return nil } +func (h *handler) sendChunkCacheCenter(cx, cz int32) error { + w := protocol.NewWriter(8) + w.VarInt(cx) + w.VarInt(cz) + return h.conn.SendWriter(protocol.PlayChunkCacheCenter, w) +} + +func (h *handler) sendDefaultSpawnPosition() error { + w := protocol.NewWriter(12) + w.Position(8, 100, 8) + w.Float32(0.0) + return h.conn.SendWriter(protocol.PlayDefaultSpawnPos, w) +} + +func (h *handler) sendPlayerAbilities() error { + w := protocol.NewWriter(9) + w.Byte(0x0F) + w.Float32(0.05) + w.Float32(0.1) + return h.conn.SendWriter(protocol.PlayAbilities, w) +} + // onPlayerMove recenters the streamer when the player crosses into a new chunk. // It is a non-blocking push; the read loop never waits on generation. -func (h *handler) onPlayerMove(x, y, z float64) error { - h.srv.SetPlayerPosition(h.conn.Profile.Name, x, y, z) +func (h *handler) onPlayerMove(x, y, z float64, yaw, pitch float32, onGround bool) error { + if math.IsNaN(x) || math.IsNaN(y) || math.IsNaN(z) || + math.IsInf(x, 0) || math.IsInf(y, 0) || math.IsInf(z, 0) || + math.IsNaN(float64(yaw)) || math.IsNaN(float64(pitch)) || + math.IsInf(float64(yaw), 0) || math.IsInf(float64(pitch), 0) { + return errors.New("invalid player position") + } + h.srv.SetPlayerTransform(h.session, x, y, z, yaw, pitch, onGround) cx := int32(int64(math.Floor(x)) >> 4) cz := int32(int64(math.Floor(z)) >> 4) if h.streamer != nil { @@ -58,6 +104,16 @@ func (h *handler) onPlayerMove(x, y, z float64) error { return nil } +func (h *handler) visibilityRadius() int { + if h.viewDistance < 2 { + return defaultViewRadius + } + if h.viewDistance > 16 { + return 16 + } + return h.viewDistance +} + // sendPlayLogin writes the clientbound play "login" packet. Field layout was // confirmed against the 26.1.2 vanilla server capture. func (h *handler) sendPlayLogin() error { @@ -67,8 +123,12 @@ func (h *handler) sendPlayLogin() error { } w := protocol.NewWriter(128) - w.Int32(1) // entity ID - w.Bool(false) // is hardcore + entityID := int32(1) + if h.session != nil { + entityID = h.session.EntityID + } + w.Int32(entityID) // entity ID + w.Bool(false) // is hardcore // Dimension names: the worlds available on this server. dims := []string{"minecraft:overworld", "minecraft:the_end", "minecraft:the_nether"} @@ -78,23 +138,23 @@ func (h *handler) sendPlayLogin() error { } w.VarInt(int32(h.srv.Config().MaxPlayers)) // max players (legacy) - w.VarInt(10) // view distance - w.VarInt(10) // simulation distance + w.VarInt(int32(h.visibilityRadius())) // view distance + w.VarInt(int32(h.visibilityRadius())) // simulation distance w.Bool(false) // reduced debug info w.Bool(true) // enable respawn screen w.Bool(false) // do limited crafting - w.VarInt(int32(dimTypeIdx)) // dimension type (registry index) + w.VarInt(int32(dimTypeIdx)) // dimension type (registry index) w.String("minecraft:overworld") // dimension name (this world) - w.Int64(0) // hashed seed - w.Byte(1) // game mode: creative (instant break, creative inventory) - w.Byte(0xFF) // previous game mode: -1 (none) - w.Bool(false) // is debug - w.Bool(false) // is flat - w.Bool(false) // has death location - w.VarInt(0) // portal cooldown - w.VarInt(63) // sea level (overworld) - w.Bool(false) // enforces secure chat + w.Int64(0) // hashed seed + w.Byte(1) // game mode: creative (instant break, creative inventory) + w.Byte(0xFF) // previous game mode: -1 (none) + w.Bool(false) // is debug + w.Bool(false) // is flat + w.Bool(false) // has death location + w.VarInt(0) // portal cooldown + w.VarInt(63) // sea level (overworld) + w.Bool(false) // enforces secure chat return h.conn.SendWriter(protocol.PlayLogin, w) } @@ -125,50 +185,174 @@ func (h *handler) sendPlayerPosition(teleportID int32) error { func (h *handler) keepAliveLoop() { ticker := time.NewTicker(15 * time.Second) defer ticker.Stop() - for range ticker.C { - id := time.Now().UnixMilli() - w := protocol.NewWriter(8) - w.Int64(id) - if err := h.conn.SendWriter(protocol.PlayKeepAliveCB, w); err != nil { + for { + select { + case <-h.ctx.Done(): return + case <-ticker.C: + id := time.Now().UnixMilli() + w := protocol.NewWriter(8) + w.Int64(id) + if err := h.conn.SendWriter(protocol.PlayKeepAliveCB, w); err != nil { + return + } } } } -// entitySyncLoop periodically sends all entities in the world to the client. -// In a real server this would track which entities the player can see and send updates. +// visibleEntity is the comparable state retained by one client's visibility +// tracker. OnGround is separate because mobs currently always use true. +type visibleEntity struct { + entity world.Entity + onGround bool +} + +// entitySyncLoop maintains tab-list membership and chunk-scoped entity state. func (h *handler) entitySyncLoop() { ticker := time.NewTicker(200 * time.Millisecond) defer ticker.Stop() - known := make(map[int32]bool) for { select { case <-h.ctx.Done(): return case <-ticker.C: - all := h.srv.Entities().All() - current := make(map[int32]bool) - for _, e := range all { - current[e.ID] = true - if !known[e.ID] { - h.sendAddEntity(e) - known[e.ID] = true - } else { - h.sendEntityTeleport(e) - } - } - // remove entities that disappeared - for id := range known { - if !current[id] { - h.sendRemoveEntity(id) - delete(known, id) - } + if err := h.syncVisibleEntities(); err != nil { + return } } } } +// syncVisibleEntities performs one deterministic visibility pass. Keeping it +// separate from the ticker makes the four-client workflow integration-testable. +func (h *handler) syncVisibleEntities() error { + if h.session == nil { + return nil + } + if h.knownPlayers == nil { + h.knownPlayers = make(map[[16]byte]bool) + } + if h.knownEntities == nil { + h.knownEntities = make(map[int32]visibleEntity) + } + + viewer := h.session.Snapshot() + players := h.srv.PlayerSnapshots() + currentPlayers := make(map[[16]byte]bool, len(players)) + for _, player := range players { + currentPlayers[player.Profile.UUID] = true + if !h.knownPlayers[player.Profile.UUID] { + if err := h.sendPlayerInfoAdd(player.Profile); err != nil { + return err + } + h.knownPlayers[player.Profile.UUID] = true + } + } + + currentEntities := make(map[int32]visibleEntity) + for _, player := range players { + if player.EntityID == viewer.EntityID || !playerVisible(viewer, player, h.visibilityRadius()) { + continue + } + currentEntities[player.EntityID] = visibleEntity{ + entity: world.Entity{ + ID: player.EntityID, UUID: player.Profile.UUID, + TypeID: registry.EntityTypeIndex("minecraft:player"), TypeName: "minecraft:player", + X: player.X, Y: player.Y, Z: player.Z, + Yaw: player.Yaw, Pitch: player.Pitch, HeadYaw: player.Yaw, + }, + onGround: player.OnGround, + } + } + for _, entity := range h.srv.Entities().All() { + if entityVisible(viewer, entity, h.visibilityRadius()) { + currentEntities[entity.ID] = visibleEntity{entity: entity, onGround: true} + } + } + + for id, current := range currentEntities { + known, exists := h.knownEntities[id] + if !exists { + entity := current.entity + if err := h.sendAddEntity(&entity); err != nil { + return err + } + } else if known != current { + entity := current.entity + if err := h.sendEntityTeleportState(&entity, current.onGround); err != nil { + return err + } + } + } + for id := range h.knownEntities { + if _, visible := currentEntities[id]; !visible { + if err := h.sendRemoveEntity(id); err != nil { + return err + } + } + } + h.knownEntities = currentEntities + + for uuid := range h.knownPlayers { + if !currentPlayers[uuid] { + if err := h.sendPlayerInfoRemove(uuid); err != nil { + return err + } + delete(h.knownPlayers, uuid) + } + } + return nil +} + +func playerVisible(viewer, target server.PlayerSnapshot, radius int) bool { + return chunksWithin(viewer.X, viewer.Z, target.X, target.Z, radius) +} + +func entityVisible(viewer server.PlayerSnapshot, target world.Entity, radius int) bool { + return chunksWithin(viewer.X, viewer.Z, target.X, target.Z, radius) +} + +func chunksWithin(ax, az, bx, bz float64, radius int) bool { + acx := int32(int64(math.Floor(ax)) >> 4) + acz := int32(int64(math.Floor(az)) >> 4) + bcx := int32(int64(math.Floor(bx)) >> 4) + bcz := int32(int64(math.Floor(bz)) >> 4) + dx := acx - bcx + if dx < 0 { + dx = -dx + } + dz := acz - bcz + if dz < 0 { + dz = -dz + } + return dx <= int32(radius) && dz <= int32(radius) +} + +func (h *handler) sendPlayerInfoAdd(profile server.Profile) error { + w := protocol.NewWriter(64) + w.Byte(0xff) // All eight initialization actions, fixed 8-bit EnumSet. + w.VarInt(1) + w.UUID(profile.UUID) + w.String(profile.Name) + w.VarInt(0) // profile properties + w.Bool(false) // no signed chat session + w.VarInt(1) // creative game mode + w.Bool(true) // listed + w.VarInt(0) // latency + w.Bool(false) // no custom display name + w.VarInt(0) // list order + w.Bool(true) // show hat + return h.conn.SendWriter(protocol.PlayPlayerInfoUpdate, w) +} + +func (h *handler) sendPlayerInfoRemove(uuid [16]byte) error { + w := protocol.NewWriter(20) + w.VarInt(1) + w.UUID(uuid) + return h.conn.SendWriter(protocol.PlayPlayerInfoRemove, w) +} + // sendAddEntity sends the minecraft:add_entity packet. func (h *handler) sendAddEntity(e *world.Entity) error { w := protocol.NewWriter(64) @@ -187,12 +371,21 @@ func (h *handler) sendAddEntity(e *world.Entity) error { } func (h *handler) sendEntityTeleport(e *world.Entity) error { + return h.sendEntityTeleportState(e, true) +} + +func (h *handler) sendEntityTeleportState(e *world.Entity, onGround bool) error { w := protocol.NewWriter(64) w.VarInt(e.ID) + // PositionMoveRotation: position, deltaMovement, yRot, xRot. w.Float64(e.X).Float64(e.Y).Float64(e.Z) - w.Byte(byte(e.Yaw * 256.0 / 360.0)) - w.Byte(byte(e.Pitch * 256.0 / 360.0)) - w.Bool(true) // On ground + w.Float64(float64(e.VelocityX) / 8000.0) + w.Float64(float64(e.VelocityY) / 8000.0) + w.Float64(float64(e.VelocityZ) / 8000.0) + w.Float32(e.Yaw) + w.Float32(e.Pitch) + w.Int32(0) // Relative.SET_STREAM_CODEC uses ByteBufCodecs.INT; no relative flags. + w.Bool(onGround) return h.conn.SendWriter(protocol.PlayTeleportEntity, w) } @@ -225,7 +418,6 @@ func (h *handler) handlePlay(pkt protocol.Packet) error { return nil case protocol.PlayMovePos, protocol.PlayMovePosRot: - // Both packets begin with the absolute X, Y, Z position. r := pkt.Body() x, err := r.Float64() if err != nil { @@ -239,7 +431,48 @@ func (h *handler) handlePlay(pkt protocol.Packet) error { if err != nil { return err } - return h.onPlayerMove(x, y, z) + snapshot := h.session.Snapshot() + yaw, pitch := snapshot.Yaw, snapshot.Pitch + if pkt.ID == protocol.PlayMovePosRot { + yaw, err = r.Float32() + if err != nil { + return err + } + pitch, err = r.Float32() + if err != nil { + return err + } + } + flags, err := r.ReadByte() + if err != nil { + return err + } + return h.onPlayerMove(x, y, z, yaw, pitch, flags&1 != 0) + + case protocol.PlayMoveRot: + r := pkt.Body() + yaw, err := r.Float32() + if err != nil { + return err + } + pitch, err := r.Float32() + if err != nil { + return err + } + flags, err := r.ReadByte() + if err != nil { + return err + } + snapshot := h.session.Snapshot() + return h.onPlayerMove(snapshot.X, snapshot.Y, snapshot.Z, yaw, pitch, flags&1 != 0) + + case protocol.PlayMoveStatusOnly: + flags, err := pkt.Body().ReadByte() + if err != nil { + return err + } + snapshot := h.session.Snapshot() + return h.onPlayerMove(snapshot.X, snapshot.Y, snapshot.Z, snapshot.Yaw, snapshot.Pitch, flags&1 != 0) case protocol.PlayPlayerAction: return h.handlePlayerAction(pkt) @@ -279,16 +512,15 @@ func (h *handler) handleChat(pkt protocol.Packet) error { } line := "<" + h.conn.Profile.Name + "> " + msg h.log.Info("chat", "msg", line) - return h.sendSystemChat(line) + return h.broadcastSystemChat(line) } -// sendSystemChat sends a plain-text system chat message. The text component is -// network NBT; a bare string tag is the shorthand for {"text": ...}. -func (h *handler) sendSystemChat(text string) error { +func (h *handler) broadcastSystemChat(text string) error { w := protocol.NewWriter(len(text) + 8) w.Raw(nbt.Marshal(nbt.String(text))) - w.Bool(false) // not an action-bar overlay - return h.conn.SendWriter(protocol.PlaySystemChat, w) + w.Bool(false) + h.srv.Broadcast(protocol.PlaySystemChat, w.Bytes()) + return nil } // handlePlayerAction processes digging. In creative the client sends @@ -315,10 +547,8 @@ func (h *handler) handlePlayerAction(pkt protocol.Packet) error { const startDig, finishDig = 0, 2 if status == startDig || status == finishDig { - if h.srv.Chunks().SetBlock(x, y, z, world.StateAir) { - if err := h.sendBlockUpdate(x, y, z, world.StateAir); err != nil { - return err - } + if valid, lightChunks := h.srv.Chunks().SetBlockWithLight(x, y, z, world.StateAir); valid { + h.broadcastBlockUpdate(x, y, z, world.StateAir, lightChunks) h.log.Debug("block broken", "x", x, "y", y, "z", z) } } @@ -401,10 +631,8 @@ func (h *handler) handleUseItemOn(pkt protocol.Packet) error { if state, ok := h.heldBlock(); ok { off := faceOffsets[face] px, py, pz := x+off[0], y+off[1], z+off[2] - if h.srv.Chunks().SetBlock(px, py, pz, state) { - if err := h.sendBlockUpdate(px, py, pz, state); err != nil { - return err - } + if valid, lightChunks := h.srv.Chunks().SetBlockWithLight(px, py, pz, state); valid { + h.broadcastBlockUpdate(px, py, pz, state, lightChunks) h.log.Debug("block placed", "x", px, "y", py, "z", pz, "state", state) } } @@ -422,12 +650,18 @@ func (h *handler) heldBlock() (uint16, bool) { return world.ItemToBlock(itemID) } -// sendBlockUpdate notifies the client of a single block change. -func (h *handler) sendBlockUpdate(x, y, z int, state uint16) error { +func (h *handler) broadcastBlockUpdate(x, y, z int, state uint16, lightChunks []world.ChunkPos) { w := protocol.NewWriter(12) w.Position(x, y, z) w.VarInt(int32(state)) - return h.conn.SendWriter(protocol.PlayBlockUpdate, w) + cx := int32(x >> 4) + cz := int32(z >> 4) + h.srv.BroadcastChunk(cx, cz, protocol.PlayBlockUpdate, w.Bytes()) + for _, chunk := range lightChunks { + if light, err := h.srv.Chunks().LightUpdate(chunk.X, chunk.Z); err == nil { + h.srv.BroadcastChunk(chunk.X, chunk.Z, protocol.PlayLightUpdate, light) + } + } } // sendBlockChangedAck confirms a block-action sequence so the client does not diff --git a/internal/network/streamer.go b/internal/network/streamer.go index 9559382..288bbb7 100644 --- a/internal/network/streamer.go +++ b/internal/network/streamer.go @@ -6,6 +6,7 @@ import ( "runtime" "sync" + "regionio/internal/protocol" "regionio/internal/world" ) @@ -141,6 +142,8 @@ func (s *streamer) run(ctx context.Context) { func (s *streamer) processRecenter(ctx context.Context, cx, cz int32) { s.centerX, s.centerZ, s.hasCenter = cx, cz, true + s.sendChunkCacheCenter(cx, cz) + // Build the desired set: everything within genRadius (the union of what we // send + the pre-gen ring). Sent = within viewRadius; pre-gen = the ring. order := spiralOrder(cx, cz, s.genRadius) @@ -179,11 +182,32 @@ func (s *streamer) processRecenter(ctx context.Context, cx, cz int32) { // bounded and avoids re-sending. for key := range s.loaded { if !desired[key] { + s.sendForgetLevelChunk(key[0], key[1]) delete(s.loaded, key) } } } +func (s *streamer) sendChunkCacheCenter(cx, cz int32) { + if s.conn == nil { + return + } + w := protocol.NewWriter(8) + w.VarInt(cx) + w.VarInt(cz) + _ = s.conn.SendWriter(protocol.PlayChunkCacheCenter, w) +} + +func (s *streamer) sendForgetLevelChunk(cx, cz int32) { + if s.conn == nil { + return + } + w := protocol.NewWriter(8) + w.Int32(cz) + w.Int32(cx) + _ = s.conn.SendWriter(protocol.PlayForgetLevelChunk, w) +} + // parallelSend generates the given chunks across the worker pool and sends each // frame as soon as it is ready (order is best-effort; the client reassembles). // Already-loaded chunks are skipped. Returns when all are sent or ctx cancels. @@ -274,14 +298,15 @@ func (s *streamer) parallelGenerate(ctx context.Context, keys [][2]int32) { return default: } - _ = s.cache.Frame(j.cx, j.cz) // warm the cache; discard the frame + _, _ = s.cache.FrameErr(j.cx, j.cz) // warm the cache; discard the frame } }() } +Loop: for _, j := range pending { select { case <-ctx.Done(): - break + break Loop case jobs <- j: } } @@ -307,7 +332,14 @@ func (s *streamer) generateWorker(ctx context.Context, jobs <-chan frameJob, res return default: } - frame := s.cache.Frame(j.cx, j.cz) + frame, err := s.cache.FrameErr(j.cx, j.cz) + if err != nil { + select { + case results <- frameResult{cx: j.cx, cz: j.cz, err: err}: + case <-ctx.Done(): + } + return + } if err := s.conn.SendFramed(frame); err != nil { select { case results <- frameResult{cx: j.cx, cz: j.cz, err: err}: diff --git a/internal/protocol/buffer.go b/internal/protocol/buffer.go index c4b184d..e3035cd 100644 --- a/internal/protocol/buffer.go +++ b/internal/protocol/buffer.go @@ -70,6 +70,15 @@ func (r *Reader) Uint16() (uint16, error) { return binary.BigEndian.Uint16(b), nil } +// Int32 reads a big-endian signed int. +func (r *Reader) Int32() (int32, error) { + b, err := r.readN(4) + if err != nil { + return 0, err + } + return int32(binary.BigEndian.Uint32(b)), nil +} + // Int64 reads a big-endian signed long. func (r *Reader) Int64() (int64, error) { b, err := r.readN(8) @@ -119,9 +128,9 @@ func (r *Reader) Position() (x, y, z int, err error) { if err != nil { return 0, 0, 0, err } - x = int(v >> 38) // top 26 bits, sign-extended - y = int(v << 52 >> 52) // low 12 bits, sign-extended - z = int(v << 26 >> 38) // middle 26 bits, sign-extended + x = int(v >> 38) // top 26 bits, sign-extended + y = int(v << 52 >> 52) // low 12 bits, sign-extended + z = int(v << 26 >> 38) // middle 26 bits, sign-extended return x, y, z, nil } diff --git a/internal/protocol/ids.go b/internal/protocol/ids.go index 75d526f..04f0176 100644 --- a/internal/protocol/ids.go +++ b/internal/protocol/ids.go @@ -31,11 +31,11 @@ const ( // Login, clientbound. const ( - LoginDisconnectID = 0x00 - EncryptionRequest = 0x01 - LoginSuccessID = 0x02 - SetCompressionID = 0x03 - LoginPluginReq = 0x04 + LoginDisconnectID = 0x00 + EncryptionRequest = 0x01 + LoginSuccessID = 0x02 + SetCompressionID = 0x03 + LoginPluginReq = 0x04 CookieRequestLogin = 0x05 ) @@ -53,12 +53,12 @@ const ( // Configuration, clientbound. const ( - ConfigCookieRequest = 0x00 - ConfigCustomPayloadCB = 0x01 - ConfigDisconnect = 0x02 - ConfigFinishClientbound = 0x03 - ConfigKeepAliveCB = 0x04 - ConfigPing = 0x05 + ConfigCookieRequest = 0x00 + ConfigCustomPayloadCB = 0x01 + ConfigDisconnect = 0x02 + ConfigFinishClientbound = 0x03 + ConfigKeepAliveCB = 0x04 + ConfigPing = 0x05 ConfigRegistryData = 0x07 ConfigUpdateEnabledFeatures = 0x0c ConfigUpdateTags = 0x0d @@ -74,42 +74,46 @@ const ( PlayDefaultSpawnPos = 0x61 PlayChunkCacheCenter = 0x5e PlayLevelChunk = 0x2d + PlayForgetLevelChunk = 0x25 PlayAbilities = 0x40 PlaySetHeldSlot = 0x69 PlayDisconnect = 0x20 PlayBlockUpdate = 0x08 PlayBlockChangedAck = 0x04 PlaySystemChat = 0x79 + PlayLightUpdate = 0x30 + PlayPlayerInfoRemove = 0x45 + PlayPlayerInfoUpdate = 0x46 // Entity packets PlayAddEntity = 0x01 - PlayTeleportEntity = 0x7D + PlayTeleportEntity = 0x7d PlayMoveEntityPos = 0x35 PlayMoveEntityPosRot = 0x36 PlayMoveEntityRot = 0x38 PlaySetEntityMotion = 0x65 - PlayRemoveEntities = 0x4D + PlayRemoveEntities = 0x4d PlaySetEntityData = 0x63 PlaySetEquipment = 0x66 ) // Play, serverbound (protocol 775). const ( - PlayAcceptTeleport = 0x00 - PlayKeepAliveServer = 0x1c - PlayClientTickEnd = 0x0d - PlayClientInformation = 0x0e - PlayCustomPayload = 0x16 - PlayMovePos = 0x1e - PlayMovePosRot = 0x1f - PlayMoveRot = 0x20 - PlayMoveStatusOnly = 0x21 - PlayPlayerLoaded = 0x2c - PlayPlayerAction = 0x29 - PlayUseItemOn = 0x42 - PlaySetCreativeSlot = 0x38 - PlaySetCarriedItem = 0x35 - PlayChatMessage = 0x09 + PlayAcceptTeleport = 0x00 + PlayKeepAliveServer = 0x1c + PlayClientTickEnd = 0x0d + PlayClientInformation = 0x0e + PlayCustomPayload = 0x16 + PlayMovePos = 0x1e + PlayMovePosRot = 0x1f + PlayMoveRot = 0x20 + PlayMoveStatusOnly = 0x21 + PlayPlayerLoaded = 0x2c + PlayPlayerAction = 0x29 + PlayUseItemOn = 0x42 + PlaySetCreativeSlot = 0x38 + PlaySetCarriedItem = 0x35 + PlayChatMessage = 0x09 ) // GameEvent sub-IDs carried by the clientbound game_event packet. diff --git a/internal/protocol/ids_test.go b/internal/protocol/ids_test.go new file mode 100644 index 0000000..735e03d --- /dev/null +++ b/internal/protocol/ids_test.go @@ -0,0 +1,117 @@ +package protocol + +import "testing" + +func TestPlayPacketIDsProtocol775(t *testing.T) { + clientbound := map[string]int32{ + "PlayAddEntity": PlayAddEntity, + "PlayBlockChangedAck": PlayBlockChangedAck, + "PlayBlockUpdate": PlayBlockUpdate, + "PlayDisconnect": PlayDisconnect, + "PlayForgetLevelChunk": PlayForgetLevelChunk, + "PlayGameEvent": PlayGameEvent, + "PlayKeepAliveCB": PlayKeepAliveCB, + "PlayLevelChunk": PlayLevelChunk, + "PlayLogin": PlayLogin, + "PlayMoveEntityPos": PlayMoveEntityPos, + "PlayMoveEntityPosRot": PlayMoveEntityPosRot, + "PlayMoveEntityRot": PlayMoveEntityRot, + "PlayAbilities": PlayAbilities, + "PlayPlayerPosition": PlayPlayerPosition, + "PlayRemoveEntities": PlayRemoveEntities, + "PlayChunkCacheCenter": PlayChunkCacheCenter, + "PlayDefaultSpawnPos": PlayDefaultSpawnPos, + "PlaySetEntityData": PlaySetEntityData, + "PlaySetEntityMotion": PlaySetEntityMotion, + "PlaySetEquipment": PlaySetEquipment, + "PlaySetHeldSlot": PlaySetHeldSlot, + "PlaySystemChat": PlaySystemChat, + "PlayLightUpdate": PlayLightUpdate, + "PlayPlayerInfoRemove": PlayPlayerInfoRemove, + "PlayPlayerInfoUpdate": PlayPlayerInfoUpdate, + "PlayTeleportEntity": PlayTeleportEntity, + } + wantClientbound := map[string]int32{ + "PlayAddEntity": 0x01, + "PlayBlockChangedAck": 0x04, + "PlayBlockUpdate": 0x08, + "PlayDisconnect": 0x20, + "PlayForgetLevelChunk": 0x25, + "PlayGameEvent": 0x26, + "PlayKeepAliveCB": 0x2c, + "PlayLevelChunk": 0x2d, + "PlayLogin": 0x31, + "PlayMoveEntityPos": 0x35, + "PlayMoveEntityPosRot": 0x36, + "PlayMoveEntityRot": 0x38, + "PlayAbilities": 0x40, + "PlayPlayerPosition": 0x48, + "PlayRemoveEntities": 0x4d, + "PlayChunkCacheCenter": 0x5e, + "PlayDefaultSpawnPos": 0x61, + "PlaySetEntityData": 0x63, + "PlaySetEntityMotion": 0x65, + "PlaySetEquipment": 0x66, + "PlaySetHeldSlot": 0x69, + "PlaySystemChat": 0x79, + "PlayLightUpdate": 0x30, + "PlayPlayerInfoRemove": 0x45, + "PlayPlayerInfoUpdate": 0x46, + "PlayTeleportEntity": 0x7d, + } + assertIDs(t, "clientbound", clientbound, wantClientbound) + + serverbound := map[string]int32{ + "PlayAcceptTeleport": PlayAcceptTeleport, + "PlayKeepAliveServer": PlayKeepAliveServer, + "PlayClientTickEnd": PlayClientTickEnd, + "PlayClientInformation": PlayClientInformation, + "PlayCustomPayload": PlayCustomPayload, + "PlayMovePos": PlayMovePos, + "PlayMovePosRot": PlayMovePosRot, + "PlayMoveRot": PlayMoveRot, + "PlayMoveStatusOnly": PlayMoveStatusOnly, + "PlayPlayerLoaded": PlayPlayerLoaded, + "PlayPlayerAction": PlayPlayerAction, + "PlayUseItemOn": PlayUseItemOn, + "PlaySetCreativeSlot": PlaySetCreativeSlot, + "PlaySetCarriedItem": PlaySetCarriedItem, + "PlayChatMessage": PlayChatMessage, + } + wantServerbound := map[string]int32{ + "PlayAcceptTeleport": 0x00, + "PlayKeepAliveServer": 0x1c, + "PlayClientTickEnd": 0x0d, + "PlayClientInformation": 0x0e, + "PlayCustomPayload": 0x16, + "PlayMovePos": 0x1e, + "PlayMovePosRot": 0x1f, + "PlayMoveRot": 0x20, + "PlayMoveStatusOnly": 0x21, + "PlayPlayerLoaded": 0x2c, + "PlayPlayerAction": 0x29, + "PlayUseItemOn": 0x42, + "PlaySetCreativeSlot": 0x38, + "PlaySetCarriedItem": 0x35, + "PlayChatMessage": 0x09, + } + assertIDs(t, "serverbound", serverbound, wantServerbound) +} + +func assertIDs(t *testing.T, direction string, got, want map[string]int32) { + t.Helper() + seen := make(map[int32]string, len(got)) + for name, gotID := range got { + wantID, ok := want[name] + if !ok { + t.Fatalf("%s %s missing expected ID", direction, name) + } + if gotID != wantID { + t.Fatalf("%s %s = %#x, want %#x", direction, name, gotID, wantID) + } + if previous, ok := seen[gotID]; ok { + t.Fatalf("%s duplicate packet ID %#x: %s and %s", direction, gotID, previous, name) + } + seen[gotID] = name + } +} diff --git a/internal/registry/entity_test.go b/internal/registry/entity_test.go new file mode 100644 index 0000000..7521f61 --- /dev/null +++ b/internal/registry/entity_test.go @@ -0,0 +1,25 @@ +package registry + +import "testing" + +func TestEntityTypeIndex(t *testing.T) { + tests := map[string]int{ + "minecraft:pig": 100, + "minecraft:player": 155, + "minecraft:zombie": 150, + } + for name, want := range tests { + if got := EntityTypeIndex(name); got != want { + t.Fatalf("EntityTypeIndex(%q) = %d, want %d", name, got, want) + } + } + if got := EntityTypeIndex("minecraft:not_a_real_entity"); got != -1 { + t.Fatalf("unknown entity type = %d, want -1", got) + } +} + +func TestEntityTypeIsNotSyncedRegistry(t *testing.T) { + if got := Index("minecraft:entity_type", "minecraft:pig"); got != -1 { + t.Fatalf("synced registry unexpectedly has entity_type pig = %d", got) + } +} diff --git a/internal/registry/registry.go b/internal/registry/registry.go index 103691d..ac9c771 100644 --- a/internal/registry/registry.go +++ b/internal/registry/registry.go @@ -39,11 +39,19 @@ type Registry struct { // synced is the parsed, ordered list loaded once at init. var synced []Registry +var syncedLookup map[string]map[string]int func init() { if err := json.Unmarshal(syncedJSON, &synced); err != nil { panic(fmt.Sprintf("registry: parsing embedded synced_registries.json: %v", err)) } + syncedLookup = make(map[string]map[string]int) + for _, reg := range synced { + syncedLookup[reg.Name] = make(map[string]int) + for i, e := range reg.Entries { + syncedLookup[reg.Name][e] = i + } + } } // Synced returns the ordered synchronized registries. The slice is shared and @@ -54,14 +62,9 @@ func Synced() []Registry { return synced } // which is the numeric (network) ID the client assigns it. It returns -1 if // the registry or entry is unknown. func Index(registryName, entry string) int { - for _, reg := range synced { - if reg.Name != registryName { - continue - } - for i, e := range reg.Entries { - if e == entry { - return i - } + if regMap, ok := syncedLookup[registryName]; ok { + if idx, ok := regMap[entry]; ok { + return idx } } return -1 @@ -77,3 +80,23 @@ type KnownPack struct { // CorePack is the vanilla built-in pack. Advertising it lets a matching client // supply registry contents from its own copy. var CorePack = KnownPack{Namespace: "minecraft", ID: "core", Version: "26.1.2"} + +// builtinEntityTypes contains the vanilla 26.1.2 BuiltInRegistries.ENTITY_TYPE +// network IDs needed by gameplay packets. This is intentionally separate from +// the synchronized registries above: minecraft:entity_type is a built-in +// registry in this protocol data set and is not sent in registry_data during +// configuration. +var builtinEntityTypes = map[string]int{ + "minecraft:pig": 100, + "minecraft:player": 155, + "minecraft:zombie": 150, +} + +// EntityTypeIndex returns the vanilla numeric ID for a built-in entity type, or +// -1 if it is not in the embedded table. +func EntityTypeIndex(name string) int { + if idx, ok := builtinEntityTypes[name]; ok { + return idx + } + return -1 +} diff --git a/internal/server/server.go b/internal/server/server.go index 2efad02..6c7e55d 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -4,6 +4,10 @@ package server import ( "encoding/json" + "errors" + "fmt" + "math" + "strings" "sync" "regionio/internal/protocol" @@ -57,31 +61,96 @@ type Server struct { store *world.Store // nil when persistence is disabled entities *world.EntityManager - // playerPos tracks the last known position of each player by name. - playerPos sync.Map // map[string][3]float64 + playersMu sync.RWMutex + players map[[16]byte]*PlayerSession + playerNames map[string][16]byte + nextPlayerID int32 } +// PacketSender is the connection capability retained by the session registry. +// The network package supplies Conn.Send without introducing an import cycle. +type PacketSender func(id int32, body []byte) error + +// PlayerSession is one active play-state client. Position is guarded separately +// so entity ticks can inspect players without holding the server registry lock. +type PlayerSession struct { + EntityID int32 + Profile Profile + send PacketSender + mu sync.RWMutex + position [3]float64 + yaw float32 + pitch float32 + onGround bool + viewDist int +} + +// PlayerSnapshot is an immutable view of one play-state session. +type PlayerSnapshot struct { + EntityID int32 + Profile Profile + X, Y, Z float64 + Yaw, Pitch float32 + OnGround bool + ViewDistance int +} + +var ( + ErrServerFull = errors.New("server: player limit reached") + ErrDuplicatePlayer = errors.New("server: player is already connected") +) + // New constructs a Server from cfg. When cfg.WorldDir is set, the world is // backed by an on-disk store under that directory; otherwise it is in-memory // only. A returned error (e.g. the world dir cannot be created) is fatal. func New(cfg Config) (*Server, error) { + if err := validateConfig(cfg); err != nil { + return nil, err + } gen := world.NewVanillaGenerator(cfg.WorldSeed) em := world.NewEntityManager() if cfg.WorldDir == "" { - // No persistence; keep eviction off too (flat/test worlds expect full - // presence). Real servers set WorldDir and MaxCachedChunks together. - return &Server{cfg: cfg, chunks: world.NewCache(int32(cfg.CompressionThreshold), gen), entities: em}, nil + return newServerState(cfg, + world.NewCacheWithLimit(int32(cfg.CompressionThreshold), gen, nil, cfg.MaxCachedChunks), nil, em), nil } - store, err := world.NewStore(cfg.WorldDir) + store, err := world.NewStoreForSeed(cfg.WorldDir, cfg.WorldSeed) if err != nil { return nil, err } + return newServerState(cfg, + world.NewCacheWithLimit(int32(cfg.CompressionThreshold), gen, store, cfg.MaxCachedChunks), store, em), nil +} + +// NewWithCache constructs a server around an existing cache. It is useful for +// embedding and integration tests that provide a specialized world generator. +func NewWithCache(cfg Config, chunks *world.Cache) (*Server, error) { + if err := validateConfig(cfg); err != nil { + return nil, err + } + if chunks == nil { + return nil, errors.New("server: nil chunk cache") + } + return newServerState(cfg, chunks, nil, world.NewEntityManager()), nil +} + +func validateConfig(cfg Config) error { + if cfg.Port < 1 || cfg.Port > 65535 { + return fmt.Errorf("server: port %d out of range", cfg.Port) + } + if cfg.MaxPlayers < 1 { + return fmt.Errorf("server: max players must be positive") + } + if cfg.MaxCachedChunks < 0 { + return fmt.Errorf("server: max cached chunks must not be negative") + } + return nil +} + +func newServerState(cfg Config, chunks *world.Cache, store *world.Store, entities *world.EntityManager) *Server { return &Server{ - cfg: cfg, - chunks: world.NewCacheWithLimit(int32(cfg.CompressionThreshold), gen, store, cfg.MaxCachedChunks), - store: store, - entities: em, - }, nil + cfg: cfg, chunks: chunks, store: store, entities: entities, + players: make(map[[16]byte]*PlayerSession), playerNames: make(map[string][16]byte), + } } // Config returns the active configuration. @@ -93,30 +162,202 @@ func (s *Server) Chunks() *world.Cache { return s.chunks } // Entities returns the shared entity manager. func (s *Server) Entities() *world.EntityManager { return s.entities } -// SetPlayerPosition updates the tracked position of a player. -func (s *Server) SetPlayerPosition(name string, x, y, z float64) { - s.playerPos.Store(name, [3]float64{x, y, z}) +// RegisterPlayer adds a profile to the active play-state registry. +func (s *Server) RegisterPlayer(profile Profile, send PacketSender) (*PlayerSession, error) { + s.playersMu.Lock() + defer s.playersMu.Unlock() + nameKey := strings.ToLower(profile.Name) + if _, exists := s.players[profile.UUID]; exists { + return nil, ErrDuplicatePlayer + } + if _, exists := s.playerNames[nameKey]; exists { + return nil, ErrDuplicatePlayer + } + if s.cfg.MaxPlayers > 0 && len(s.players) >= s.cfg.MaxPlayers { + return nil, ErrServerFull + } + s.nextPlayerID++ + session := &PlayerSession{ + EntityID: s.nextPlayerID, + Profile: profile, + send: send, + onGround: true, + viewDist: 4, + } + s.players[profile.UUID] = session + s.playerNames[nameKey] = profile.UUID + return session, nil } -// RemovePlayerPosition removes a player from tracking. -func (s *Server) RemovePlayerPosition(name string) { - s.playerPos.Delete(name) +// UnregisterPlayer removes exactly the supplied session. Pointer identity keeps +// a delayed disconnect from removing a future session with the same profile. +func (s *Server) UnregisterPlayer(session *PlayerSession) { + if session == nil { + return + } + s.playersMu.Lock() + defer s.playersMu.Unlock() + if current := s.players[session.Profile.UUID]; current == session { + delete(s.players, session.Profile.UUID) + delete(s.playerNames, strings.ToLower(session.Profile.Name)) + } +} + +// SetPlayerPosition updates a session's authoritative position snapshot. +func (s *Server) SetPlayerPosition(session *PlayerSession, x, y, z float64) { + if session == nil { + return + } + session.mu.Lock() + session.position = [3]float64{x, y, z} + session.mu.Unlock() +} + +// SetPlayerTransform updates all movement fields received from the client. +func (s *Server) SetPlayerTransform(session *PlayerSession, x, y, z float64, yaw, pitch float32, onGround bool) { + if session == nil { + return + } + session.mu.Lock() + session.position = [3]float64{x, y, z} + session.yaw = yaw + session.pitch = pitch + session.onGround = onGround + session.mu.Unlock() +} + +// SetPlayerViewDistance records the clamped chunk radius used for visibility. +func (s *Server) SetPlayerViewDistance(session *PlayerSession, distance int) { + if session == nil { + return + } + if distance < 2 { + distance = 4 + } + if distance > 16 { + distance = 16 + } + session.mu.Lock() + session.viewDist = distance + session.mu.Unlock() +} + +// Snapshot returns a consistent copy of this session's gameplay state. +func (session *PlayerSession) Snapshot() PlayerSnapshot { + if session == nil { + return PlayerSnapshot{} + } + session.mu.RLock() + snapshot := PlayerSnapshot{ + EntityID: session.EntityID, + Profile: session.Profile, + X: session.position[0], + Y: session.position[1], + Z: session.position[2], + Yaw: session.yaw, + Pitch: session.pitch, + OnGround: session.onGround, + ViewDistance: session.viewDist, + } + session.mu.RUnlock() + return snapshot +} + +// PlayerSnapshots returns consistent copies of all active play sessions. +func (s *Server) PlayerSnapshots() []PlayerSnapshot { + s.playersMu.RLock() + players := make([]*PlayerSession, 0, len(s.players)) + for _, player := range s.players { + players = append(players, player) + } + s.playersMu.RUnlock() + + snapshots := make([]PlayerSnapshot, 0, len(players)) + for _, player := range players { + snapshots = append(snapshots, player.Snapshot()) + } + return snapshots +} + +// PlayerCount returns the number of tracked players. +func (s *Server) PlayerCount() int { + s.playersMu.RLock() + defer s.playersMu.RUnlock() + return len(s.players) +} + +// Broadcast sends one already-encoded packet body to every active player. A +// failed recipient cannot make another player's gameplay handler fail. +func (s *Server) Broadcast(id int32, body []byte) { + s.playersMu.RLock() + senders := make([]PacketSender, 0, len(s.players)) + for _, player := range s.players { + senders = append(senders, player.send) + } + s.playersMu.RUnlock() + for _, send := range senders { + if send != nil { + _ = send(id, body) + } + } +} + +// BroadcastChunk sends a packet only to players whose chunk view contains the +// target chunk. It is used for block and light changes. +func (s *Server) BroadcastChunk(cx, cz int32, id int32, body []byte) { + s.playersMu.RLock() + players := make([]*PlayerSession, 0, len(s.players)) + for _, player := range s.players { + players = append(players, player) + } + s.playersMu.RUnlock() + + for _, player := range players { + snapshot := player.Snapshot() + pcx := int32(int64(math.Floor(snapshot.X)) >> 4) + pcz := int32(int64(math.Floor(snapshot.Z)) >> 4) + if chunkDistance(pcx, pcz, cx, cz) <= int32(snapshot.ViewDistance) && player.send != nil { + _ = player.send(id, body) + } + } +} + +func chunkDistance(ax, az, bx, bz int32) int32 { + dx := ax - bx + if dx < 0 { + dx = -dx + } + dz := az - bz + if dz < 0 { + dz = -dz + } + if dz > dx { + return dz + } + return dx } // NearestPlayer returns the position of the nearest player to (x, y, z). // Returns false if no players are online. func (s *Server) NearestPlayer(x, y, z float64) (pos [3]float64, ok bool) { minDist := float64(-1) - s.playerPos.Range(func(key, value any) bool { - p := value.([3]float64) + s.playersMu.RLock() + players := make([]*PlayerSession, 0, len(s.players)) + for _, player := range s.players { + players = append(players, player) + } + s.playersMu.RUnlock() + for _, player := range players { + player.mu.RLock() + p := player.position + player.mu.RUnlock() dist := (p[0]-x)*(p[0]-x) + (p[1]-y)*(p[1]-y) + (p[2]-z)*(p[2]-z) if minDist < 0 || dist < minDist { minDist = dist pos = p ok = true } - return true - }) + } return } diff --git a/internal/server/server_test.go b/internal/server/server_test.go new file mode 100644 index 0000000..3fde6c4 --- /dev/null +++ b/internal/server/server_test.go @@ -0,0 +1,130 @@ +package server + +import ( + "errors" + "sync" + "sync/atomic" + "testing" + + "regionio/internal/world" +) + +func TestPlayerRegistrySupportsFourPlayersAndEnforcesLimit(t *testing.T) { + cfg := DefaultConfig() + cfg.MaxPlayers = 4 + srv, err := NewWithCache(cfg, world.NewCache(-1, world.GenerateFlat)) + if err != nil { + t.Fatal(err) + } + + var sends atomic.Int32 + var sessions []*PlayerSession + for _, name := range []string{"Alice", "Bob", "Carol", "Dave"} { + profile := Profile{Name: name, UUID: OfflineUUID(name)} + session, err := srv.RegisterPlayer(profile, func(int32, []byte) error { + sends.Add(1) + return nil + }) + if err != nil { + t.Fatalf("register %s: %v", name, err) + } + sessions = append(sessions, session) + } + if got := srv.PlayerCount(); got != 4 { + t.Fatalf("player count = %d, want 4", got) + } + if _, err := srv.RegisterPlayer(Profile{Name: "Eve", UUID: OfflineUUID("Eve")}, nil); !errors.Is(err, ErrServerFull) { + t.Fatalf("fifth player error = %v, want ErrServerFull", err) + } + + srv.Broadcast(1, []byte("packet")) + if got := sends.Load(); got != 4 { + t.Fatalf("broadcast sends = %d, want 4", got) + } + for _, session := range sessions { + srv.UnregisterPlayer(session) + } + if got := srv.PlayerCount(); got != 0 { + t.Fatalf("player count after disconnect = %d, want 0", got) + } +} + +func TestConcurrentFourPlayerTransformsAndChunkBroadcast(t *testing.T) { + cfg := DefaultConfig() + cfg.WorldDir = "" + cfg.MaxPlayers = 4 + srv, err := NewWithCache(cfg, world.NewCache(-1, world.GenerateFlat)) + if err != nil { + t.Fatal(err) + } + + var sends [4]atomic.Int32 + sessions := make([]*PlayerSession, 4) + for i, name := range []string{"Alice", "Bob", "Carol", "Dave"} { + i := i + sessions[i], err = srv.RegisterPlayer(Profile{Name: name, UUID: OfflineUUID(name)}, func(int32, []byte) error { + sends[i].Add(1) + return nil + }) + if err != nil { + t.Fatal(err) + } + srv.SetPlayerViewDistance(sessions[i], 2) + } + + var wg sync.WaitGroup + for i, session := range sessions { + i, session := i, session + wg.Add(1) + go func() { + defer wg.Done() + for step := 0; step < 500; step++ { + srv.SetPlayerTransform(session, float64(i*16+step), 80, float64(-step), float32(step%360), 10, step%2 == 0) + _ = srv.PlayerSnapshots() + srv.BroadcastChunk(int32(step>>4), int32(-step>>4), 1, nil) + } + }() + } + wg.Wait() + if got := len(srv.PlayerSnapshots()); got != 4 { + t.Fatalf("snapshot count = %d, want 4", got) + } + + for i, session := range sessions { + x := float64(i * 160) + srv.SetPlayerTransform(session, x, 80, 0, 0, 0, true) + before := sends[i].Load() + srv.BroadcastChunk(0, 0, 2, nil) + got := sends[i].Load() - before + if i == 0 && got != 1 { + t.Fatalf("near player received %d packets, want 1", got) + } + if i > 0 && got != 0 { + t.Fatalf("far player %d received %d packets, want 0", i, got) + } + } +} + +func TestPlayerRegistryRejectsDuplicateName(t *testing.T) { + cfg := DefaultConfig() + srv, err := NewWithCache(cfg, world.NewCache(-1, world.GenerateFlat)) + if err != nil { + t.Fatal(err) + } + first := Profile{Name: "Alice", UUID: OfflineUUID("Alice")} + if _, err := srv.RegisterPlayer(first, nil); err != nil { + t.Fatal(err) + } + duplicate := Profile{Name: "ALICE", UUID: OfflineUUID("ALICE")} + if _, err := srv.RegisterPlayer(duplicate, nil); !errors.Is(err, ErrDuplicatePlayer) { + t.Fatalf("duplicate error = %v, want ErrDuplicatePlayer", err) + } +} + +func TestServerRejectsNegativeCacheLimit(t *testing.T) { + cfg := DefaultConfig() + cfg.MaxCachedChunks = -1 + if _, err := NewWithCache(cfg, world.NewCache(-1, world.GenerateFlat)); err == nil { + t.Fatal("negative cache limit was accepted") + } +} diff --git a/internal/server/spawner.go b/internal/server/spawner.go index bd9434f..0d64070 100644 --- a/internal/server/spawner.go +++ b/internal/server/spawner.go @@ -1,6 +1,7 @@ package server import ( + "context" "math" "math/rand" "time" @@ -10,92 +11,102 @@ import ( ) // StartSpawning begins the entity tick and spawn loops. -func (s *Server) StartSpawning() { - go s.entityTickLoop() - go s.mobSpawnLoop() +func (s *Server) StartSpawning(ctx context.Context) { + go s.entityTickLoop(ctx) + go s.mobSpawnLoop(ctx) } -func (s *Server) entityTickLoop() { +func (s *Server) entityTickLoop(ctx context.Context) { ticker := time.NewTicker(50 * time.Millisecond) // 20 TPS defer ticker.Stop() - for range ticker.C { - all := s.entities.All() - for _, e := range all { - // Apply gravity - yBelow := int(e.Y - 0.1) // slightly below the entity - blockBelow := s.chunks.GetBlock(int(e.X), yBelow, int(e.Z)) - - if blockBelow == world.StateAir || blockBelow == world.StateWater { // Air or Water - e.VelocityY -= 80 // gravity acceleration - if e.VelocityY < -3000 { - e.VelocityY = -3000 // terminal velocity - } - } else { - e.VelocityY = 0 - e.Y = float64(yBelow + 1) - - // Basic random wandering or player tracking when on ground - pos, ok := s.NearestPlayer(e.X, e.Y, e.Z) - - if ok && e.TypeName == "minecraft:zombie" { - // Zombies move towards the player - dx := pos[0] - e.X - dz := pos[2] - e.Z - dist := math.Sqrt(dx*dx + dz*dz) - if dist > 1.0 && dist < 32.0 { - e.X += (dx / dist) * 0.15 - e.Z += (dz / dist) * 0.15 - // Simple yaw calculation - e.Yaw = float32(math.Atan2(-dx, dz) * (180 / math.Pi)) + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + all := s.entities.All() + for i := range all { + snapshot := all[i] + yBelow := int(math.Floor(snapshot.Y - 0.1)) + blockBelow := s.chunks.GetBlock(int(math.Floor(snapshot.X)), yBelow, int(math.Floor(snapshot.Z))) + nearest, hasPlayer := s.NearestPlayer(snapshot.X, snapshot.Y, snapshot.Z) + s.entities.Update(all[i].ID, func(e *world.Entity) { + // Apply gravity + if blockBelow == world.StateAir || blockBelow == world.StateWater { // Air or Water + e.VelocityY -= 80 // gravity acceleration + if e.VelocityY < -3000 { + e.VelocityY = -3000 // terminal velocity + } + } else { + e.VelocityY = 0 + e.Y = float64(yBelow + 1) + + // Basic random wandering or player tracking when on ground + if hasPlayer && e.TypeName == "minecraft:zombie" { + // Zombies move towards the player + dx := nearest[0] - e.X + dz := nearest[2] - e.Z + dist := math.Sqrt(dx*dx + dz*dz) + if dist > 1.0 && dist < 32.0 { + e.X += (dx / dist) * 0.15 + e.Z += (dz / dist) * 0.15 + // Simple yaw calculation + e.Yaw = float32(math.Atan2(-dx, dz) * (180 / math.Pi)) + } + } else { + // Random wander + e.X += (rand.Float64() - 0.5) * 0.2 + e.Z += (rand.Float64() - 0.5) * 0.2 + e.Yaw += float32((rand.Float64() - 0.5) * 10.0) + } } - } else { - // Random wander - e.X += (rand.Float64() - 0.5) * 0.2 - e.Z += (rand.Float64() - 0.5) * 0.2 - e.Yaw += float32((rand.Float64() - 0.5) * 10.0) - } - } - - if e.VelocityY != 0 { - e.Y += float64(e.VelocityY) / 8000.0 + + if e.VelocityY != 0 { + e.Y += float64(e.VelocityY) / 8000.0 + } + }) } } } } -func (s *Server) mobSpawnLoop() { +func (s *Server) mobSpawnLoop(ctx context.Context) { ticker := time.NewTicker(2 * time.Second) defer ticker.Stop() - pigType := registry.Index("minecraft:entity_type", "minecraft:pig") - zombieType := registry.Index("minecraft:entity_type", "minecraft:zombie") + pigType := registry.EntityTypeIndex("minecraft:pig") + zombieType := registry.EntityTypeIndex("minecraft:zombie") if pigType < 0 || zombieType < 0 { return } - for range ticker.C { - all := s.entities.All() - if len(all) > 50 { - continue // limit to 50 entities - } + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + if s.PlayerCount() == 0 || s.entities.Count() >= 50 { + continue // limit to 50 entities + } - // Spawn near the spawn point (8.5, 200, 8.5) - x := (rand.Float64() - 0.5) * 30.0 - z := (rand.Float64() - 0.5) * 30.0 - - t := pigType - name := "minecraft:pig" - if rand.Float32() < 0.5 { - t = zombieType - name = "minecraft:zombie" - } + // Spawn near the spawn point (8.5, 200, 8.5) + x := (rand.Float64() - 0.5) * 30.0 + z := (rand.Float64() - 0.5) * 30.0 - s.entities.Add(&world.Entity{ - TypeID: t, - TypeName: name, - X: x + 8.5, - Y: 200.0, // They float for now since there's no gravity - Z: z + 8.5, - }) + t := pigType + name := "minecraft:pig" + if rand.Float32() < 0.5 { + t = zombieType + name = "minecraft:zombie" + } + + s.entities.Add(&world.Entity{ + TypeID: t, + TypeName: name, + X: x + 8.5, + Y: 200.0, + Z: z + 8.5, + }) + } } } diff --git a/internal/world/cache.go b/internal/world/cache.go index 761205a..24b506f 100644 --- a/internal/world/cache.go +++ b/internal/world/cache.go @@ -3,6 +3,8 @@ package world import ( "container/list" "context" + "errors" + "fmt" "log/slog" "sync" "time" @@ -36,13 +38,23 @@ type Cache struct { store *Store // nil = in-memory only (tests, flat worlds) maxChunks int // LRU capacity; 0 = unbounded - mu sync.Mutex - chunks map[[2]int32]*Chunk - frames map[[2]int32][]byte - dirty map[[2]int32]struct{} + mu sync.Mutex + lightMu sync.Mutex + chunks map[[2]int32]*Chunk + frames map[[2]int32][]byte + dirty map[[2]int32]uint64 // LRU bookkeeping: order is MRU(front)→LRU(back); index gives O(1) lookup. - order *list.List // elements are *[2]int32; nil when maxChunks==0 + order *list.List // elements are *[2]int32; nil when maxChunks==0 index map[[2]int32]*list.Element + loads map[[2]int32]*chunkLoad +} + +// chunkLoad coordinates concurrent misses for the same coordinate. The first +// caller performs disk I/O or generation; all others wait for that exact result. +type chunkLoad struct { + done chan struct{} + ch *Chunk + err error } // NewCache returns a world cache that frames packets at the given compression @@ -54,7 +66,8 @@ func NewCache(threshold int32, gen Generator) *Cache { gen: gen, chunks: make(map[[2]int32]*Chunk), frames: make(map[[2]int32][]byte), - dirty: make(map[[2]int32]struct{}), + dirty: make(map[[2]int32]uint64), + loads: make(map[[2]int32]*chunkLoad), } return c } @@ -74,6 +87,9 @@ func NewCacheWithStore(threshold int32, gen Generator, store *Store) *Cache { // ≈ maxChunks×200KiB. func NewCacheWithLimit(threshold int32, gen Generator, store *Store, maxChunks int) *Cache { c := NewCacheWithStore(threshold, gen, store) + if maxChunks < 0 { + maxChunks = 0 + } c.maxChunks = maxChunks if maxChunks > 0 { c.order = list.New() @@ -84,7 +100,7 @@ func NewCacheWithLimit(threshold int32, gen Generator, store *Store, maxChunks i // touch marks key as most-recently-used. Must be called under c.mu. func (c *Cache) touch(key [2]int32) { - if c.maxChunks == 0 { + if c.maxChunks <= 0 { return } if e, ok := c.index[key]; ok { @@ -98,10 +114,11 @@ func (c *Cache) touch(key [2]int32) { // Dirty chunks are skipped (moved back to MRU and the eviction halts) so the // autosave can persist them first. Must be called under c.mu. func (c *Cache) evictIfNeeded() { - if c.maxChunks == 0 { + if c.maxChunks <= 0 { return } - for len(c.chunks) > c.maxChunks { + checked := 0 + for len(c.chunks) > c.maxChunks && checked < len(c.chunks) { back := c.order.Back() if back == nil { return @@ -112,12 +129,14 @@ func (c *Cache) evictIfNeeded() { // next eviction pass can reclaim it. if _, dirty := c.dirty[key]; dirty && c.store != nil { c.order.MoveToFront(back) - break + checked++ + continue } delete(c.chunks, key) delete(c.frames, key) c.order.Remove(back) delete(c.index, key) + checked = 0 } } @@ -125,66 +144,110 @@ func (c *Cache) evictIfNeeded() { // disk (if a store is attached) → generation. Generation and disk reads run // outside the lock. func (c *Cache) chunkAt(cx, cz int32) *Chunk { + ch, _ := c.chunkAtErr(cx, cz) + return ch +} + +// chunkAtErr is the error-preserving form used by network and mutation paths. +// A corrupt or unreadable stored chunk is never replaced by generated terrain. +func (c *Cache) chunkAtErr(cx, cz int32) (*Chunk, error) { key := [2]int32{cx, cz} c.mu.Lock() if ch, ok := c.chunks[key]; ok { c.touch(key) c.mu.Unlock() - return ch + return ch, nil } + if pending, ok := c.loads[key]; ok { + c.mu.Unlock() + <-pending.done + return pending.ch, pending.err + } + pending := &chunkLoad{done: make(chan struct{})} + c.loads[key] = pending c.mu.Unlock() // Try disk before generation so saved edits survive restarts. var ch *Chunk + var loadErr error if c.store != nil { if loaded, err := c.store.LoadChunk(cx, cz); err == nil { ch = loaded + } else if !errors.Is(err, ErrChunkNotFound) { + loadErr = fmt.Errorf("world: load chunk (%d,%d): %w", cx, cz, err) } } - if ch == nil { + if ch == nil && loadErr == nil { ch = c.gen(cx, cz) // generate outside the lock + if ch == nil { + loadErr = fmt.Errorf("world: generator returned nil chunk (%d,%d)", cx, cz) + } } c.mu.Lock() - defer c.mu.Unlock() - if existing, ok := c.chunks[key]; ok { + if loadErr == nil { + c.chunks[key] = ch c.touch(key) - return existing // another goroutine won the race + c.evictIfNeeded() } - c.chunks[key] = ch - c.touch(key) - c.evictIfNeeded() - return ch + pending.ch, pending.err = ch, loadErr + delete(c.loads, key) + close(pending.done) + c.mu.Unlock() + return ch, loadErr } // Frame returns the prebuilt level_chunk packet for (cx, cz), building it on // first request and caching until the chunk is edited. The slice must not be // mutated. func (c *Cache) Frame(cx, cz int32) []byte { + frame, _ := c.FrameErr(cx, cz) + return frame +} + +// FrameErr returns a framed chunk packet while preserving storage failures. +// Callers serving clients should prefer it to Frame so corruption is observable. +func (c *Cache) FrameErr(cx, cz int32) ([]byte, error) { key := [2]int32{cx, cz} - c.mu.Lock() - if f, ok := c.frames[key]; ok { - c.touch(key) + for { + c.mu.Lock() + if f, ok := c.frames[key]; ok { + c.touch(key) + c.mu.Unlock() + return f, nil + } c.mu.Unlock() - return f - } - c.mu.Unlock() - ch := c.chunkAt(cx, cz) - frame := protocol.AppendPacket(nil, c.threshold, protocol.PlayLevelChunk, ch.Encode()) + ch, err := c.chunkAtErr(cx, cz) + if err != nil { + return nil, err + } + if err := c.ensureLight(ch); err != nil { + return nil, err + } + snapshot, revision := ch.snapshot() + frame := protocol.AppendPacket(nil, c.threshold, protocol.PlayLevelChunk, snapshot.encode()) - c.mu.Lock() - defer c.mu.Unlock() - if existing, ok := c.frames[key]; ok { + c.mu.Lock() + if existing, ok := c.frames[key]; ok { + c.touch(key) + c.mu.Unlock() + return existing, nil + } + // An edit or eviction while the frame was being built makes it stale. + // Retry from a fresh snapshot instead of publishing old bytes forever. + if c.chunks[key] != ch || ch.currentRevision() != revision { + c.mu.Unlock() + continue + } + c.frames[key] = frame c.touch(key) - return existing + c.evictIfNeeded() + c.mu.Unlock() + return frame, nil } - c.frames[key] = frame - c.touch(key) - c.evictIfNeeded() - return frame } // GetBlock returns the block state at world coordinates (x, y, z). @@ -195,32 +258,82 @@ func (c *Cache) GetBlock(x, y, z int) uint16 { } cx := int32(x >> 4) cz := int32(z >> 4) - ch := c.chunkAt(cx, cz) + ch, err := c.chunkAtErr(cx, cz) + if err != nil { + return StateAir + } return ch.GetBlock(x, y, z) } -// SetBlock changes the block at world coordinates (x, y, z), invalidating the -// affected chunk's cached frame and marking it dirty for autosave. It reports -// whether a chunk was actually touched (false if y is out of range). +// LightUpdate returns the standalone light_update body for a loaded chunk. +func (c *Cache) LightUpdate(cx, cz int32) ([]byte, error) { + ch, err := c.chunkAtErr(cx, cz) + if err != nil { + return nil, err + } + if err := c.ensureLight(ch); err != nil { + return nil, err + } + return ch.EncodeLightUpdate(), nil +} + +// SetBlock changes a block and incrementally updates lighting. Callers that need +// to broadcast every affected light chunk should use SetBlockWithLight. func (c *Cache) SetBlock(x, y, z int, state uint16) bool { + valid, _ := c.SetBlockWithLight(x, y, z, state) + return valid +} + +// ChunkPos identifies a chunk changed by a lighting update. +type ChunkPos struct { + X, Z int32 +} + +// SetBlockWithLight changes a block and returns the loaded chunks whose stored +// light changed. Lighting operations are serialized so concurrent edits cannot +// publish mutually stale propagation results. +func (c *Cache) SetBlockWithLight(x, y, z int, state uint16) (bool, []ChunkPos) { if y < MinY || y >= MinY+WorldHeight { - return false + return false, nil } cx := int32(x >> 4) cz := int32(z >> 4) - ch := c.chunkAt(cx, cz) + ch, err := c.chunkAtErr(cx, cz) + if err != nil { + return false, nil + } - ch.SetBlock(x, y, z, state) + c.lightMu.Lock() + defer c.lightMu.Unlock() + live := c.cachedLightNeighborhood(cx, cz) + for _, neighbor := range live { + if err := c.ensureLightLocked(neighbor); err != nil { + return false, nil + } + } + _, changed := ch.setBlock(x, y, z, state) + if !changed { + return true, nil + } + lightChanged, err := c.updateLightAfterBlockLocked(x, y, z, live) + if err != nil { + // The block edit is still valid and dirty; a later Frame call will rebuild + // its light from the authoritative blocks. + ch.mu.Lock() + ch.lightReady = false + ch.mu.Unlock() + lightChanged = []ChunkPos{{X: cx, Z: cz}} + } c.mu.Lock() key := [2]int32{cx, cz} delete(c.frames, key) if c.store != nil { - c.dirty[key] = struct{}{} + c.dirty[key] = ch.currentRevision() } c.touch(key) // edited chunk is most-recently-used c.mu.Unlock() - return true + return true, lightChanged } // markDirty flags the chunk at (cx, cz) for the next autosave. Public so tests @@ -230,7 +343,10 @@ func (c *Cache) markDirty(cx, cz int32) { return } c.mu.Lock() - c.dirty[[2]int32{cx, cz}] = struct{}{} + key := [2]int32{cx, cz} + if ch := c.chunks[key]; ch != nil { + c.dirty[key] = ch.currentRevision() + } c.mu.Unlock() } @@ -277,22 +393,32 @@ func (c *Cache) flushDirty() error { for _, k := range keys { chunks[k] = c.chunks[k] } - c.dirty = make(map[[2]int32]struct{}) c.mu.Unlock() + var firstErr error for _, k := range keys { ch := chunks[k] if ch == nil { + c.mu.Lock() + delete(c.dirty, k) + c.mu.Unlock() continue } - if err := c.store.SaveChunk(ch); err != nil { - c.mu.Lock() - c.dirty[k] = struct{}{} // re-mark; retry next cycle - c.mu.Unlock() - return err + snapshot, savedRevision := ch.snapshot() + if err := c.store.saveSnapshot(snapshot); err != nil { + if firstErr == nil { + firstErr = err + } + continue } + c.mu.Lock() + if dirtyRevision, ok := c.dirty[k]; ok && dirtyRevision <= savedRevision { + delete(c.dirty, k) + } + c.evictIfNeeded() + c.mu.Unlock() } - return nil + return firstErr } // SaveAll synchronously persists every chunk currently in memory. Used at @@ -301,24 +427,5 @@ func (c *Cache) SaveAll() error { if c.store == nil { return nil } - c.mu.Lock() - keys := make([][2]int32, 0, len(c.chunks)) - for k := range c.chunks { - keys = append(keys, k) - } - chunks := make(map[[2]int32]*Chunk, len(keys)) - for _, k := range keys { - chunks[k] = c.chunks[k] - } - c.mu.Unlock() - - var firstErr error - for _, k := range keys { - if ch := chunks[k]; ch != nil { - if err := c.store.SaveChunk(ch); err != nil && firstErr == nil { - firstErr = err - } - } - } - return firstErr + return c.flushDirty() } diff --git a/internal/world/cache_light.go b/internal/world/cache_light.go new file mode 100644 index 0000000..3bcf900 --- /dev/null +++ b/internal/world/cache_light.go @@ -0,0 +1,174 @@ +package world + +import ( + "errors" + "fmt" + "sort" +) + +// ensureLight calculates an exact center-chunk solution from a 3x3 block +// neighborhood. Light attenuates to zero within 15 blocks, so chunks outside +// that neighborhood cannot affect the center chunk. +func (c *Cache) ensureLight(chunk *Chunk) error { + c.lightMu.Lock() + defer c.lightMu.Unlock() + return c.ensureLightLocked(chunk) +} + +func (c *Cache) ensureLightLocked(chunk *Chunk) error { + for { + center, revision := chunk.snapshot() + if center.lightReady { + return nil + } + + chunks := make(map[[2]int32]*Chunk, 9) + for dz := int32(-1); dz <= 1; dz++ { + for dx := int32(-1); dx <= 1; dx++ { + key := [2]int32{chunk.X + dx, chunk.Z + dz} + if dx == 0 && dz == 0 { + chunks[key] = center + continue + } + snapshot, err := c.lightInputSnapshot(key[0], key[1]) + if err != nil { + return err + } + chunks[key] = snapshot + } + } + + volume := newLightVolume(int(chunk.X-1), int(chunk.Z-1), 3, 3, chunks) + clear(volume.sky) + clear(volume.block) + volume.calculate() + + chunk.mu.Lock() + if chunk.revision.Load() != revision { + chunk.mu.Unlock() + continue + } + chunk.installLight(volume) + chunk.mu.Unlock() + + c.mu.Lock() + delete(c.frames, [2]int32{chunk.X, chunk.Z}) + c.mu.Unlock() + return nil + } +} + +// lightInputSnapshot returns blocks for a neighboring chunk without inserting +// a cache miss into the LRU. This keeps lighting correct even for very small +// cache limits and avoids an eight-chunk eviction cascade per frame. +func (c *Cache) lightInputSnapshot(cx, cz int32) (*Chunk, error) { + key := [2]int32{cx, cz} + c.mu.Lock() + if chunk := c.chunks[key]; chunk != nil { + c.touch(key) + c.mu.Unlock() + snapshot, _ := chunk.snapshot() + return snapshot, nil + } + if pending := c.loads[key]; pending != nil { + c.mu.Unlock() + <-pending.done + if pending.err != nil { + return nil, pending.err + } + snapshot, _ := pending.ch.snapshot() + return snapshot, nil + } + c.mu.Unlock() + + if c.store != nil { + loaded, err := c.store.LoadChunk(cx, cz) + if err == nil { + snapshot, _ := loaded.snapshot() + return snapshot, nil + } + if !errors.Is(err, ErrChunkNotFound) { + return nil, fmt.Errorf("world: load light neighbor (%d,%d): %w", cx, cz, err) + } + } + generated := c.gen(cx, cz) + if generated == nil { + return nil, fmt.Errorf("world: generator returned nil light neighbor (%d,%d)", cx, cz) + } + snapshot, _ := generated.snapshot() + return snapshot, nil +} + +func (c *Cache) cachedLightNeighborhood(cx, cz int32) map[[2]int32]*Chunk { + c.mu.Lock() + defer c.mu.Unlock() + chunks := make(map[[2]int32]*Chunk, 9) + for dz := int32(-1); dz <= 1; dz++ { + for dx := int32(-1); dx <= 1; dx++ { + key := [2]int32{cx + dx, cz + dz} + if chunk := c.chunks[key]; chunk != nil { + chunks[key] = chunk + c.touch(key) + } + } + } + return chunks +} + +func (c *Cache) updateLightAfterBlockLocked(x, y, z int, live map[[2]int32]*Chunk) ([]ChunkPos, error) { + cx, cz := int32(x>>4), int32(z>>4) + inputs := make(map[[2]int32]*Chunk, 9) + for dz := int32(-1); dz <= 1; dz++ { + for dx := int32(-1); dx <= 1; dx++ { + key := [2]int32{cx + dx, cz + dz} + if chunk := live[key]; chunk != nil { + snapshot, _ := chunk.snapshot() + inputs[key] = snapshot + continue + } + snapshot, err := c.lightInputSnapshot(key[0], key[1]) + if err != nil { + return nil, err + } + inputs[key] = snapshot + } + } + + volume := newLightVolume(int(cx-1), int(cz-1), 3, 3, inputs) + volume.relaxBlockChange(x-volume.minX, y, z-volume.minZ) + + keys := make([][2]int32, 0, len(live)) + for key := range live { + keys = append(keys, key) + } + sort.Slice(keys, func(i, j int) bool { + if keys[i][0] != keys[j][0] { + return keys[i][0] < keys[j][0] + } + return keys[i][1] < keys[j][1] + }) + + changed := make([]ChunkPos, 0, len(keys)) + for _, key := range keys { + chunk := live[key] + chunk.mu.Lock() + didChange := chunk.installLight(volume) + if didChange { + chunk.revision.Add(1) + changed = append(changed, ChunkPos{X: key[0], Z: key[1]}) + } + revision := chunk.revision.Load() + chunk.mu.Unlock() + + if didChange { + c.mu.Lock() + delete(c.frames, key) + if c.store != nil { + c.dirty[key] = revision + } + c.touch(key) + c.mu.Unlock() + } + } + return changed, nil +} diff --git a/internal/world/cache_test.go b/internal/world/cache_test.go index 501d4ed..e21c9c0 100644 --- a/internal/world/cache_test.go +++ b/internal/world/cache_test.go @@ -1,7 +1,13 @@ package world import ( + "bytes" + "sync" + "sync/atomic" "testing" + "time" + + "regionio/internal/protocol" ) // flatGen returns a generator that produces distinct chunks keyed by coordinate, @@ -173,3 +179,72 @@ func TestEvictionReloadPreservesEdits(t *testing.T) { t.Errorf("after eviction+reload, edited block = %d, want bedrock %d", got, StateBedrock) } } + +func TestConcurrentMissGeneratesChunkOnce(t *testing.T) { + var targetCalls atomic.Int32 + gen := func(cx, cz int32) *Chunk { + if cx == 4 && cz == -7 { + targetCalls.Add(1) + } + time.Sleep(10 * time.Millisecond) + return NewChunk(cx, cz, BiomePlains) + } + c := NewCache(256, gen) + var wg sync.WaitGroup + for i := 0; i < 16; i++ { + wg.Add(1) + go func() { + defer wg.Done() + if frame, err := c.FrameErr(4, -7); err != nil || len(frame) == 0 { + t.Errorf("FrameErr = %d bytes, %v", len(frame), err) + } + }() + } + wg.Wait() + if got := targetCalls.Load(); got != 1 { + t.Fatalf("target generator calls = %d, want 1", got) + } +} + +func TestConcurrentFrameAndBlockEdits(t *testing.T) { + c := NewCache(256, flatGen()) + if len(c.Frame(0, 0)) == 0 { + t.Fatal("initial frame is empty") + } + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + for i := 0; i < 250; i++ { + state := StateStone + if i%2 == 0 { + state = StateBedrock + } + c.SetBlock(3, SeaLevel, 3, state) + } + }() + go func() { + defer wg.Done() + for i := 0; i < 100; i++ { + if len(c.Frame(0, 0)) == 0 { + t.Error("frame became empty") + return + } + } + }() + wg.Wait() + c.SetBlock(3, SeaLevel, 3, StateBedrock) + if got := c.GetBlock(3, SeaLevel, 3); got != StateBedrock { + t.Fatalf("final block = %d, want %d", got, StateBedrock) + } + finalFrame := c.Frame(0, 0) + if len(finalFrame) == 0 { + t.Fatal("final frame is empty") + } + snapshot, _ := c.chunkAt(0, 0).snapshot() + wantFrame := protocol.AppendPacket(nil, 256, protocol.PlayLevelChunk, snapshot.encode()) + if !bytes.Equal(finalFrame, wantFrame) { + t.Fatal("cached frame does not represent the final chunk revision") + } +} diff --git a/internal/world/chunk.go b/internal/world/chunk.go index 3926582..4168826 100644 --- a/internal/world/chunk.go +++ b/internal/world/chunk.go @@ -13,10 +13,10 @@ func GenerateFlat(cx, cz int32) *Chunk { c := NewChunk(cx, cz, BiomePlains) for lx := 0; lx < 16; lx++ { for lz := 0; lz < 16; lz++ { - c.SetBlock(lx, MinY+0, lz, StateBedrock) - c.SetBlock(lx, MinY+1, lz, StateDirt) - c.SetBlock(lx, MinY+2, lz, StateDirt) - c.SetBlock(lx, FlatSurfaceY, lz, StateGrass) + c.setBlockRaw(lx, MinY+0, lz, StateBedrock) + c.setBlockRaw(lx, MinY+1, lz, StateDirt) + c.setBlockRaw(lx, MinY+2, lz, StateDirt) + c.setBlockRaw(lx, FlatSurfaceY, lz, StateGrass) } } return c diff --git a/internal/world/compress.go b/internal/world/compress.go index 476120b..72b1c23 100644 --- a/internal/world/compress.go +++ b/internal/world/compress.go @@ -10,12 +10,16 @@ import ( // default compression for Anvil .mca chunk records (compression type 2). // zlibDeflate compresses src into a new byte slice. -func zlibDeflate(src []byte) []byte { +func zlibDeflate(src []byte) ([]byte, error) { var buf bytes.Buffer w := zlib.NewWriter(&buf) - _, _ = w.Write(src) - _ = w.Close() - return buf.Bytes() + if _, err := w.Write(src); err != nil { + return nil, err + } + if err := w.Close(); err != nil { + return nil, err + } + return buf.Bytes(), nil } // zlibInflate decompresses src (a zlib stream). It returns an error if src is diff --git a/internal/world/encode.go b/internal/world/encode.go index eb11af3..a316313 100644 --- a/internal/world/encode.go +++ b/internal/world/encode.go @@ -2,6 +2,8 @@ package world import ( "math/bits" + "sync" + "sync/atomic" "regionio/internal/protocol" ) @@ -37,6 +39,7 @@ const ( StateSnow uint16 = 6919 // snow, layers=1 StateSnowBlock uint16 = 6928 StateIce uint16 = 6927 + StateGlowstone uint16 = 7016 StateMycelium uint16 = 8919 StateTerracotta uint16 = 12912 StateRedSandstone uint16 = 13247 @@ -57,19 +60,24 @@ const totalBlockStates = 29873 // the biome direct-palette bit width. const ( biomeCellSize = 4 - biomeCellsXZ = 16 / biomeCellSize // 4 + biomeCellsXZ = 16 / biomeCellSize // 4 biomeCellsPerSection = biomeCellsXZ * biomeCellsXZ * biomeCellsXZ // 64 - totalBiomes = 65 // synced minecraft:worldgen/biome registry size + totalBiomes = 65 // synced minecraft:worldgen/biome registry size ) // Chunk is a 16xWorldHeightx16 column of block states. Each section may carry a // per-cell biome array (4×4×4); when biomes[si] is nil the section falls back to // the column-wide biome field (used by flat/simple generators). type Chunk struct { - X, Z int32 - sections [SectionCount]*[sectionVol]uint16 - biomes [SectionCount]*[biomeCellsPerSection]uint16 - biome uint16 // fallback uniform biome when biomes[si] is nil + mu sync.RWMutex + revision atomic.Uint64 + X, Z int32 + sections [SectionCount]*[sectionVol]uint16 + biomes [SectionCount]*[biomeCellsPerSection]uint16 + skyLight [SectionCount]*[2048]byte + blockLight [SectionCount]*[2048]byte + lightReady bool + biome uint16 // fallback uniform biome when biomes[si] is nil } // NewChunk returns an empty (all-air) chunk at (x, z) with the given biome. @@ -91,6 +99,13 @@ func (c *Chunk) section(i int) *[sectionVol]uint16 { // GetBlock returns the block state at local (lx, lz) and world height y, or // StateAir if the section is empty or y is out of range. func (c *Chunk) GetBlock(lx, y, lz int) uint16 { + c.mu.RLock() + defer c.mu.RUnlock() + return c.getBlock(lx, y, lz) +} + +// getBlock is the lock-free form used while operating on a private snapshot. +func (c *Chunk) getBlock(lx, y, lz int) uint16 { si := (y - MinY) >> 4 if si < 0 || si >= SectionCount { return StateAir @@ -106,6 +121,8 @@ func (c *Chunk) GetBlock(lx, y, lz int) uint16 { // mirrors GetBlock: the per-section biome array if present, else the column's // uniform fallback biome. Needed for on-disk chunk serialization. func (c *Chunk) GetBiome(lx, y, lz int) uint16 { + c.mu.RLock() + defer c.mu.RUnlock() si := (y - MinY) >> 4 if si < 0 || si >= SectionCount { return c.biome @@ -119,6 +136,18 @@ func (c *Chunk) GetBiome(lx, y, lz int) uint16 { // SetBlock sets the block at local (lx, lz) and absolute world height y. func (c *Chunk) SetBlock(lx, y, lz int, state uint16) { + _, changed := c.setBlock(lx, y, lz, state) + if changed { + c.mu.Lock() + c.lightReady = false + c.mu.Unlock() + } +} + +// setBlockRaw writes to an unpublished chunk during generation. Generators call +// it only after their parallel sampling phases have joined and before the chunk +// enters Cache, avoiding a mutex operation for every solid terrain block. +func (c *Chunk) setBlockRaw(lx, y, lz int, state uint16) { si := (y - MinY) >> 4 if si < 0 || si >= SectionCount { return @@ -126,6 +155,24 @@ func (c *Chunk) SetBlock(lx, y, lz int, state uint16) { c.section(si)[blockIndex(lx, y, lz)] = state } +// setBlock changes a block and returns the resulting revision. The changed flag +// lets the cache avoid dirtying a chunk for a no-op client prediction. +func (c *Chunk) setBlock(lx, y, lz int, state uint16) (revision uint64, changed bool) { + c.mu.Lock() + defer c.mu.Unlock() + si := (y - MinY) >> 4 + if si < 0 || si >= SectionCount { + return c.revision.Load(), false + } + idx := blockIndex(lx, y, lz) + s := c.section(si) + if s[idx] == state { + return c.revision.Load(), false + } + s[idx] = state + return c.revision.Add(1), true +} + // biomeIndex maps a block within a section to its YZX-ordered 4×4×4 biome cell. // Coordinates are folded into 0..15 (block coords) then divided to cell coords. func biomeIndex(lx, ly, lz int) int { @@ -142,18 +189,70 @@ const biomeCellsXZBits = 2 // biomeCellsXZ=4 → 2 bits // section's per-cell biome array is allocated lazily on first write. Any block // in the cell shares its biome, matching the 4-block resolution vanilla uses. func (c *Chunk) SetBiome(lx, y, lz int, biome uint16) { + c.mu.Lock() + defer c.mu.Unlock() si := (y - MinY) >> 4 if si < 0 || si >= SectionCount { return } if c.biomes[si] == nil { - c.biomes[si] = new([biomeCellsPerSection]uint16) + cells := new([biomeCellsPerSection]uint16) + for i := range cells { + cells[i] = c.biome + } + c.biomes[si] = cells + } + idx := biomeIndex(lx, y, lz) + if c.biomes[si][idx] != biome { + c.biomes[si][idx] = biome + c.revision.Add(1) } - c.biomes[si][biomeIndex(lx, y, lz)] = biome } // Encode serializes the level_chunk_with_light body for this chunk. func (c *Chunk) Encode() []byte { + snapshot, _ := c.snapshot() + return snapshot.encode() +} + +// snapshot returns a detached, immutable copy and the revision it represents. +// Section arrays are copied so encoding and persistence never race block edits. +func (c *Chunk) snapshot() (*Chunk, uint64) { + c.mu.RLock() + defer c.mu.RUnlock() + + revision := c.revision.Load() + clone := &Chunk{X: c.X, Z: c.Z, biome: c.biome} + clone.revision.Store(revision) + for i := 0; i < SectionCount; i++ { + if c.sections[i] != nil { + section := *c.sections[i] + clone.sections[i] = §ion + } + if c.biomes[i] != nil { + biomes := *c.biomes[i] + clone.biomes[i] = &biomes + } + if c.skyLight[i] != nil { + light := *c.skyLight[i] + clone.skyLight[i] = &light + } + if c.blockLight[i] != nil { + light := *c.blockLight[i] + clone.blockLight[i] = &light + } + } + clone.lightReady = c.lightReady + return clone, revision +} + +// currentRevision returns the latest mutation revision. +func (c *Chunk) currentRevision() uint64 { + return c.revision.Load() +} + +// encode serializes a detached snapshot. +func (c *Chunk) encode() []byte { w := protocol.NewWriter(8192) w.Int32(c.X).Int32(c.Z) c.writeHeightmaps(w) @@ -173,8 +272,8 @@ func (c *Chunk) Encode() []byte { // Heightmap.Types ordinals sent to the client. const ( - hmWorldSurface = 1 - hmMotionBlocking = 4 + hmWorldSurface = 1 + hmMotionBlocking = 4 hmMotionBlockingNoLeaves = 5 ) @@ -232,8 +331,8 @@ func packHeightmap(h [256]uint16) []uint64 { func (c *Chunk) writeSection(w *protocol.Writer, i int) { s := c.sections[i] if s == nil { - w.Uint16(0) // non-air block count - w.Uint16(0) // reserved 2-byte field (always 0 in vanilla) + w.Uint16(0) // non-air block count + w.Uint16(0) // reserved 2-byte field (always 0 in vanilla) writeSingleValued(w, uint32(StateAir)) } else { w.Uint16(uint16(nonAirCount(s))) diff --git a/internal/world/entity.go b/internal/world/entity.go index b6e9d57..8081f0f 100644 --- a/internal/world/entity.go +++ b/internal/world/entity.go @@ -3,22 +3,21 @@ package world import ( "crypto/rand" "sync" - "sync/atomic" ) // Entity represents an in-game movable entity (mob, animal, etc). type Entity struct { ID int32 UUID [16]byte - TypeID int // Network ID from the minecraft:entity_type registry + TypeID int // Network ID from the built-in minecraft:entity_type registry TypeName string - X, Y, Z float64 - Pitch, Yaw float32 - HeadYaw float32 - VelocityX int16 - VelocityY int16 - VelocityZ int16 + X, Y, Z float64 + Pitch, Yaw float32 + HeadYaw float32 + VelocityX int16 + VelocityY int16 + VelocityZ int16 } // EntityManager tracks active entities in the server and manages thread-safe access. @@ -40,7 +39,8 @@ func NewEntityManager() *EntityManager { func (em *EntityManager) Add(e *Entity) int32 { em.mu.Lock() defer em.mu.Unlock() - e.ID = atomic.AddInt32(&em.nextID, 1) + em.nextID++ + e.ID = em.nextID if e.UUID == [16]byte{} { rand.Read(e.UUID[:]) // Version 4 UUID @@ -58,20 +58,41 @@ func (em *EntityManager) Remove(id int32) { delete(em.entities, id) } -// Get retrieves an entity by ID, or nil if not found. +// Get retrieves a snapshot of an entity by ID, or nil if not found. func (em *EntityManager) Get(id int32) *Entity { em.mu.RLock() defer em.mu.RUnlock() - return em.entities[id] + e := em.entities[id] + if e == nil { + return nil + } + copy := *e + return © +} + +// Update executes a function on an entity under a write lock. +func (em *EntityManager) Update(id int32, fn func(*Entity)) { + em.mu.Lock() + defer em.mu.Unlock() + if e, ok := em.entities[id]; ok { + fn(e) + } } // All returns a snapshot slice of all active entities. -func (em *EntityManager) All() []*Entity { +func (em *EntityManager) All() []Entity { em.mu.RLock() defer em.mu.RUnlock() - list := make([]*Entity, 0, len(em.entities)) + list := make([]Entity, 0, len(em.entities)) for _, e := range em.entities { - list = append(list, e) + list = append(list, *e) } return list } + +// Count returns the number of active entities. +func (em *EntityManager) Count() int { + em.mu.RLock() + defer em.mu.RUnlock() + return len(em.entities) +} diff --git a/internal/world/features.go b/internal/world/features.go index 79bd907..f82a416 100644 --- a/internal/world/features.go +++ b/internal/world/features.go @@ -28,7 +28,7 @@ var oreSpecs = []oreSpec{ {"minecraft:gold_ore", MinY, MinY + 32, 4, 4, 0.25}, {"minecraft:redstone_ore", MinY, MinY + 16, 4, 4, 0.3}, {"minecraft:lapis_ore", MinY, MinY + 32, 3, 4, 0.25}, - {"minecraft:diamond_ore", MinY, MinY - 16 + 16, 3, 3, 0.2}, // -64..-16-ish + {"minecraft:diamond_ore", MinY, MinY + 16, 3, 3, 0.2}, // -64..-48 {"minecraft:emerald_ore", MinY + 16, MinY + 48, 1, 1, 0.15}, } diff --git a/internal/world/golden_test.go b/internal/world/golden_test.go index 044ecd1..6fbe91c 100644 --- a/internal/world/golden_test.go +++ b/internal/world/golden_test.go @@ -26,10 +26,50 @@ func extractSectionData(t *testing.T, body []byte) []byte { return body[8+consumed : 8+consumed+int(size)] } -// TestGoldenAgainstVanilla asserts our flat-chunk section data is byte-for-byte -// identical to a chunk captured from the official 26.1.2 server (same world -// coordinate). This guards the paletted-container and heightmap encoding. -// Light is intentionally not compared (we send full-bright, which differs). +// extractLightData returns the complete LightData tail of level_chunk_with_light. +func extractLightData(t *testing.T, body []byte) []byte { + t.Helper() + r := protocol.NewReader(body[8:]) + heightmaps, err := r.VarInt() + if err != nil { + t.Fatal(err) + } + for i := int32(0); i < heightmaps; i++ { + if _, err := r.VarInt(); err != nil { + t.Fatal(err) + } + longs, err := r.VarInt() + if err != nil { + t.Fatal(err) + } + for j := int32(0); j < longs; j++ { + if _, err := r.Int64(); err != nil { + t.Fatal(err) + } + } + } + sectionBytes, err := r.VarInt() + if err != nil { + t.Fatal(err) + } + for i := int32(0); i < sectionBytes; i++ { + if _, err := r.ReadByte(); err != nil { + t.Fatal(err) + } + } + blockEntities, err := r.VarInt() + if err != nil { + t.Fatal(err) + } + if blockEntities != 0 { + t.Fatalf("fixture has %d block entities; extractor only supports zero", blockEntities) + } + offset := len(body) - r.Remaining() + return body[offset:] +} + +// TestGoldenAgainstVanilla asserts our flat chunk's section and light data are +// byte-for-byte identical to a capture from the official 26.1.2 server. func TestGoldenAgainstVanilla(t *testing.T) { vanilla, err := os.ReadFile("testdata/vanilla_flat_chunk.bin") if err != nil { @@ -43,4 +83,54 @@ func TestGoldenAgainstVanilla(t *testing.T) { if !bytes.Equal(want, got) { t.Fatalf("section data differs: vanilla=%d bytes, ours=%d bytes", len(want), len(got)) } + + wantLight := extractLightData(t, vanilla) + gotLight := extractLightData(t, ours) + if !bytes.Equal(wantLight, gotLight) { + first := 0 + for first < len(wantLight) && first < len(gotLight) && wantLight[first] == gotLight[first] { + first++ + } + t.Fatalf("light data differs at byte %d: vanilla=%d bytes %v %x, ours=%d bytes %v %x", first, len(wantLight), lightSummary(t, wantLight), wantLight[:24], len(gotLight), lightSummary(t, gotLight), gotLight[:24]) + } +} + +func lightSummary(t *testing.T, data []byte) [6]uint64 { + t.Helper() + r := protocol.NewReader(data) + var summary [6]uint64 + for i := 0; i < 4; i++ { + longs, err := r.VarInt() + if err != nil { + t.Fatal(err) + } + for j := int32(0); j < longs; j++ { + value, err := r.Int64() + if err != nil { + t.Fatal(err) + } + if j == 0 { + summary[i] = uint64(value) + } + } + } + for i := 0; i < 2; i++ { + count, err := r.VarInt() + if err != nil { + t.Fatal(err) + } + summary[4+i] = uint64(count) + for j := int32(0); j < count; j++ { + length, err := r.VarInt() + if err != nil { + t.Fatal(err) + } + for k := int32(0); k < length; k++ { + if _, err := r.ReadByte(); err != nil { + t.Fatal(err) + } + } + } + } + return summary } diff --git a/internal/world/light.go b/internal/world/light.go index 6386f8a..b686ccd 100644 --- a/internal/world/light.go +++ b/internal/world/light.go @@ -2,119 +2,484 @@ package world import "regionio/internal/protocol" -// lightSections is the number of light subchunks: one below the world and one -// above, plus one per block section. +// Light is stored as vanilla nibble arrays: one 2048-byte array per 16^3 +// section. The two protocol-only sections below and above the world are added +// while encoding. const lightSections = SectionCount + 2 -// writeLight computes and emits simple lighting data. It does a vertical pass -// for sky light (sunlight propagating downward) and a single-block pass for -// block light (emissive blocks), without horizontal flood-fill. -func (c *Chunk) writeLight(w *protocol.Writer) { - skyLight := make([]*[2048]byte, lightSections) - blockLight := make([]*[2048]byte, lightSections) +type lightVolume struct { + minX, minZ int + width int + depth int + blocks []uint16 + sky []byte + block []byte +} - // Section lightSections-1 is above the world, fully lit by the sky. - skyLight[lightSections-1] = new([2048]byte) - for i := range skyLight[lightSections-1] { - skyLight[lightSections-1][i] = 0xFF +type lightNode struct { + x, y, z int +} + +var lightDirections = [...]lightNode{ + {0, -1, 0}, + {0, 1, 0}, + {0, 0, -1}, + {0, 0, 1}, + {-1, 0, 0}, + {1, 0, 0}, +} + +func newLightVolume(minCX, minCZ, chunksWide, chunksDeep int, chunks map[[2]int32]*Chunk) *lightVolume { + v := &lightVolume{ + minX: minCX * 16, + minZ: minCZ * 16, + width: chunksWide * 16, + depth: chunksDeep * 16, } - - for lx := 0; lx < 16; lx++ { - for lz := 0; lz < 16; lz++ { - // Sky light pass - currentSky := byte(15) - for y := MinY + WorldHeight - 1; y >= MinY; y-- { - block := c.GetBlock(lx, y, lz) - op := blockOpacity[block] - if op >= currentSky { - currentSky = 0 - } else { - currentSky -= op - } - - if currentSky > 0 { - si := (y - MinY) >> 4 - lsi := si + 1 - if skyLight[lsi] == nil { - skyLight[lsi] = new([2048]byte) - } - idx := blockIndex(lx, y, lz) - if idx%2 == 0 { - skyLight[lsi][idx/2] |= currentSky - } else { - skyLight[lsi][idx/2] |= currentSky << 4 - } - } - - // Block light pass - em := blockEmission[block] - if em > 0 { - si := (y - MinY) >> 4 - lsi := si + 1 - if blockLight[lsi] == nil { - blockLight[lsi] = new([2048]byte) - } - idx := blockIndex(lx, y, lz) - if idx%2 == 0 { - blockLight[lsi][idx/2] |= em - } else { - blockLight[lsi][idx/2] |= em << 4 + count := v.width * WorldHeight * v.depth + v.blocks = make([]uint16, count) + v.sky = make([]byte, count) + v.block = make([]byte, count) + for key, chunk := range chunks { + baseX := int(key[0])*16 - v.minX + baseZ := int(key[1])*16 - v.minZ + for y := MinY; y < MinY+WorldHeight; y++ { + for z := 0; z < 16; z++ { + for x := 0; x < 16; x++ { + idx := v.indexLocal(baseX+x, y, baseZ+z) + v.blocks[idx] = chunk.getBlock(x, y, z) + if chunk.lightReady { + v.sky[idx] = chunk.getLight(false, x, y, z) + v.block[idx] = chunk.getLight(true, x, y, z) } } } } } + return v +} - var skyMask, blockMask, emptySkyMask, emptyBlockMask uint64 - var skyCount, blockCount int +func (v *lightVolume) indexLocal(x, y, z int) int { + return ((y-MinY)*v.depth+z)*v.width + x +} - for i := 0; i < lightSections; i++ { - if skyLight[i] != nil { - skyMask |= 1 << i - skyCount++ - } else { - emptySkyMask |= 1 << i +func (v *lightVolume) inside(x, y, z int) bool { + return x >= 0 && x < v.width && z >= 0 && z < v.depth && y >= MinY && y < MinY+WorldHeight +} + +func (v *lightVolume) calculate() { + v.calculateSky() + v.calculateBlock() +} + +func (v *lightVolume) calculateSky() { + queue := make([]int, 0, len(v.sky)/2) + for z := 0; z < v.depth; z++ { + for x := 0; x < v.width; x++ { + from := StateAir + for y := MinY + WorldHeight - 1; y >= MinY; y-- { + idx := v.indexLocal(x, y, z) + state := v.blocks[idx] + if lightOpacity(state) != 0 || lightShapeOccludes(from, state, 0) { + break + } + v.sky[idx] = 15 + queue = append(queue, idx) + from = state + } } + } + v.propagateIncreases(v.sky, queue) +} - if blockLight[i] != nil { - blockMask |= 1 << i - blockCount++ - } else { - emptyBlockMask |= 1 << i +func (v *lightVolume) calculateBlock() { + queue := make([]int, 0, 256) + for idx, state := range v.blocks { + if emission := lightEmission(state); emission > 0 { + v.block[idx] = emission + queue = append(queue, idx) + } + } + v.propagateIncreases(v.block, queue) +} + +func (v *lightVolume) propagateIncreases(levels []byte, queue []int) { + for head := 0; head < len(queue); head++ { + idx := queue[head] + level := levels[idx] + if level <= 1 { + continue + } + x, y, z := v.coordinates(idx) + from := v.blocks[idx] + for direction, delta := range lightDirections { + nx, ny, nz := x+delta.x, y+delta.y, z+delta.z + if !v.inside(nx, ny, nz) { + continue + } + nidx := v.indexLocal(nx, ny, nz) + into := v.blocks[nidx] + attenuation := lightOpacity(into) + if attenuation < 1 { + attenuation = 1 + } + if attenuation >= level || lightShapeOccludes(from, into, direction) { + continue + } + candidate := level - attenuation + if candidate > levels[nidx] { + levels[nidx] = candidate + queue = append(queue, nidx) + } + } + } +} + +func (v *lightVolume) coordinates(idx int) (x, y, z int) { + x = idx % v.width + row := idx / v.width + z = row % v.depth + y = row/v.depth + MinY + return +} + +func (v *lightVolume) relaxBlockChange(x, y, z int) { + skySources := v.skySources() + blockSeeds := make([]int, 0, 7) + if v.inside(x, y, z) { + idx := v.indexLocal(x, y, z) + blockSeeds = append(blockSeeds, idx) + for _, delta := range lightDirections { + if v.inside(x+delta.x, y+delta.y, z+delta.z) { + blockSeeds = append(blockSeeds, v.indexLocal(x+delta.x, y+delta.y, z+delta.z)) + } + } + } + v.relax(v.block, nil, blockSeeds) + + skySeeds := make([]int, 0, WorldHeight*2) + for sy := MinY; sy < MinY+WorldHeight; sy++ { + idx := v.indexLocal(x, sy, z) + skySeeds = append(skySeeds, idx) + for _, direction := range []int{4, 5, 2, 3} { + delta := lightDirections[direction] + if v.inside(x+delta.x, sy, z+delta.z) { + skySeeds = append(skySeeds, v.indexLocal(x+delta.x, sy, z+delta.z)) + } + } + } + v.relax(v.sky, skySources, skySeeds) +} + +func (v *lightVolume) skySources() []bool { + sources := make([]bool, len(v.sky)) + for z := 0; z < v.depth; z++ { + for x := 0; x < v.width; x++ { + from := StateAir + for y := MinY + WorldHeight - 1; y >= MinY; y-- { + idx := v.indexLocal(x, y, z) + state := v.blocks[idx] + if lightOpacity(state) != 0 || lightShapeOccludes(from, state, 0) { + break + } + sources[idx] = true + from = state + } + } + } + return sources +} + +func (v *lightVolume) relax(levels []byte, sources []bool, seeds []int) { + queued := make([]bool, len(levels)) + queue := make([]int, 0, len(seeds)*2) + for _, idx := range seeds { + if idx >= 0 && idx < len(levels) && !queued[idx] { + queued[idx] = true + queue = append(queue, idx) + } + } + for head := 0; head < len(queue); head++ { + idx := queue[head] + queued[idx] = false + desired := v.desiredLight(levels, sources, idx) + if desired == levels[idx] { + continue + } + levels[idx] = desired + x, y, z := v.coordinates(idx) + for _, delta := range lightDirections { + nx, ny, nz := x+delta.x, y+delta.y, z+delta.z + if !v.inside(nx, ny, nz) { + continue + } + nidx := v.indexLocal(nx, ny, nz) + if !queued[nidx] { + queued[nidx] = true + queue = append(queue, nidx) + } + } + } +} + +func (v *lightVolume) desiredLight(levels []byte, sources []bool, idx int) byte { + state := v.blocks[idx] + desired := lightEmission(state) + if sources != nil { + desired = 0 + if sources[idx] { + desired = 15 + } + } + attenuation := lightOpacity(state) + if attenuation < 1 { + attenuation = 1 + } + x, y, z := v.coordinates(idx) + opposite := [...]int{1, 0, 3, 2, 5, 4} + for direction, delta := range lightDirections { + nx, ny, nz := x+delta.x, y+delta.y, z+delta.z + if !v.inside(nx, ny, nz) { + continue + } + nidx := v.indexLocal(nx, ny, nz) + neighbor := levels[nidx] + if neighbor <= attenuation || lightShapeOccludes(v.blocks[nidx], state, opposite[direction]) { + continue + } + candidate := neighbor - attenuation + if candidate > desired { + desired = candidate + } + } + return desired +} + +func (c *Chunk) getLight(block bool, x, y, z int) byte { + si := (y - MinY) >> 4 + if si < 0 || si >= SectionCount { + return 0 + } + layers := &c.skyLight + if block { + layers = &c.blockLight + } + section := layers[si] + if section == nil { + return 0 + } + idx := blockIndex(x, y, z) + b := section[idx>>1] + if idx&1 == 0 { + return b & 0x0f + } + return b >> 4 +} + +func (c *Chunk) setLight(block bool, x, y, z int, value byte) bool { + si := (y - MinY) >> 4 + if si < 0 || si >= SectionCount { + return false + } + layers := &c.skyLight + if block { + layers = &c.blockLight + } + section := layers[si] + if section == nil { + if value == 0 { + return false + } + section = new([2048]byte) + layers[si] = section + } + idx := blockIndex(x, y, z) + old := section[idx>>1] + if idx&1 == 0 { + section[idx>>1] = old&0xf0 | value&0x0f + } else { + section[idx>>1] = old&0x0f | value<<4 + } + return old != section[idx>>1] +} + +// LightAt returns the stored sky and block light at a local block coordinate. +func (c *Chunk) LightAt(x, y, z int) (sky, block byte, ready bool) { + c.mu.RLock() + defer c.mu.RUnlock() + return c.getLight(false, x, y, z), c.getLight(true, x, y, z), c.lightReady +} + +func (c *Chunk) installLight(v *lightVolume) bool { + changed := !c.lightReady + var sky [SectionCount]*[2048]byte + var block [SectionCount]*[2048]byte + baseX := int(c.X)*16 - v.minX + baseZ := int(c.Z)*16 - v.minZ + for y := MinY; y < MinY+WorldHeight; y++ { + for z := 0; z < 16; z++ { + for x := 0; x < 16; x++ { + idx := v.indexLocal(baseX+x, y, baseZ+z) + installNibble(&sky, x, y, z, v.sky[idx]) + installNibble(&block, x, y, z, v.block[idx]) + } + } + } + if !lightLayersEqual(c.skyLight, sky) || !lightLayersEqual(c.blockLight, block) { + changed = true + } + c.skyLight = sky + c.blockLight = block + c.lightReady = true + return changed +} + +func installNibble(layers *[SectionCount]*[2048]byte, x, y, z int, value byte) { + if value == 0 { + return + } + si := (y - MinY) >> 4 + if layers[si] == nil { + layers[si] = new([2048]byte) + } + idx := blockIndex(x, y, z) + if idx&1 == 0 { + layers[si][idx>>1] |= value + } else { + layers[si][idx>>1] |= value << 4 + } +} + +func lightLayersEqual(a, b [SectionCount]*[2048]byte) bool { + for i := 0; i < SectionCount; i++ { + if a[i] == nil || b[i] == nil { + if a[i] != nil || b[i] != nil { + return false + } + continue + } + if *a[i] != *b[i] { + return false + } + } + return true +} + +func (c *Chunk) writeLight(w *protocol.Writer) { + sky, block, highest := c.skyLight, c.blockLight, c.highestFilledSection() + if !c.lightReady { + chunks := map[[2]int32]*Chunk{{c.X, c.Z}: c} + v := newLightVolume(int(c.X), int(c.Z), 1, 1, chunks) + v.calculate() + standalone := &Chunk{X: c.X, Z: c.Z} + standalone.installLight(v) + sky, block = standalone.skyLight, standalone.blockLight + } + maxLightSection := highest + 2 + if maxLightSection < 0 { + maxLightSection = 0 + } + writeLightLayers(w, sky, block, maxLightSection) +} + +func (c *Chunk) highestFilledSection() int { + for si := SectionCount - 1; si >= 0; si-- { + if c.sections[si] == nil { + continue + } + for _, state := range c.sections[si] { + if state != StateAir { + return si + } + } + } + return -1 +} + +func writeLightLayers(w *protocol.Writer, sky, block [SectionCount]*[2048]byte, maxLightSection int) { + if maxLightSection >= lightSections { + maxLightSection = lightSections - 1 + } + var skySections [lightSections]*[2048]byte + var blockSections [lightSections]*[2048]byte + for i := 0; i < SectionCount && i+1 <= maxLightSection; i++ { + skySections[i+1] = sky[i] + blockSections[i+1] = block[i] + } + if maxLightSection == lightSections-1 { + skySections[maxLightSection] = new([2048]byte) + for i := range skySections[maxLightSection] { + skySections[maxLightSection][i] = 0xff } } + var skyMask, blockMask, emptySkyMask, emptyBlockMask uint64 + for i := 0; i <= maxLightSection; i++ { + if skySections[i] == nil { + emptySkyMask |= 1 << i + } else { + skyMask |= 1 << i + } + if blockSections[i] == nil { + emptyBlockMask |= 1 << i + } else { + blockMask |= 1 << i + } + } writeBitSet(w, []uint64{skyMask}) writeBitSet(w, []uint64{blockMask}) writeBitSet(w, []uint64{emptySkyMask}) writeBitSet(w, []uint64{emptyBlockMask}) + writeLightArrays(w, skySections[:]) + writeLightArrays(w, blockSections[:]) +} - w.VarInt(int32(skyCount)) - for i := 0; i < lightSections; i++ { - if skyLight[i] != nil { - w.VarInt(2048) - w.Raw(skyLight[i][:]) +func writeLightArrays(w *protocol.Writer, sections []*[2048]byte) { + count := 0 + for _, section := range sections { + if section != nil { + count++ } } - - w.VarInt(int32(blockCount)) - for i := 0; i < lightSections; i++ { - if blockLight[i] != nil { + w.VarInt(int32(count)) + for _, section := range sections { + if section != nil { w.VarInt(2048) - w.Raw(blockLight[i][:]) + w.Raw(section[:]) } } } -// allSectionsMask returns a bitset (as longs) with the low lightSections bits set. +// EncodeLightUpdate serializes a standalone light_update packet body from a +// consistent chunk snapshot. +func (c *Chunk) EncodeLightUpdate() []byte { + snapshot, _ := c.snapshot() + w := protocol.NewWriter(8192) + w.VarInt(snapshot.X) + w.VarInt(snapshot.Z) + sky, block := snapshot.skyLight, snapshot.blockLight + if !snapshot.lightReady { + chunks := map[[2]int32]*Chunk{{snapshot.X, snapshot.Z}: snapshot} + volume := newLightVolume(int(snapshot.X), int(snapshot.Z), 1, 1, chunks) + volume.calculate() + standalone := &Chunk{X: snapshot.X, Z: snapshot.Z} + standalone.installLight(volume) + sky, block = standalone.skyLight, standalone.blockLight + } + writeLightLayers(w, sky, block, lightSections-1) + return w.Bytes() +} + func allSectionsMask() []uint64 { return []uint64{(uint64(1) << lightSections) - 1} } -// writeBitSet emits a length-prefixed array of longs. func writeBitSet(w *protocol.Writer, longs []uint64) { + for len(longs) > 0 && longs[len(longs)-1] == 0 { + longs = longs[:len(longs)-1] + } w.VarInt(int32(len(longs))) - for _, v := range longs { - w.Int64(int64(v)) + for _, value := range longs { + w.Int64(int64(value)) } } diff --git a/internal/world/light_data.go b/internal/world/light_data.go deleted file mode 100644 index 707002c..0000000 --- a/internal/world/light_data.go +++ /dev/null @@ -1,5 +0,0 @@ -package world -var blockOpacity = [29873]byte{ -0,15,15,15,15,15,15,15,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,15,15,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,15,15,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,15,15,15,15,15,15,15,15,15,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,1,1,1,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,15,15,15,15,15,15,15,15,15,15,15,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,1,0,0,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,1,15,15,0,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,} -var blockEmission = [29873]byte{ -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,14,14,14,14,14,14,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,14,14,14,14,14,14,14,15,0,0,0,0,0,0,14,14,14,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,14,0,0,0,0,0,0,0,14,14,0,0,0,0,0,0,0,0,14,14,0,0,0,0,0,0,0,0,14,14,0,0,0,0,0,0,0,0,14,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,14,14,14,14,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,14,14,14,14,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,} diff --git a/internal/world/light_properties.bin b/internal/world/light_properties.bin new file mode 100644 index 0000000000000000000000000000000000000000..b121079f24e30e6ecc66c25b7990f144248dd6d2 GIT binary patch literal 204677 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 shapes { + return fmt.Errorf("state %d references shape %d of %d", id, blockLightShape[id], shapes) + } + offset += 5 + } + lightFaceShapes = make([][lightShapeBytes]byte, shapes) + for i := range lightFaceShapes { + copy(lightFaceShapes[i][:], data[offset:offset+lightShapeBytes]) + offset += lightShapeBytes + } + return nil +} + +func lightOpacity(state uint16) byte { + if int(state) >= len(blockOpacity) { + return 15 + } + return blockOpacity[state] +} + +func lightEmission(state uint16) byte { + if int(state) >= len(blockEmission) { + return 0 + } + return blockEmission[state] +} + +// lightShapeOccludes mirrors Shapes.faceShapeOccludes for the 1/16-resolution +// face masks emitted from vanilla's VoxelShape data. +func lightShapeOccludes(from, into uint16, direction int) bool { + if direction < 0 || direction >= 6 { + return false + } + var fromShape, intoShape [lightShapeBytes]byte + // Vanilla substitutes an empty shape unless both flags are true. Ordinary + // full cubes are handled by dampening; only shape-aware blocks (slabs, + // stairs, etc.) participate in face occlusion. + if int(from) < len(blockLightFlags) && blockLightFlags[from]&6 == 6 { + fromShape = lightFaceShapes[blockLightShape[from]] + } + if int(into) < len(blockLightFlags) && blockLightFlags[into]&6 == 6 { + intoShape = lightFaceShapes[blockLightShape[into]] + } + opposite := [...]int{1, 0, 3, 2, 5, 4} + fromOffset := direction * 32 + intoOffset := opposite[direction] * 32 + for i := 0; i < 32; i++ { + if fromShape[fromOffset+i]|intoShape[intoOffset+i] != 0xff { + return false + } + } + return true +} diff --git a/internal/world/light_test.go b/internal/world/light_test.go new file mode 100644 index 0000000..31f268a --- /dev/null +++ b/internal/world/light_test.go @@ -0,0 +1,207 @@ +package world + +import ( + _ "embed" + "testing" + + "regionio/internal/protocol" +) + +// Captured from vanilla 26.1.2 after placing minecraft:glowstone at +// (15,100,8). Layout is YZX over [0..30]x[85..115]x[-7..23]. +// +//go:embed testdata/vanilla_glowstone_block_light.bin +var vanillaGlowstoneBlockLight []byte + +func TestIncrementalBlockLightAgainstVanillaFixture(t *testing.T) { + const minX, minY, minZ, size = 0, 85, -7, 31 + if len(vanillaGlowstoneBlockLight) != size*size*size { + t.Fatalf("vanilla fixture length = %d, want %d", len(vanillaGlowstoneBlockLight), size*size*size) + } + cache := NewCache(-1, func(cx, cz int32) *Chunk { + return NewChunk(cx, cz, BiomePlains) + }) + for cz := int32(-1); cz <= 1; cz++ { + for cx := int32(-1); cx <= 1; cx++ { + cache.chunkAt(cx, cz) + } + } + glowstone := nameToStateID("minecraft:glowstone", nil) + if valid, _ := cache.SetBlockWithLight(15, 100, 8, glowstone); !valid { + t.Fatal("glowstone edit rejected") + } + + fixtureIndex := 0 + mismatches := 0 + for y := minY; y < minY+size; y++ { + for z := minZ; z < minZ+size; z++ { + for x := minX; x < minX+size; x++ { + chunk := cache.chunkAt(int32(x>>4), int32(z>>4)) + _, got, ready := chunk.LightAt(x, y, z) + want := vanillaGlowstoneBlockLight[fixtureIndex] + fixtureIndex++ + if !ready || got != want { + if mismatches < 10 { + t.Errorf("block light (%d,%d,%d) = %d, ready=%v; vanilla=%d", x, y, z, got, ready, want) + } + mismatches++ + } + } + } + } + if mismatches > 10 { + t.Errorf("... and %d additional light mismatches", mismatches-10) + } +} + +func TestIncrementalBlockLightCrossesChunkBoundaryAndClears(t *testing.T) { + cache := NewCache(-1, func(cx, cz int32) *Chunk { + return NewChunk(cx, cz, BiomePlains) + }) + left := cache.chunkAt(0, 0) + right := cache.chunkAt(1, 0) + if _, err := cache.FrameErr(0, 0); err != nil { + t.Fatal(err) + } + if _, err := cache.FrameErr(1, 0); err != nil { + t.Fatal(err) + } + + glowstone := nameToStateID("minecraft:glowstone", nil) + if emission := lightEmission(glowstone); emission != 15 { + t.Fatalf("glowstone emission = %d, want 15", emission) + } + valid, changed := cache.SetBlockWithLight(15, 0, 8, glowstone) + if !valid || !containsChunkPos(changed, 0, 0) || !containsChunkPos(changed, 1, 0) { + t.Fatalf("place changed = %v, valid=%v; want chunks (0,0) and (1,0)", changed, valid) + } + assertLight(t, left, 15, 0, 8, 15) + assertLight(t, right, 0, 0, 8, 14) + assertLight(t, right, 1, 0, 8, 13) + + valid, changed = cache.SetBlockWithLight(15, 0, 8, StateAir) + if !valid || !containsChunkPos(changed, 0, 0) || !containsChunkPos(changed, 1, 0) { + t.Fatalf("remove changed = %v, valid=%v; want chunks (0,0) and (1,0)", changed, valid) + } + assertLight(t, left, 15, 0, 8, 0) + assertLight(t, right, 0, 0, 8, 0) +} + +func TestIncrementalSkyLightSpreadsUnderRoofAcrossChunkBoundary(t *testing.T) { + cache := NewCache(-1, func(cx, cz int32) *Chunk { + chunk := NewChunk(cx, cz, BiomePlains) + for z := 0; z < 16; z++ { + for x := 0; x < 16; x++ { + chunk.setBlockRaw(x, 1, z, StateStone) + } + } + return chunk + }) + left := cache.chunkAt(0, 0) + right := cache.chunkAt(1, 0) + if _, err := cache.FrameErr(0, 0); err != nil { + t.Fatal(err) + } + if _, err := cache.FrameErr(1, 0); err != nil { + t.Fatal(err) + } + + assertSky(t, right, 0, 0, 8, 0) + valid, changed := cache.SetBlockWithLight(15, 1, 8, StateAir) + if !valid || !containsChunkPos(changed, 0, 0) || !containsChunkPos(changed, 1, 0) { + t.Fatalf("open changed = %v, valid=%v; want chunks (0,0) and (1,0)", changed, valid) + } + assertSky(t, left, 15, 0, 8, 15) + assertSky(t, right, 0, 0, 8, 14) + assertSky(t, right, 1, 0, 8, 13) + + valid, changed = cache.SetBlockWithLight(15, 1, 8, StateStone) + if !valid || !containsChunkPos(changed, 0, 0) || !containsChunkPos(changed, 1, 0) { + t.Fatalf("close changed = %v, valid=%v; want chunks (0,0) and (1,0)", changed, valid) + } + assertSky(t, left, 15, 0, 8, 0) + assertSky(t, right, 0, 0, 8, 0) +} + +func assertLight(t *testing.T, chunk *Chunk, x, y, z int, want byte) { + t.Helper() + _, got, ready := chunk.LightAt(x, y, z) + if !ready || got != want { + t.Fatalf("block light (%d,%d,%d) = %d, ready=%v; want %d", x, y, z, got, ready, want) + } +} + +func assertSky(t *testing.T, chunk *Chunk, x, y, z int, want byte) { + t.Helper() + got, _, ready := chunk.LightAt(x, y, z) + if !ready || got != want { + t.Fatalf("sky light (%d,%d,%d) = %d, ready=%v; want %d", x, y, z, got, ready, want) + } +} + +func containsChunkPos(chunks []ChunkPos, x, z int32) bool { + for _, chunk := range chunks { + if chunk.X == x && chunk.Z == z { + return true + } + } + return false +} + +func TestEncodeLightUpdateLayout(t *testing.T) { + chunk := NewChunk(-2, 3, BiomePlains) + r := protocol.NewReader(chunk.EncodeLightUpdate()) + + if x, err := r.VarInt(); err != nil || x != -2 { + t.Fatalf("chunk x = %d, %v; want -2", x, err) + } + if z, err := r.VarInt(); err != nil || z != 3 { + t.Fatalf("chunk z = %d, %v; want 3", z, err) + } + + masks := make([]uint64, 4) + for i := range masks { + length, err := r.VarInt() + if err != nil || length < 0 || length > 1 { + t.Fatalf("mask[%d] length = %d, %v; want 0 or 1", i, length, err) + } + if length == 1 { + value, err := r.Int64() + if err != nil { + t.Fatalf("mask[%d]: %v", i, err) + } + masks[i] = uint64(value) + } + } + if masks[0] == 0 || masks[2] == 0 { + t.Fatalf("sky masks were not populated: data=%#x empty=%#x", masks[0], masks[2]) + } + if masks[1] != 0 || masks[3] != (uint64(1)< 31 || localZ < 0 || localZ > 31 { + return nil, fmt.Errorf("world: local coordinates out of bounds") + } + + r.mu.Lock() + defer r.mu.Unlock() + loc := r.offsets[locationIndex(localX, localZ)] if loc == 0 { return nil, ErrChunkNotFound @@ -96,9 +103,6 @@ func (r *RegionFile) ReadChunk(localX, localZ int) ([]byte, error) { return nil, fmt.Errorf("world: invalid sector offset %d", sectorOffset) } - r.mu.Lock() - defer r.mu.Unlock() - // 4-byte length then payload (compression byte + compressed data). var lenBuf [4]byte if _, err := r.f.ReadAt(lenBuf[:], int64(sectorOffset)*sectorSize); err != nil { @@ -124,7 +128,14 @@ func (r *RegionFile) ReadChunk(localX, localZ int) ([]byte, error) { // WriteChunk stores the NBT payload for the chunk, allocating (or reusing) // sectors and updating the offset + timestamp tables. func (r *RegionFile) WriteChunk(localX, localZ int, nbt []byte) error { - compressed := append([]byte{compressionZlib}, zlibDeflate(nbt)...) + if localX < 0 || localX > 31 || localZ < 0 || localZ > 31 { + return fmt.Errorf("world: local coordinates out of bounds") + } + deflated, err := zlibDeflate(nbt) + if err != nil { + return err + } + compressed := append([]byte{compressionZlib}, deflated...) // +4 for the length prefix; sectors needed to hold everything. totalLen := 4 + len(compressed) sectorsNeeded := (totalLen + sectorSize - 1) / sectorSize @@ -229,4 +240,4 @@ var _ = io.EOF // (zlib helpers live in compress.go to keep this file format-focused; the // references below are satisfied there.) -var _ = bytes.Equal \ No newline at end of file +var _ = bytes.Equal diff --git a/internal/world/state_names.go b/internal/world/state_names.go index c2822c6..fb5d22c 100644 --- a/internal/world/state_names.go +++ b/internal/world/state_names.go @@ -23,8 +23,9 @@ type stateName struct { } var ( - stateByIDOnce sync.Once - stateByIDImpl map[uint16]stateName + stateByIDOnce sync.Once + stateByIDImpl map[uint16]stateName + idsByName map[string][]uint16 ) // stateByID returns the named form of a block-state ID, building the lookup @@ -49,12 +50,15 @@ func buildStateTable() { panic("world: parsing embedded blocks.json: " + err.Error()) } stateByIDImpl = make(map[uint16]stateName, 30000) + idsByName = make(map[string][]uint16, len(blocks)) for name, b := range blocks { for _, s := range b.States { if s.ID < 0 || s.ID > 65535 { continue } - stateByIDImpl[uint16(s.ID)] = stateName{Name: name, Properties: s.Properties} + id := uint16(s.ID) + stateByIDImpl[id] = stateName{Name: name, Properties: s.Properties} + idsByName[name] = append(idsByName[name], id) } } } @@ -89,19 +93,14 @@ type paletteEntryKey struct { // Chunk. Unknown names/properties map to air (0). func nameToStateID(name string, props map[string]string) uint16 { stateByIDOnce.Do(buildStateTable) - for id, s := range stateByIDImpl { - if s.Name != name { - continue - } - if propsMatch(s.Properties, props) { + ids := idsByName[name] + for _, id := range ids { + if propsMatch(stateByIDImpl[id].Properties, props) { return id } } - // Fall back to any state of that block if properties don't match exactly. - for id, s := range stateByIDImpl { - if s.Name == name { - return id - } + if len(ids) > 0 { + return ids[0] } return StateAir } diff --git a/internal/world/store.go b/internal/world/store.go index a9c8baf..d6908a7 100644 --- a/internal/world/store.go +++ b/internal/world/store.go @@ -1,6 +1,7 @@ package world import ( + "encoding/json" "fmt" "os" "path/filepath" @@ -62,11 +63,82 @@ type Store struct { regions map[[2]int]*RegionFile } +const worldMetadataFile = "regionio-world.json" + +type worldMetadata struct { + Format int `json:"format"` + Seed int64 `json:"seed"` +} + // NewStore opens (or creates) the world directory at dir, ensuring region/ // exists. Chunks are loaded/saved relative to dir/region. func NewStore(dir string) (*Store, error) { + return newStore(dir, nil) +} + +// NewStoreForSeed opens a persistent world and records its generation seed. +// Reopening the same directory with another seed is rejected to prevent seams +// between previously stored chunks and newly generated terrain. +func NewStoreForSeed(dir string, seed int64) (*Store, error) { + return newStore(dir, &seed) +} + +func newStore(dir string, seed *int64) (*Store, error) { regionDir := filepath.Join(dir, "region") - return &Store{dir: dir, regions: make(map[[2]int]*RegionFile)}, mkdirAll(regionDir) + if err := mkdirAll(regionDir); err != nil { + return nil, err + } + if seed != nil { + if err := validateWorldMetadata(dir, *seed); err != nil { + return nil, err + } + } + return &Store{dir: dir, regions: make(map[[2]int]*RegionFile)}, nil +} + +func validateWorldMetadata(dir string, seed int64) error { + path := filepath.Join(dir, worldMetadataFile) + raw, err := os.ReadFile(path) + if err == nil { + var meta worldMetadata + if err := json.Unmarshal(raw, &meta); err != nil { + return fmt.Errorf("world: decode %s: %w", path, err) + } + if meta.Format != 1 { + return fmt.Errorf("world: unsupported metadata format %d", meta.Format) + } + if meta.Seed != seed { + return fmt.Errorf("world: seed mismatch for %s: stored %d, configured %d", dir, meta.Seed, seed) + } + return nil + } + if !os.IsNotExist(err) { + return err + } + + raw, err = json.MarshalIndent(worldMetadata{Format: 1, Seed: seed}, "", " ") + if err != nil { + return err + } + raw = append(raw, '\n') + tmp, err := os.CreateTemp(dir, ".regionio-world-*.tmp") + if err != nil { + return err + } + tmpName := tmp.Name() + defer os.Remove(tmpName) + if _, err := tmp.Write(raw); err != nil { + tmp.Close() + return err + } + if err := tmp.Sync(); err != nil { + tmp.Close() + return err + } + if err := tmp.Close(); err != nil { + return err + } + return os.Rename(tmpName, path) } // regionFor returns the cached RegionFile for the chunk's region, opening it on @@ -122,6 +194,12 @@ func (s *Store) LoadChunk(cx, cz int32) (*Chunk, error) { // SaveChunk encodes the chunk and writes it to its region file. func (s *Store) SaveChunk(c *Chunk) error { + snapshot, _ := c.snapshot() + return s.saveSnapshot(snapshot) +} + +// saveSnapshot writes a detached chunk snapshot without copying it again. +func (s *Store) saveSnapshot(c *Chunk) error { rf, err := s.regionFor(c.X, c.Z) if err != nil { return err @@ -156,6 +234,9 @@ func chunkToNBT(c *Chunk) *nbt.Compound { Set("Status", nbt.String("minecraft:full")). Set("LastUpdate", nbt.Long(0)). Set("InhabitedTime", nbt.Long(0)) + if c.lightReady { + level.Set("isLightOn", nbt.Byte(1)) + } // Sections: one compound per vertical section, including empty ones so the // section Y range is contiguous (vanilla expects all sections present for @@ -238,6 +319,14 @@ func sectionToNBT(c *Chunk, si int) *nbt.Compound { biomes.Set("data", packIndices(c.biomes[si][:], biomeIndexOf)) } sec.Set("biomes", biomes) + if c.lightReady { + if sky := c.skyLight[si]; sky != nil { + sec.Set("SkyLight", nbt.ByteArray(append([]byte(nil), sky[:]...))) + } + if block := c.blockLight[si]; block != nil { + sec.Set("BlockLight", nbt.ByteArray(append([]byte(nil), block[:]...))) + } + } return sec } @@ -287,7 +376,7 @@ func topNonAirY(c *Chunk, x, z int) int { // for the palette size, mirroring the network paletted-container packing (no // value spans a long boundary in vanilla's chunk NBT). func packIndices(ids []uint16, indexOf map[uint16]int) nbt.LongArray { - bits := bitsNeeded(len(indexOf)) + bits := bitsFor(len(indexOf)) if bits < 1 { bits = 1 } @@ -306,21 +395,10 @@ func packIndices(ids []uint16, indexOf map[uint16]int) nbt.LongArray { return longs } -// bitsNeeded returns ceil(log2(n)) for n>1, or 0 for n<=1. -func bitsNeeded(n int) int { - bits := 0 - v := n - 1 - for v > 0 { - v >>= 1 - bits++ - } - return bits -} - // nbtToChunk decodes the Level-nested chunk NBT back into a Chunk. The chunk's // absolute coordinates are derived from the on-disk xPos/zPos (authoritative); // the region/local coords passed in are used only to validate. -func nbtToChunk(root *nbt.Compound, regionX, regionZ, _, _ int) (*Chunk, error) { +func nbtToChunk(root *nbt.Compound, regionX, regionZ, localX, localZ int) (*Chunk, error) { levelTag, ok := root.Get("Level") if !ok { return nil, fmt.Errorf("world: chunk NBT missing Level") @@ -331,8 +409,18 @@ func nbtToChunk(root *nbt.Compound, regionX, regionZ, _, _ int) (*Chunk, error) } cx := int32(nbtAsInt(level, "xPos")) cz := int32(nbtAsInt(level, "zPos")) + wantX := int32(regionX*32 + localX) + wantZ := int32(regionZ*32 + localZ) + if cx != wantX || cz != wantZ { + return nil, fmt.Errorf("world: chunk coordinates (%d,%d) do not match region slot (%d,%d)", cx, cz, wantX, wantZ) + } c := &Chunk{X: cx, Z: cz, biome: BiomePlains} + if lightTag, ok := level.Get("isLightOn"); ok { + if enabled, ok := lightTag.(nbt.Byte); ok && enabled != 0 { + c.lightReady = true + } + } // Sections. if secTag, ok := level.Get("sections"); ok { @@ -349,12 +437,32 @@ func nbtToChunk(root *nbt.Compound, regionX, regionZ, _, _ int) (*Chunk, error) } readBlockStates(c, si, sc) readBiomes(c, si, sc) + readLightSection(c, si, sc) } } } return c, nil } +func readLightSection(c *Chunk, si int, sc *nbt.Compound) { + read := func(name string) *[2048]byte { + tag, ok := sc.Get(name) + if !ok { + return nil + } + data, ok := tag.(nbt.ByteArray) + if !ok || len(data) != 2048 { + c.lightReady = false + return nil + } + out := new([2048]byte) + copy(out[:], data) + return out + } + c.skyLight[si] = read("SkyLight") + c.blockLight[si] = read("BlockLight") +} + // readBlockStates decodes a section's block_states {palette, data?} into the // chunk's section array. A palette of size 1 fills the whole section; otherwise // the packed data array is unpacked. @@ -427,9 +535,11 @@ func readBiomes(c *Chunk, si int, sc *nbt.Compound) { ids[i] = biomeIDByName(string(e.(nbt.String))) } if len(ids) == 1 { - // Uniform biome for the section: keep the per-cell array nil and set the - // column fallback when this is the only biome source. - c.biome = ids[0] + cells := new([biomeCellsPerSection]uint16) + for i := range cells { + cells[i] = ids[0] + } + c.biomes[si] = cells return } if dataTag, ok := bc.Get("data"); ok { @@ -481,7 +591,7 @@ func nbtAsString(c *nbt.Compound, name string) nbt.String { // unpackIndices reverses packIndices: fills dst with palette IDs using the // packed long array. func unpackIndices(dst []uint16, ids []uint16, data nbt.LongArray) { - bits := bitsNeeded(len(ids)) + bits := bitsFor(len(ids)) if bits < 1 { bits = 1 } diff --git a/internal/world/store_test.go b/internal/world/store_test.go index 470ed09..a59418a 100644 --- a/internal/world/store_test.go +++ b/internal/world/store_test.go @@ -1,10 +1,13 @@ package world import ( + "bytes" "context" "log/slog" "os" "path/filepath" + "sync" + "sync/atomic" "testing" "time" @@ -100,7 +103,8 @@ func TestStoreChunkRoundTrip(t *testing.T) { if !ok { t.Fatal("root not compound") } - decoded, err := nbtToChunk(root, 0, 0, 0, 0) + rx, rz, lx, lz := regionIndex(original.X, original.Z) + decoded, err := nbtToChunk(root, rx, rz, lx, lz) if err != nil { t.Fatalf("nbtToChunk: %v", err) } @@ -117,6 +121,68 @@ func TestStoreChunkRoundTrip(t *testing.T) { if decoded.X != 10 || decoded.Z != -5 { t.Errorf("coords = (%d,%d), want (10,-5)", decoded.X, decoded.Z) } + if decoded.lightReady { + t.Error("legacy chunk without isLightOn loaded as light-ready") + } +} + +func TestStoreLightRoundTrip(t *testing.T) { + dir := t.TempDir() + store, err := NewStore(dir) + if err != nil { + t.Fatal(err) + } + cache := NewCacheWithStore(-1, func(cx, cz int32) *Chunk { + return NewChunk(cx, cz, BiomePlains) + }, store) + left := cache.chunkAt(0, 0) + right := cache.chunkAt(1, 0) + if _, err := cache.FrameErr(0, 0); err != nil { + t.Fatal(err) + } + if _, err := cache.FrameErr(1, 0); err != nil { + t.Fatal(err) + } + glowstone := nameToStateID("minecraft:glowstone", nil) + if valid, _ := cache.SetBlockWithLight(15, 0, 8, glowstone); !valid { + t.Fatal("glowstone edit rejected") + } + wantLeftSky, wantLeftBlock, ready := left.LightAt(15, 0, 8) + if !ready || wantLeftBlock != 15 { + t.Fatalf("pre-save source light = sky %d block %d ready %v", wantLeftSky, wantLeftBlock, ready) + } + wantRightSky, wantRightBlock, ready := right.LightAt(0, 0, 8) + if !ready || wantRightBlock != 14 { + t.Fatalf("pre-save neighbor light = sky %d block %d ready %v", wantRightSky, wantRightBlock, ready) + } + if err := cache.SaveAll(); err != nil { + t.Fatal(err) + } + if err := store.Close(); err != nil { + t.Fatal(err) + } + + store, err = NewStore(dir) + if err != nil { + t.Fatal(err) + } + defer store.Close() + loadedLeft, err := store.LoadChunk(0, 0) + if err != nil { + t.Fatal(err) + } + gotSky, gotBlock, gotReady := loadedLeft.LightAt(15, 0, 8) + if !gotReady || gotSky != wantLeftSky || gotBlock != wantLeftBlock { + t.Fatalf("loaded source light = sky %d block %d ready %v; want sky %d block %d ready", gotSky, gotBlock, gotReady, wantLeftSky, wantLeftBlock) + } + loadedRight, err := store.LoadChunk(1, 0) + if err != nil { + t.Fatal(err) + } + gotSky, gotBlock, gotReady = loadedRight.LightAt(0, 0, 8) + if !gotReady || gotSky != wantRightSky || gotBlock != wantRightBlock { + t.Fatalf("loaded neighbor light = sky %d block %d ready %v; want sky %d block %d ready", gotSky, gotBlock, gotReady, wantRightSky, wantRightBlock) + } } // TestStoreSaveLoadIntegration is the end-to-end "world survives restart" test: @@ -207,8 +273,8 @@ func TestCacheAutosavePersistsEdits(t *testing.T) { // Wait for at least one autosave cycle. time.Sleep(250 * time.Millisecond) - cancel() // stop the autosave loop so it releases the store - <-autosaveDone // wait for the goroutine to fully exit + cancel() // stop the autosave loop so it releases the store + <-autosaveDone // wait for the goroutine to fully exit store.Close() // Fresh cache over the same store should see the edit without SaveAll. @@ -223,3 +289,130 @@ func TestCacheAutosavePersistsEdits(t *testing.T) { t.Errorf("autosaved block = %d, want bedrock %d", got, StateBedrock) } } + +func TestStoreRejectsSeedMismatch(t *testing.T) { + dir := t.TempDir() + store, err := NewStoreForSeed(dir, 12345) + if err != nil { + t.Fatal(err) + } + if err := store.Close(); err != nil { + t.Fatal(err) + } + store, err = NewStoreForSeed(dir, 12345) + if err != nil { + t.Fatalf("reopen with matching seed: %v", err) + } + if err := store.Close(); err != nil { + t.Fatal(err) + } + if _, err := NewStoreForSeed(dir, 54321); err == nil { + t.Fatal("opening a world with a different seed succeeded") + } +} + +func TestCacheDoesNotRegenerateCorruptStoredChunk(t *testing.T) { + dir := t.TempDir() + regionDir := filepath.Join(dir, "region") + rf, err := OpenRegion(regionDir, 0, 0) + if err != nil { + t.Fatal(err) + } + corrupt := []byte("not an nbt document") + if err := rf.WriteChunk(0, 0, corrupt); err != nil { + t.Fatal(err) + } + if err := rf.Close(); err != nil { + t.Fatal(err) + } + + store, err := NewStore(dir) + if err != nil { + t.Fatal(err) + } + defer store.Close() + var generated atomic.Bool + cache := NewCacheWithStore(256, func(cx, cz int32) *Chunk { + generated.Store(true) + return NewChunk(cx, cz, BiomePlains) + }, store) + if _, err := cache.FrameErr(0, 0); err == nil { + t.Fatal("FrameErr succeeded for corrupt stored chunk") + } + if generated.Load() { + t.Fatal("generator ran after a stored chunk read error") + } + if cache.SetBlock(0, SeaLevel, 0, StateBedrock) { + t.Fatal("SetBlock accepted an edit over a corrupt stored chunk") + } + if err := cache.SaveAll(); err != nil { + t.Fatal(err) + } + + _, _, lx, lz := regionIndex(0, 0) + rf = store.regions[[2]int{0, 0}] + raw, err := rf.ReadChunk(lx, lz) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(raw, corrupt) { + t.Fatalf("stored corrupt payload was overwritten: %q", raw) + } +} + +func TestConcurrentAutosavePreservesLatestEdit(t *testing.T) { + dir := t.TempDir() + store, err := NewStore(dir) + if err != nil { + t.Fatal(err) + } + cache := NewCacheWithStore(256, flatGen(), store) + cache.chunkAt(0, 0) + + done := make(chan struct{}) + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + for i := 0; i < 100; i++ { + state := StateStone + if i%2 == 0 { + state = StateDirt + } + cache.SetBlock(5, SeaLevel, 5, state) + } + close(done) + }() + go func() { + defer wg.Done() + for { + select { + case <-done: + return + default: + _ = cache.flushDirty() + } + } + }() + wg.Wait() + cache.SetBlock(5, SeaLevel, 5, StateBedrock) + if err := cache.SaveAll(); err != nil { + t.Fatal(err) + } + if err := store.Close(); err != nil { + t.Fatal(err) + } + + store, err = NewStore(dir) + if err != nil { + t.Fatal(err) + } + defer store.Close() + loaded, err := store.LoadChunk(0, 0) + if err != nil { + t.Fatal(err) + } + if got := loaded.GetBlock(5, SeaLevel, 5); got != StateBedrock { + t.Fatalf("persisted final block = %d, want %d", got, StateBedrock) + } +} diff --git a/internal/world/terrain.go b/internal/world/terrain.go index 457c7c9..8103d65 100644 --- a/internal/world/terrain.go +++ b/internal/world/terrain.go @@ -47,7 +47,7 @@ func generateFromDensity(d worldgen.DensityFunction, cx, cz int32) *Chunk { col := &columns[lx][lz] for i := 0; i < WorldHeight; i++ { if s := col[i]; s != StateAir { - c.SetBlock(lx, MinY+i, lz, s) + c.setBlockRaw(lx, MinY+i, lz, s) } } } diff --git a/internal/world/testdata/vanilla_glowstone_block_light.bin b/internal/world/testdata/vanilla_glowstone_block_light.bin new file mode 100644 index 0000000000000000000000000000000000000000..8ab95820af67bbd22c4636ce164f3c6cb4d7ed80 GIT binary patch literal 29791 zcmeI(?N*ym5QX8y`mJs6|85&X2t+vNaji-MWc$a|(UX~X_6z7*-R<%b0wE9rA+SgS z`$cw@ra~YDS^{-L_YbHCV+e%6tOQ_S)zJOJFVu1Rtdr&N5ZD3%I0OT0h93NVL>;H2 zw=Lk$3?TplumBFhz>1-bzfY*+bo7SFz$N7nSOx*)7#6@G7+5y+=A=bTj#KpK!GBoR4=1#k!k77XqDeM23mqc= zV4p|>vVW_CIc5x#ZItMutak8^J!224o3IM2=wr9D;#Uh7SDw zL>;H2H%taDpo*PfqsRl&ge-1Lyl+nfDqxC@V4p|>vVu@USOX+V~cMC2G2z#$koGW62lzo_GM^oGg6 z1yr#UY!rDwnvg{#86Lo*`2B@Io9NQUPI)ivyR%_BEnO8!M2=wr9D;!(e|Nic*Y6*n za93>|XmUf0voeOEp&hd_N}^=GDo4VCBj#+7pYXi~A> zdUpNi^Zp(?{nxp3z3{iau3qZ=R4>lm*2~n6^$PQ*j*TuSdg812&|Yu-R~6-mexZc K1VUi-1pWaDU_ycb literal 0 HcmV?d00001 diff --git a/internal/world/vanilla.go b/internal/world/vanilla.go index ebacad8..0e02e33 100644 --- a/internal/world/vanilla.go +++ b/internal/world/vanilla.go @@ -15,7 +15,7 @@ import ( const ( cellWidth = 4 cellHeight = 8 - cellsXZ = 16 / cellWidth // 4 + cellsXZ = 16 / cellWidth // 4 cellsY = WorldHeight / cellHeight // 48 ) @@ -82,8 +82,8 @@ func generateVanilla(od *worldgen.OverworldDensity, seed int64, cx, cz int32) *C surfaceRule, ruleErr := od.SurfaceRule() var columns [16][16][WorldHeight]uint16 - var surfTop [16][16]int // top solid index, -1 if none - var grass [16][16]bool // grassy land surface (tree-plantable) + var surfTop [16][16]int // top solid index, -1 if none + var grass [16][16]bool // grassy land surface (tree-plantable) for lx := 0; lx < 16; lx++ { wg.Add(1) go func(lx int) { @@ -105,7 +105,7 @@ func generateVanilla(od *worldgen.OverworldDensity, seed int64, cx, cz int32) *C col := &columns[lx][lz] for i := 0; i < WorldHeight; i++ { if s := col[i]; s != StateAir { - c.SetBlock(lx, MinY+i, lz, s) + c.setBlockRaw(lx, MinY+i, lz, s) } } } diff --git a/tools/VanillaLightDump.java b/tools/VanillaLightDump.java new file mode 100644 index 0000000..91f4b6e --- /dev/null +++ b/tools/VanillaLightDump.java @@ -0,0 +1,111 @@ +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.minecraft.SharedConstants; +import net.minecraft.core.Direction; +import net.minecraft.server.Bootstrap; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.shapes.VoxelShape; + +// Dumps protocol-775 lighting properties directly from vanilla runtime state. +// Compile/run against the unpacked 26.1.2 server classpath and redirect stdout +// to internal/world/light_properties.bin. +public final class VanillaLightDump { + private record MaskKey(byte[] data) { + @Override public boolean equals(Object other) { + return other instanceof MaskKey key && Arrays.equals(data, key.data); + } + @Override public int hashCode() { return Arrays.hashCode(data); } + } + + public static void main(String[] args) throws Exception { + SharedConstants.tryDetectVersion(); + Bootstrap.bootStrap(); + + int count = Block.BLOCK_STATE_REGISTRY.size(); + byte[] dampening = new byte[count]; + byte[] emission = new byte[count]; + byte[] flags = new byte[count]; + int[] shapeIndex = new int[count]; + List shapes = new ArrayList<>(); + Map indices = new HashMap<>(); + + for (BlockState state : Block.BLOCK_STATE_REGISTRY) { + int id = Block.getId(state); + dampening[id] = (byte) state.getLightDampening(); + emission[id] = (byte) state.getLightEmission(); + int stateFlags = 0; + if (state.propagatesSkylightDown()) stateFlags |= 1; + if (state.canOcclude()) stateFlags |= 2; + if (state.useShapeForLightOcclusion()) stateFlags |= 4; + flags[id] = (byte) stateFlags; + + byte[] masks = faceMasks(state); + MaskKey key = new MaskKey(masks); + Integer index = indices.get(key); + if (index == null) { + index = shapes.size(); + indices.put(key, index); + shapes.add(masks); + } + shapeIndex[id] = index; + } + + DataOutputStream out = new DataOutputStream(new BufferedOutputStream(System.out)); + out.writeInt(0x52494f4c); // RIOL + out.writeInt(1); + out.writeInt(count); + out.writeInt(shapes.size()); + for (int id = 0; id < count; id++) { + out.writeByte(dampening[id]); + out.writeByte(emission[id]); + out.writeByte(flags[id]); + out.writeShort(shapeIndex[id]); + } + for (byte[] shape : shapes) out.write(shape); + out.flush(); + } + + private static byte[] faceMasks(BlockState state) { + byte[] result = new byte[Direction.values().length * 32]; + for (Direction direction : Direction.values()) { + VoxelShape shape = state.getFaceOcclusionShape(direction); + List boxes = shape.toAabbs(); + int base = direction.get3DDataValue() * 32; + for (int v = 0; v < 16; v++) { + for (int u = 0; u < 16; u++) { + double du = (u + 0.5) / 16.0; + double dv = (v + 0.5) / 16.0; + if (covered(boxes, direction, du, dv)) { + int bit = v * 16 + u; + result[base + bit / 8] |= (byte) (1 << (bit % 8)); + } + } + } + } + return result; + } + + private static boolean covered(List boxes, Direction direction, double u, double v) { + for (AABB box : boxes) { + boolean inside = switch (direction.getAxis()) { + case Y -> contains(box.minX, box.maxX, u) && contains(box.minZ, box.maxZ, v); + case Z -> contains(box.minX, box.maxX, u) && contains(box.minY, box.maxY, v); + case X -> contains(box.minZ, box.maxZ, u) && contains(box.minY, box.maxY, v); + }; + if (inside) return true; + } + return false; + } + + private static boolean contains(double min, double max, double value) { + return value >= min - 1.0e-7 && value <= max + 1.0e-7; + } +} diff --git a/tools/vanilla_light_fixture.go b/tools/vanilla_light_fixture.go new file mode 100644 index 0000000..012cceb --- /dev/null +++ b/tools/vanilla_light_fixture.go @@ -0,0 +1,148 @@ +// Command vanilla_light_fixture extracts a compact block-light parity fixture +// from a vanilla world containing glowstone at (15,100,8). +package main + +import ( + "flag" + "fmt" + "os" + "path/filepath" + + "regionio/internal/nbt" + "regionio/internal/world" +) + +const ( + minX, minY, minZ = 0, 85, -7 + sizeX, sizeY, sizeZ = 31, 31, 31 +) + +type lightChunk struct { + sections map[int][]byte +} + +func main() { + worldDir := flag.String("world", "", "vanilla overworld directory") + output := flag.String("output", "", "fixture output path") + flag.Parse() + if *worldDir == "" || *output == "" { + fmt.Fprintln(os.Stderr, "usage: go run ./tools/vanilla_light_fixture.go -world -output ") + os.Exit(2) + } + + chunks := make(map[[2]int]*lightChunk) + data := make([]byte, 0, sizeX*sizeY*sizeZ) + for y := minY; y < minY+sizeY; y++ { + for z := minZ; z < minZ+sizeZ; z++ { + for x := minX; x < minX+sizeX; x++ { + key := [2]int{x >> 4, z >> 4} + chunk := chunks[key] + if chunk == nil { + var err error + chunk, err = readLightChunk(*worldDir, key[0], key[1]) + if err != nil { + panic(err) + } + chunks[key] = chunk + } + section := chunk.sections[y>>4] + if section == nil { + data = append(data, 0) + continue + } + idx := (y&15)<<8 | (z&15)<<4 | (x & 15) + value := section[idx>>1] + if idx&1 == 0 { + data = append(data, value&0x0f) + } else { + data = append(data, value>>4) + } + } + } + } + if err := os.WriteFile(*output, data, 0o644); err != nil { + panic(err) + } +} + +func readLightChunk(worldDir string, cx, cz int) (*lightChunk, error) { + rx, rz := floorDiv(cx, 32), floorDiv(cz, 32) + regionDir := filepath.Join(worldDir, "dimensions", "minecraft", "overworld", "region") + if _, err := os.Stat(regionDir); err != nil { + regionDir = filepath.Join(worldDir, "region") + } + region, err := world.OpenRegion(regionDir, rx, rz) + if err != nil { + return nil, err + } + defer region.Close() + raw, err := region.ReadChunk(cx-rx*32, cz-rz*32) + if err != nil { + return nil, fmt.Errorf("chunk (%d,%d): %w", cx, cz, err) + } + _, tag, err := nbt.UnmarshalNamed(raw) + if err != nil { + return nil, err + } + root, ok := tag.(*nbt.Compound) + if !ok { + return nil, fmt.Errorf("chunk (%d,%d): root is not a compound", cx, cz) + } + if level, ok := root.Get("Level"); ok { + root, _ = level.(*nbt.Compound) + } + sectionsTag, ok := root.Get("sections") + if !ok { + return nil, fmt.Errorf("chunk (%d,%d): missing sections", cx, cz) + } + sections, ok := sectionsTag.(nbt.List) + if !ok { + return nil, fmt.Errorf("chunk (%d,%d): sections is not a list", cx, cz) + } + out := &lightChunk{sections: make(map[int][]byte)} + for _, sectionTag := range sections.Elems { + section, ok := sectionTag.(*nbt.Compound) + if !ok { + continue + } + yTag, ok := section.Get("Y") + if !ok { + continue + } + y, ok := integerTag(yTag) + if !ok { + continue + } + lightTag, ok := section.Get("BlockLight") + if !ok { + continue + } + light, ok := lightTag.(nbt.ByteArray) + if !ok || len(light) != 2048 { + return nil, fmt.Errorf("chunk (%d,%d) section %d: invalid BlockLight", cx, cz, y) + } + out.sections[y] = append([]byte(nil), light...) + } + return out, nil +} + +func integerTag(tag nbt.Tag) (int, bool) { + switch value := tag.(type) { + case nbt.Byte: + return int(value), true + case nbt.Short: + return int(value), true + case nbt.Int: + return int(value), true + default: + return 0, false + } +} + +func floorDiv(value, divisor int) int { + quotient := value / divisor + if value < 0 && value%divisor != 0 { + quotient-- + } + return quotient +} diff --git a/world/region/r.-1.-1.mca b/world/region/r.-1.-1.mca index aaf64ff0c7745406d764a801e5561701f15ffadc..9df442246916cb72b3c0569f92fee72d153beae3 100644 GIT binary patch delta 5162 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 Y`r8}cc3-so5C8!G|B3!I%&`IB3Gy95lK=n! delta 61 zcmZp8pxDr$xS@)FBD2cmdHe~R*#&;{PZZ#6W>RQpQeXsPCLm@8Viq7~1!6WJX5Y@F I!0}HW06wY^5C8xG diff --git a/world/region/r.-1.0.mca b/world/region/r.-1.0.mca index 8cd3fac06e5b1e2cbe4eea908a0fb7b6e5f404f0..85a95cf4a858eb09985d0272a5358f5566670893 100644 GIT binary patch delta 5823 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 F{{a?7SIYnZ delta 103 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

t_bxU;sP?_0RNc6zK%|KdpDj3lHk>pX{ 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 D1mj*e delta 1730 zcmV;z20i(Jzz=}n4FmuH0s)b+9+4tok-!|0o@9~09J3w)N)$-CPKu&&fVj0gIfOfP z@sJ^Zfje|)hYsBu6v(7@ZpQ-Y8{R__KU|8UC`VSTFTUhKKP0*z=brQ9xx7C2+{-LL z8qUkQr!N_fYubNR0PZLYnft~s4J!@kQNeZr$RPBqR`qegc8`yb-_kJgz|*}=7cNxH zs!?+ERquzZo0f$jdtY}wTr2CI0U;1ftJQUV#q^AF(X2%MlR&#h$usS$n+1T&X`p^a ztY=|r#j;Bq%dV$;hC2!8YPw~3ok?Yx2BNA{e z!J2KAmu;sd`%X*d80A2Z$g_|wICjl&JkxM#NL!`{nN{6#4S*>?W!=*OU>gR6z!2XM zVgv+;3c45>r~&{F05Y&-8uABuSdWP!a{eASmPt0;OHSSi7QNffBGRU`C>NO3NtdE11OekB#=L@ynxOGmzzW6~^BJ5I1Xd{`O%W7a z#7Yrk4fBeY(fFl5Xk?6x1&oP)uUP6`}p#yi^)p1ZneoSwPcoFGu=Qq#8}oh_%n@S z2GD1^>VK=y&ub()honM>{|7;KK>eXX0-&V9ZS;cw0l=*RDU*cM=3*bQ?EMI186S^j zwVHq7v`J$ow{*{NP9$s@i)6Gw-5P4THb#$#hvpFk;M6Jge)KCSbc6Z;f29UAa2>9HM1Fc}jNZ}fl5FS6!+~IKbgIK*&hBW#B`?-H)HWZvohNzDvneMtI>5neSv!7;~8nGMr zvgvqH|8%QgH=U?kh+Tm@y6Y|nPkWNat=e0UqkHbA?swx)?U=QiQC_z7jb+PTJ7ECn zk4zV+$IywlWUs7PM!Vm!Gaj*6v-IkQ8~bDYTQYBaj4=iVmmi_=lq&kIYIIH4PKkf~ zX8&Zb_Bpk&(TIDZFTtz|9H8goHUKLg0KguRsow+OgS^9+fC{L|VIvi#B3-Tyxrj#u zO7vCU7yUdnt8(fMNXh%pAtn0-0G5o|n*-nq5Kie_WTVimbUEU162+;aFLA24Jt_Js zx1e&W?5mtA9{PD-1(5v(0FHReiidxYT7Y0m0nlO7M(YN%!{$qjisZ>?Ulsj4AtV0+ z646&@Rq-I;N#%Vt4;)WsOZ2yqO7aR(1%QLdjVRSXvG+jc9{>e*x7n8c56eZu2u*x&7t^$BpRjYo7}iPlG29O?w9&?Z-=ETon)YxCMVz4E=mT zq{vri7gXen`;_R*FZ}s`s$_xwe!gCLdbM&?pRZSr>OGXdCO?1vMg7hnN&8QKnXlWw zc~d#6-?>^j`geV)KL0O27#dDjx}VJ&b=|BdN4S%oy8kD)#k!tuI_?Rh6RUd3tgb<( zYCGOK4QO4rR$*er_B^{nyjg$OYX;D+=ji6zx<|wLAWO0AwKb#sftNAHHH*wpO|PE( zpx$5#RSAQ=8EbYhPXpQ_FEMHvy6bhL-qI~=*|VL}dRJ#V>UPRSD401t?DUGpM*=B4 zY&7G55|+2+jvo<*c7b}k+~mI(ls^yqr7*+3T^<(GDg1J~|M#MKklKIbLq-N;9F)7e zJ3+Y(oy(>?96xkm+}O%~x*L{sd%GzgH8P=lnVt6b7-Nhv#u)ABBiGEF?OyamuGtUM zL}E