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 0000000..b121079 Binary files /dev/null and b/internal/world/light_properties.bin differ diff --git a/internal/world/light_properties.go b/internal/world/light_properties.go new file mode 100644 index 0000000..c864455 --- /dev/null +++ b/internal/world/light_properties.go @@ -0,0 +1,113 @@ +package world + +import ( + _ "embed" + "encoding/binary" + "fmt" +) + +// lightPropertiesBinary is generated by tools/VanillaLightDump.java directly +// from the 26.1.2 runtime block-state registry. +// +//go:embed light_properties.bin +var lightPropertiesBinary []byte + +var ( + blockOpacity [totalBlockStates]byte + blockEmission [totalBlockStates]byte + blockLightFlags [totalBlockStates]byte + blockLightShape [totalBlockStates]uint16 + lightFaceShapes [][lightShapeBytes]byte +) + +const ( + lightPropertiesMagic = 0x52494f4c // RIOL + lightPropertiesVersion = 1 + lightShapeBytes = 6 * 32 // six 16x16 face masks +) + +func init() { + if err := decodeLightProperties(lightPropertiesBinary); err != nil { + panic(fmt.Sprintf("world: decode vanilla light properties: %v", err)) + } +} + +func decodeLightProperties(data []byte) error { + if len(data) < 16 { + return fmt.Errorf("header is truncated") + } + if binary.BigEndian.Uint32(data[0:4]) != lightPropertiesMagic { + return fmt.Errorf("invalid magic") + } + if version := binary.BigEndian.Uint32(data[4:8]); version != lightPropertiesVersion { + return fmt.Errorf("unsupported version %d", version) + } + states := int(binary.BigEndian.Uint32(data[8:12])) + shapes := int(binary.BigEndian.Uint32(data[12:16])) + if states != totalBlockStates { + return fmt.Errorf("state count %d, want %d", states, totalBlockStates) + } + want := 16 + states*5 + shapes*lightShapeBytes + if len(data) != want { + return fmt.Errorf("length %d, want %d", len(data), want) + } + + offset := 16 + for id := 0; id < states; id++ { + blockOpacity[id] = data[offset] + blockEmission[id] = data[offset+1] + blockLightFlags[id] = data[offset+2] + blockLightShape[id] = binary.BigEndian.Uint16(data[offset+3 : offset+5]) + if int(blockLightShape[id]) >= 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 0000000..8ab9582 Binary files /dev/null and b/internal/world/testdata/vanilla_glowstone_block_light.bin differ 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 aaf64ff..9df4422 100644 Binary files a/world/region/r.-1.-1.mca and b/world/region/r.-1.-1.mca differ diff --git a/world/region/r.-1.0.mca b/world/region/r.-1.0.mca index 8cd3fac..85a95cf 100644 Binary files a/world/region/r.-1.0.mca and b/world/region/r.-1.0.mca differ diff --git a/world/region/r.0.-1.mca b/world/region/r.0.-1.mca index ce2fd58..879bb09 100644 Binary files a/world/region/r.0.-1.mca and b/world/region/r.0.-1.mca differ diff --git a/world/region/r.0.0.mca b/world/region/r.0.0.mca index 6473e91..dae2152 100644 Binary files a/world/region/r.0.0.mca and b/world/region/r.0.0.mca differ